From c928884dccdb356314aa6e680a9c61b8e59e445d Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Fri, 29 Aug 2025 11:57:29 +0100 Subject: [PATCH 01/13] feat: trying to improve running speed of the retry code in e2e tests --- .gitignore | 1 + .../src/api/RetryCore/Retry.ts | 136 ++++++ tests/playwright-tests/src/api/apiHelper.ts | 414 +++++------------- .../src/api/core/assertOnTypes.ts | 152 +++++++ tests/playwright-tests/src/config/env.ts | 3 + ...c4b-7667-block-paricipant-tessuite.spec.ts | 36 +- .../playwright-tests/src/tests/steps/steps.ts | 4 +- 7 files changed, 405 insertions(+), 341 deletions(-) create mode 100644 tests/playwright-tests/src/api/RetryCore/Retry.ts create mode 100644 tests/playwright-tests/src/api/core/assertOnTypes.ts diff --git a/.gitignore b/.gitignore index 3dd4d4d6c..5250244c4 100644 --- a/.gitignore +++ b/.gitignore @@ -118,3 +118,4 @@ application/CohortManager/src/Functions/*.json !**/playwright-tests/src/tests/e2e/testFiles/**/*.json +tests/.vscode/settings.json diff --git a/tests/playwright-tests/src/api/RetryCore/Retry.ts b/tests/playwright-tests/src/api/RetryCore/Retry.ts new file mode 100644 index 000000000..efbbef463 --- /dev/null +++ b/tests/playwright-tests/src/api/RetryCore/Retry.ts @@ -0,0 +1,136 @@ +import { APIResponse, expect } from "@playwright/test"; +import { config } from "../../config/env"; +import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; +import { ApiResponse } from "../core/types"; + +const NHS_NUMBER_KEY = config.nhsNumberKey; +const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; + +export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { + let status = false; + let endpoint = ""; + let errorTrace: any = undefined; + + try { + for (const apiValidation of validationJson) { + endpoint = apiValidation.validations.apiEndpoint; + var response = await pollAPI(endpoint, apiValidation, request); + console.info(response); + switch(response.status()) { + case 204: + console.info("now handling no content response"); + const expectedCount = apiValidation.validations.expectedCount; + status = await HandleNoContentResponse(expectedCount, apiValidation, endpoint); + break; + case 200: + console.info("now handling OK response"); + status = await handleOKResponse(apiValidation, endpoint, response); + default: + console.error("there was an error when handling response from "); + break; + } + + } + } + catch (error) { + const errorMsg = `Endpoint: ${endpoint}, Error: ${error instanceof Error ? error.stack || error.message : error}`; + errorTrace = errorMsg; + console.error(errorMsg); + } + return { status, errorTrace }; +} + +async function handleOKResponse(apiValidation: any, endpoint: string, response: any ) : Promise{ + const responseBody = await response.json(); + expect(Array.isArray(responseBody)).toBeTruthy(); + + const { matchingObject, nhsNumber, matchingObjects } = await findMatchingObject(endpoint, responseBody, apiValidation); + + console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); + console.info(`From Response ${JSON.stringify(matchingObject, null, 2)}`); + return await validateFields(apiValidation, matchingObject, nhsNumber, matchingObjects); +} + + +async function HandleNoContentResponse(expectedCount: number, apiValidation: any, endpoint: string): Promise { + if (expectedCount !== undefined && Number(expectedCount) === 0) { + console.info(`✅ Status 204: Expected 0 records for endpoint ${endpoint}`); + + // Get NHS number for validation + const nhsNumber = apiValidation.validations.NHSNumber || + apiValidation.validations.NhsNumber || + apiValidation.validations[NHS_NUMBER_KEY] || + apiValidation.validations[NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC]; + + console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); + console.info(`From Response: null (204 No Content - 0 records as expected)`); + return await validateFields(apiValidation, null, nhsNumber, []); + } else { + // 204 is unexpected, throw error to trigger retry + throw new Error(`Status 204: No data found in the table using endpoint ${endpoint}`); + } +} + + +async function pollAPI(endpoint: string, apiValidation: any, request: any): Promise { + let apiResponse: APIResponse | null = null; + let i = 0; + + let maxNumberOfRetries = config.maxNumberOfRetries; + let maxTimeBetweenRequests = config.maxTimeBetweenRequests; + + console.info(`now trying request for ${maxNumberOfRetries} retries`); + while (i < maxNumberOfRetries) { + try { + apiResponse = await fetchApiResponse(endpoint, request); + if (apiResponse.status() == 200) { + console.info("200 response found") + break; + } + } + catch(exception) { + console.error("Error reading request body:", exception); + } + i++; + + console.info(`http response completed ${i}/${maxNumberOfRetries} of number of reties`); + await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); + } + + if (!apiResponse) { + throw new Error("apiResponse was never assigned"); + } + + return apiResponse; +} + + +export async function pollApiForOKResponse(httpRequest: () => Promise): Promise{ + let apiResponse: ApiResponse | null = null; + let i = 0; + let maxNumberOfRetries = config.maxNumberOfRetries; + let maxTimeBetweenRequests = config.maxTimeBetweenRequests; + + console.info(`now trying request for ${maxNumberOfRetries} retries`); + while (i < maxNumberOfRetries) { + try { + apiResponse = await httpRequest(); + if (apiResponse.status == 200) { + console.info("200 response found") + break; + } + } + catch(exception) { + console.error("Error reading request body:", exception); + } + i++; + + console.info(`http response completed ${i}/${maxNumberOfRetries} of number of reties`); + await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); + } + + if (!apiResponse) { + throw new Error("apiResponse was never assigned"); + } + return apiResponse; +}; diff --git a/tests/playwright-tests/src/api/apiHelper.ts b/tests/playwright-tests/src/api/apiHelper.ts index 491d70c0b..b5b56f034 100644 --- a/tests/playwright-tests/src/api/apiHelper.ts +++ b/tests/playwright-tests/src/api/apiHelper.ts @@ -1,114 +1,43 @@ -import { APIResponse, expect } from "@playwright/test"; +import { APIResponse } from "@playwright/test"; import { config } from "../config/env"; +import { assertOnCounts, assertOnRecordDateTimes, assertOnNhsNumber, MatchDynamicType, MatchOnRuleDescriptionDynamic } from "./core/assertOnTypes"; -const apiRetry = Number(config.apiRetry); -const initialWaitTime = Number(config.apiWaitTime) || 2000; -const endpointCohortDistributionDataService = config.endpointCohortDistributionDataService; -const endpointParticipantManagementDataService = config.endpointParticipantManagementDataService; -const endpointExceptionManagementDataService = config.endpointExceptionManagementDataService; -const endpointParticipantDemographicDataService = config.endpointParticipantDemographicDataService; -const COHORT_DISTRIBUTION_SERVICE = config.cohortDistributionService; -const PARTICIPANT_MANAGEMENT_SERVICE = config.participantManagementService; -const EXCEPTION_MANAGEMENT_SERVICE = config.exceptionManagementService; -const PARTICIPANT_DEMOGRAPHIC_SERVICE = config.participantDemographicDataService; -const NHS_NUMBER_KEY = config.nhsNumberKey; -const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; -const IGNORE_VALIDATION_KEY = config.ignoreValidationKey; -let waitTime = initialWaitTime; -let response: APIResponse; - -export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { - let status = false; - let endpoint = ""; - let errorTrace: any = undefined; - - for (let attempt = 1; attempt <= apiRetry; attempt++) { - if (status) break; - - try { - for (const apiValidation of validationJson) { - endpoint = apiValidation.validations.apiEndpoint; - response = await fetchApiResponse(endpoint, request); +export async function fetchApiResponse(endpoint: string, request: any): Promise { + let currentEndPoint = endpoint.toLowerCase() - // Handle 204 No Content responses BEFORE parsing JSON - if (response.status() === 204) { - // Check if 204 is expected (expectedCount: 0) - const expectedCount = apiValidation.validations.expectedCount; + switch(endpoint) { + case `api/${config.cohortDistributionService}`: + return request.get(`${config.endpointCohortDistributionDataService}${currentEndPoint}`); - if (expectedCount !== undefined && Number(expectedCount) === 0) { - console.info(`✅ Status 204: Expected 0 records for endpoint ${endpoint}`); + case `api/${config.participantManagementService}`: + return request.get(`${config.endpointParticipantManagementDataService}${currentEndPoint}`); - // Get NHS number for validation - const nhsNumber = apiValidation.validations.NHSNumber || - apiValidation.validations.NhsNumber || - apiValidation.validations[NHS_NUMBER_KEY] || - apiValidation.validations[NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC]; + case `api/${config.exceptionManagementService}`: + return request.get(`${config.endpointExceptionManagementDataService}${currentEndPoint}`); - console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); - console.info(`From Response: null (204 No Content - 0 records as expected)`); - status = await validateFields(apiValidation, null, nhsNumber, []); - } else { - // 204 is unexpected, throw error to trigger retry - throw new Error(`Status 204: No data found in the table using endpoint ${endpoint}`); - } - } else { - // Normal response handling (200, etc.) - expect(response.ok()).toBeTruthy(); - const responseBody = await response.json(); - expect(Array.isArray(responseBody)).toBeTruthy(); - const { matchingObject, nhsNumber, matchingObjects } = await findMatchingObject(endpoint, responseBody, apiValidation); - console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); - console.info(`From Response ${JSON.stringify(matchingObject, null, 2)}`); - status = await validateFields(apiValidation, matchingObject, nhsNumber, matchingObjects); - } - } - } catch (error) { - const errorMsg = `Endpoint: ${endpoint}, Status: ${response?.status?.()}, Error: ${error instanceof Error ? error.stack || error.message : error}`; - errorTrace = errorMsg; - if (response?.status?.() === 204) { - console.info(`ℹ️\t Status 204: No data found in the table using endpoint ${endpoint}`); - } - } + case `api/${config.participantDemographicDataService}`: + return request.get(`${config.endpointParticipantDemographicDataService}${currentEndPoint}`); - if (attempt < apiRetry && !status) { - console.info(`🚧 Function processing in progress; will check again using data service ${endpoint} in ${Math.round(waitTime / 1000)} seconds...`); - await delayRetry(); - } + default: + throw new Error(`Unknown endpoint: ${endpoint}`); } - waitTime = Number(config.apiWaitTime); - return { status, errorTrace }; } -export async function fetchApiResponse(endpoint: string, request: any): Promise { - - if (endpoint.includes(COHORT_DISTRIBUTION_SERVICE)) { - return await request.get(`${endpointCohortDistributionDataService}${endpoint.toLowerCase()}`); - } else if (endpoint.includes(PARTICIPANT_MANAGEMENT_SERVICE)) { - return await request.get(`${endpointParticipantManagementDataService}${endpoint.toLowerCase()}`); - } else if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE)) { - return await request.get(`${endpointExceptionManagementDataService}${endpoint.toLowerCase()}`); - } else if (endpoint.includes(PARTICIPANT_DEMOGRAPHIC_SERVICE)) { - return await request.get(`${endpointParticipantDemographicDataService}${endpoint.toLowerCase()}`); - } - throw new Error(`Unknown endpoint: ${endpoint}`); -} - -async function findMatchingObject(endpoint: string, responseBody: any[], apiValidation: any) { +export async function findMatchingObject(endpoint: string, responseBody: any[], apiValidation: any) { let nhsNumber: any; let matchingObjects: any[] = []; let matchingObject: any; - let nhsNumberKey; - if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) || endpoint.includes(PARTICIPANT_DEMOGRAPHIC_SERVICE)) { - nhsNumberKey = NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC; + if (endpoint.includes(config.exceptionManagementService) || endpoint.includes(config.participantDemographicDataService)) { + nhsNumberKey = config.nhsNumberKeyExceptionDemographic; } else if (endpoint.includes("participantmanagementdataservice") || endpoint.includes("CohortDistributionDataService")) { nhsNumberKey = "NHSNumber"; } else { - nhsNumberKey = NHS_NUMBER_KEY; + nhsNumberKey = config.nhsNumberKey; } nhsNumber = apiValidation.validations[nhsNumberKey]; @@ -122,26 +51,26 @@ async function findMatchingObject(endpoint: string, responseBody: any[], apiVali } matchingObjects = responseBody.filter((item: Record) => - item[nhsNumberKey] == nhsNumber || - item.NhsNumber == nhsNumber || - item.NHSNumber == nhsNumber + item[nhsNumberKey] === nhsNumber || + item.NhsNumber === nhsNumber || + item.NHSNumber === nhsNumber ); matchingObject = matchingObjects[matchingObjects.length - 1]; - if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) && + if (endpoint.includes(config.exceptionManagementService) && (apiValidation.validations.RuleId !== undefined || apiValidation.validations.RuleDescription)) { - const ruleIdToFind = apiValidation.validations.RuleId; - const ruleDescToFind = apiValidation.validations.RuleDescription; + let ruleIdToFind = apiValidation.validations.RuleId; + let ruleDescToFind = apiValidation.validations.RuleDescription; - const betterMatches = matchingObjects.filter(record => + let betterMatches = matchingObjects.filter(record => (ruleIdToFind === undefined || record.RuleId === ruleIdToFind) && (ruleDescToFind === undefined || record.RuleDescription === ruleDescToFind) ); if (betterMatches.length > 0) { matchingObject = betterMatches[0]; - console.log(`Found better matching record with NHS Number ${nhsNumber} and RuleId ${ruleIdToFind || 'any'}`); + console.info(`Found better matching record with NHS Number ${nhsNumber} and RuleId ${ruleIdToFind || 'any'}`); } } @@ -149,241 +78,94 @@ async function findMatchingObject(endpoint: string, responseBody: any[], apiVali } -async function validateFields(apiValidation: any, matchingObject: any, nhsNumber: any, matchingObjects: any): Promise { - const fieldsToValidate = Object.entries(apiValidation.validations).filter(([key]) => key !== IGNORE_VALIDATION_KEY); +export async function validateFields(apiValidation: any, matchingObject: any, nhsNumber: any, matchingObjects: any): Promise { + const fieldsToValidate = Object.entries(apiValidation.validations).filter(([key]) => key !== config.ignoreValidationKey); for (const [fieldName, expectedValue] of fieldsToValidate) { - if (fieldName === "expectedCount") { - console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); - - let actualCount = 0; - if (matchingObjects && Array.isArray(matchingObjects)) { - actualCount = matchingObjects.length; - } else if (matchingObjects === null || matchingObjects === undefined) { - actualCount = 0; - console.warn(`⚠️ matchingObjects is ${matchingObjects === null ? 'null' : 'undefined'} for NHS Number ${nhsNumber}`); - } else { - actualCount = 1; - console.warn(`⚠️ matchingObjects is not an array for NHS Number ${nhsNumber}, treating as single object`); - } - - console.info(`📊 Actual count: ${actualCount}, Expected count: ${expectedValue} for NHS Number ${nhsNumber}`); - - const expectedCount = Number(expectedValue); - - if (isNaN(expectedCount)) { - throw new Error(`❌ expectedCount value '${expectedValue}' is not a valid number for NHS Number ${nhsNumber}`); - } - - // Perform the assertion - try { - expect(actualCount).toBe(expectedCount); - console.info(`✅ Count check completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); - } catch (error) { - console.error(`❌ Count check failed for NHS Number ${nhsNumber}: Expected ${expectedCount}, but got ${actualCount}`); - throw error; - } - } - - // Handle NHS Number validation specially for 204 responses - else if ((fieldName === "NHSNumber" || fieldName === "NhsNumber") && !matchingObject) { - console.info(`🚧 Validating NHS Number field ${fieldName} for 204 response`); - - // For 204 responses, validate that we searched for the correct NHS number - const expectedNhsNumber = Number(expectedValue); - const actualNhsNumber = Number(nhsNumber); - - try { - expect(actualNhsNumber).toBe(expectedNhsNumber); - console.info(`✅ NHS Number validation completed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); - } catch (error) { - console.error(`❌ NHS Number validation failed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); - throw error; - } - } - - else if (fieldName === 'RecordInsertDateTime' || fieldName === 'RecordUpdateDateTime') { - console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); - - if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { - throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); - } - - if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { - console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); - continue; - } - - expect(matchingObject).toHaveProperty(fieldName); - const actualValue = matchingObject[fieldName]; - - if (typeof expectedValue === 'string' && expectedValue.startsWith('PATTERN:')) { - const pattern = expectedValue.substring('PATTERN:'.length); - console.info(`Validating timestamp against pattern: ${pattern}`); - - const formatMatch = validateTimestampFormat(actualValue, pattern); - - if (formatMatch) { - console.info(`✅ Timestamp matches pattern for ${fieldName}`); - } else { - console.error(`❌ Timestamp doesn't match pattern for ${fieldName}`); - expect(formatMatch).toBe(true); - } - } else { - if (expectedValue === actualValue) { - console.info(`✅ Timestamp exact match for ${fieldName}`); - } else { - try { - const expectedDate = new Date(expectedValue as string); - const actualDate = new Date(actualValue); - - const expectedTimeWithoutMs = new Date(expectedDate); - expectedTimeWithoutMs.setMilliseconds(0); - const actualTimeWithoutMs = new Date(actualDate); - actualTimeWithoutMs.setMilliseconds(0); - - if (expectedTimeWithoutMs.getTime() === actualTimeWithoutMs.getTime()) { - console.info(`✅ Timestamp matches (ignoring milliseconds) for ${fieldName}`); - } else { - const timeDiff = Math.abs(expectedDate.getTime() - actualDate.getTime()); - const oneMinute = 60 * 1000; - - if (timeDiff <= oneMinute) { - console.info(`✅ Timestamp within acceptable range (±1 minute) for ${fieldName}`); - } else { - expect(actualValue).toBe(expectedValue); - } - } - } catch (e) { - console.error(`Error validating timestamp: ${e}`); - expect(actualValue).toBe(expectedValue); - } + switch(fieldName.toLowerCase()){ + case "expectedcount": + assertOnCounts(matchingObject, nhsNumber, matchingObjects, fieldName, expectedValue); + break; + case "nhsnumber": + if(!matchingObject) { + assertOnNhsNumber(expectedValue, nhsNumber); } - } - - console.info(`✅ Validation completed for timestamp field ${fieldName} for NHS Number ${nhsNumber}`); - } - - // ✅ Custom dynamic rule description handling - else if (fieldName === 'RuleDescriptionDynamic') { - const actualValue = matchingObject['RuleDescription']; - console.info(`Actual RuleDescription: "${actualValue}"`); - - // Regex based on message requirement - const dynamicPattern = /Unable to add to cohort distribution\. As participant \d+ has triggered a validation exception/; - - try { - expect(actualValue).toMatch(dynamicPattern); - console.info(`✅ Dynamic message validation passed for NHS Number ${nhsNumber}`); - } catch (error) { - console.info(`❌ Dynamic message validation failed!`); - throw error; - } - } - - else { - console.info(`🚧 Validating field ${fieldName} with expected value ${expectedValue} for NHS Number ${nhsNumber}`); - - if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { - throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); - } - - if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { - console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); - continue; - } - - expect(matchingObject).toHaveProperty(fieldName); - expect(matchingObject[fieldName]).toBe(expectedValue); - console.info(`✅ Validation completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); + break; + case "recordinsertdatetime": + assertOnRecordDateTimes(fieldName, expectedValue, nhsNumber, matchingObject); + break; + case "recordUpdatedatetime": + assertOnRecordDateTimes(fieldName, expectedValue, nhsNumber, matchingObject); + break; + case "ruledescriptiondynamic": + MatchOnRuleDescriptionDynamic(matchingObject, nhsNumber); + break; + default: + MatchDynamicType(matchingObject, nhsNumber, expectedValue, fieldName); + break; } } return true; } -// Helper function to validate timestamp format -function validateTimestampFormat(timestamp: string, pattern: string): boolean { - if (!timestamp) return false; - - console.info(`Actual timestamp: ${timestamp}`); - - - if (pattern === 'yyyy-MM-ddTHH:mm:ss' || pattern === 'yyyy-MM-ddTHH:mm:ss.SSS') { - - return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(timestamp); - } - else if (pattern === 'yyyy-MM-dd') { - - return /^\d{4}-\d{2}-\d{2}$/.test(timestamp); - } - else { - - return !isNaN(new Date(timestamp).getTime()); - } -} - -async function delayRetry() { - await new Promise((resolve) => setTimeout(resolve, waitTime)); - waitTime += 5000; -} export async function checkMappingsByIndex( original: Array<{ requestId: string; nhsNumber: string }>, shifted: Array<{ requestId: string; nhsNumber: string }> ): Promise { - const uniqueOriginalRequestIds: string[] = []; - original.forEach(item => { - if (!uniqueOriginalRequestIds.includes(item.requestId)) { - uniqueOriginalRequestIds.push(item.requestId); - } - }); - const uniqueShiftedRequestIds: string[] = []; - shifted.forEach(item => { - if (!uniqueShiftedRequestIds.includes(item.requestId)) { - uniqueShiftedRequestIds.push(item.requestId); - } - }); - - let allMatched = true; - for (let i = 0; i < uniqueShiftedRequestIds.length; i++) { - const shiftedRequestId = uniqueShiftedRequestIds[i]; - const originalNextRequestId = uniqueOriginalRequestIds[i + 1]; - - if (!originalNextRequestId) { - console.info(`No next request ID for index ${i}`); - continue; - } - const shiftedNhsNumbers = shifted - .filter(item => item.requestId === shiftedRequestId) - .map(item => item.nhsNumber) - .sort((a, b) => a.localeCompare(b)); + const uniqueOriginalRequestIds: string[] = []; + original.forEach(item => { + if (!uniqueOriginalRequestIds.includes(item.requestId)) { + uniqueOriginalRequestIds.push(item.requestId); + } + }); + const uniqueShiftedRequestIds: string[] = []; + shifted.forEach(item => { + if (!uniqueShiftedRequestIds.includes(item.requestId)) { + uniqueShiftedRequestIds.push(item.requestId); + } + }); - const originalNextNhsNumbers = original - .filter(item => item.requestId === originalNextRequestId) - .map(item => item.nhsNumber) - .sort((a, b) => a.localeCompare(b)); + let allMatched = true; + for (let i = 0; i < uniqueShiftedRequestIds.length; i++) { + const shiftedRequestId = uniqueShiftedRequestIds[i]; + const originalNextRequestId = uniqueOriginalRequestIds[i + 1]; - if (shiftedNhsNumbers.length !== originalNextNhsNumbers.length) { - console.info(`Length mismatch for index ${i}`); - console.info(`Shifted [${shiftedRequestId}]: ${shiftedNhsNumbers.length} items`); - console.info(`Original Next [${originalNextRequestId}]: ${originalNextNhsNumbers.length} items`); - allMatched = false; - continue; - } + if (!originalNextRequestId) { + console.info(`No next request ID for index ${i}`); + continue; + } + const shiftedNhsNumbers = shifted + .filter(item => item.requestId === shiftedRequestId) + .map(item => item.nhsNumber) + .sort((a, b) => a.localeCompare(b)); + + const originalNextNhsNumbers = original + .filter(item => item.requestId === originalNextRequestId) + .map(item => item.nhsNumber) + .sort((a, b) => a.localeCompare(b)); + + if (shiftedNhsNumbers.length !== originalNextNhsNumbers.length) { + console.info(`Length mismatch for index ${i}`); + console.info(`Shifted [${shiftedRequestId}]: ${shiftedNhsNumbers.length} items`); + console.info(`Original Next [${originalNextRequestId}]: ${originalNextNhsNumbers.length} items`); + allMatched = false; + continue; + } - const allNhsNumbersMatch = shiftedNhsNumbers.every( - (nhsNumber, index) => nhsNumber === originalNextNhsNumbers[index] - ); + const allNhsNumbersMatch = shiftedNhsNumbers.every( + (nhsNumber, index) => nhsNumber === originalNextNhsNumbers[index] + ); - if (!allNhsNumbersMatch) { - console.error(`❌ NHS numbers don't match for index ${i}`); - console.warn(`Shifted [${shiftedRequestId}]: ${shiftedNhsNumbers}`); - console.warn(`Original Next [${originalNextRequestId}]: ${originalNextNhsNumbers}`); - allMatched = false; - } else { - console.info(`✅ NHS numbers match for index ${i} (${shiftedRequestId} -> ${originalNextRequestId})`); - } + if (!allNhsNumbersMatch) { + console.error(`❌ NHS numbers don't match for index ${i}`); + console.warn(`Shifted [${shiftedRequestId}]: ${shiftedNhsNumbers}`); + console.warn(`Original Next [${originalNextRequestId}]: ${originalNextNhsNumbers}`); + allMatched = false; + } else { + console.info(`✅ NHS numbers match for index ${i} (${shiftedRequestId} -> ${originalNextRequestId})`); + } } - return allMatched; } + diff --git a/tests/playwright-tests/src/api/core/assertOnTypes.ts b/tests/playwright-tests/src/api/core/assertOnTypes.ts new file mode 100644 index 000000000..a1363295f --- /dev/null +++ b/tests/playwright-tests/src/api/core/assertOnTypes.ts @@ -0,0 +1,152 @@ +import { expect } from "@playwright/test"; + +export function assertOnCounts(matchingObject: any, nhsNumber: any, matchingObjects: any, fieldName: string, expectedValue: unknown) { + let actualCount = 0; + console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); + + if (matchingObjects && Array.isArray(matchingObjects)) { + actualCount = matchingObjects.length; + } else if (matchingObjects === null || matchingObjects === undefined) { + actualCount = 0; + console.warn(`⚠️ matchingObjects is ${matchingObjects === null ? 'null' : 'undefined'} for NHS Number ${nhsNumber}`); + } else { + actualCount = 1; + console.warn(`⚠️ matchingObjects is not an array for NHS Number ${nhsNumber}, treating as single object`); + } + + console.info(`📊 Actual count: ${actualCount}, Expected count: ${expectedValue} for NHS Number ${nhsNumber}`); + const expectedCount = Number(expectedValue); + + if (isNaN(expectedCount)) { + throw new Error(`❌ expectedCount value '${expectedValue}' is not a valid number for NHS Number ${nhsNumber}`); + } + expect(actualCount).toBe(expectedCount); + console.info(`✅ Count check completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`) +} + + +export function assertOnNhsNumber(expectedValue: unknown , nhsNumber: string) +{ + // For 204 responses, validate that we searched for the correct NHS number + const expectedNhsNumber = Number(expectedValue); + const actualNhsNumber = Number(nhsNumber); + + expect(actualNhsNumber).toBe(expectedNhsNumber); + console.info(`✅ NHS Number validation completed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); +} + +export function assertOnRecordDateTimes(fieldName: string, expectedValue: unknown, nhsNumber: string, matchingObject: any ) { + console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); + + if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { + throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); + } + + if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { + console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); + } + + expect(matchingObject).toHaveProperty(fieldName); + const actualValue = matchingObject[fieldName]; + + if (typeof expectedValue === 'string' && expectedValue.startsWith('PATTERN:')) { + const pattern = expectedValue.substring('PATTERN:'.length); + console.info(`Validating timestamp against pattern: ${pattern}`); + + const formatMatch = validateTimestampFormat(actualValue, pattern); + + if (formatMatch) { + console.info(`✅ Timestamp matches pattern for ${fieldName}`); + } else { + console.error(`❌ Timestamp doesn't match pattern for ${fieldName}`); + expect(formatMatch).toBe(true); + } + + }else { + if (expectedValue === actualValue) { + console.info(`✅ Timestamp exact match for ${fieldName}`); + } else { + try { + const expectedDate = new Date(expectedValue as string); + const actualDate = new Date(actualValue); + + const expectedTimeWithoutMs = new Date(expectedDate); + expectedTimeWithoutMs.setMilliseconds(0); + const actualTimeWithoutMs = new Date(actualDate); + actualTimeWithoutMs.setMilliseconds(0); + + if (expectedTimeWithoutMs.getTime() === actualTimeWithoutMs.getTime()) { + console.info(`✅ Timestamp matches (ignoring milliseconds) for ${fieldName}`); + } else { + const timeDiff = Math.abs(expectedDate.getTime() - actualDate.getTime()); + const oneMinute = 60 * 1000; + + if (timeDiff <= oneMinute) { + console.info(`✅ Timestamp within acceptable range (±1 minute) for ${fieldName}`); + } else { + expect(actualValue).toBe(expectedValue); + } + } + } catch (e) { + console.error(`Error validating timestamp: ${e}`); + expect(actualValue).toBe(expectedValue); + } + } + } + console.info(`✅ Validation completed for timestamp field ${fieldName} for NHS Number ${nhsNumber}`); +} + + + + +export function MatchOnRuleDescriptionDynamic(matchingObject: any, nhsNumber: string) { + const actualValue = matchingObject['RuleDescription']; + console.info(`Actual RuleDescription: "${actualValue}"`); + + // Regex based on message requirement + const dynamicPattern = /Unable to add to cohort distribution\. As participant \d+ has triggered a validation exception/; + + try { + expect(actualValue).toMatch(dynamicPattern); + console.info(`✅ Dynamic message validation passed for NHS Number ${nhsNumber}`); + } catch (error) { + console.info(`❌ Dynamic message validation failed!`); + throw error; + } +} + +export function MatchDynamicType(matchingObject: any, nhsNumber: string, expectedValue: any, fieldName: string) { + console.info(`🚧 Validating field ${fieldName} with expected value ${expectedValue} for NHS Number ${nhsNumber}`); + + if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { + throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); + } + + if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { + console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); + } + + expect(matchingObject).toHaveProperty(fieldName); + expect(matchingObject[fieldName]).toBe(expectedValue); + console.info(`✅ Validation completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); +} + + +function validateTimestampFormat(timestamp: string, pattern: string): boolean { + if (!timestamp) { + return false; + } + console.info(`Actual timestamp: ${timestamp}`); + + if (pattern === 'yyyy-MM-ddTHH:mm:ss' || pattern === 'yyyy-MM-ddTHH:mm:ss.SSS') { + return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(timestamp); + } + else if (pattern === 'yyyy-MM-dd') { + return /^\d{4}-\d{2}-\d{2}$/.test(timestamp); + } + else { + return !isNaN(new Date(timestamp).getTime()); + } +} + + diff --git a/tests/playwright-tests/src/config/env.ts b/tests/playwright-tests/src/config/env.ts index c3a48d239..f74396e2f 100644 --- a/tests/playwright-tests/src/config/env.ts +++ b/tests/playwright-tests/src/config/env.ts @@ -60,6 +60,9 @@ export const config = { apiTestFilesPath: 'api/testFiles', apiRetry: 8, apiWaitTime: 5000, + maxNumberOfRetries: 10, + timeBetweenRetriesInSeconds: 10, + maxTimeBetweenRequests: 2500, nhsNumberKey: 'NHSNumber', nhsNumberKeyExceptionDemographic: 'NhsNumber', uniqueKeyCohortDistribution: 'CohortDistributionId', diff --git a/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts b/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts index e50adea16..d4a87b886 100644 --- a/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts +++ b/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts @@ -7,6 +7,7 @@ import { APIRequestContext, TestInfo } from '@playwright/test'; import { config } from '../../../config/env'; import { getRecordsFromExceptionService } from '../../../api/dataService/exceptionService'; import { sendHttpGet, sendHttpPOSTCall } from '../../../api/core/sendHTTPRequest'; +import { pollApiForOKResponse } from '../../../api/RetryCore/Retry'; annotation: [{ type: 'Requirement', @@ -38,7 +39,6 @@ test.describe('@regression @e2e @epic4b-block-tests @smoke Tests', async () => { await validateSqlDatabaseFromAPI(request, addValidations); }); - // Call the block participant function await test.step(`Go to PDS and get the participant data for the blocking of a participant that already exists in the database`, async () => { // Call the block participant function @@ -77,17 +77,11 @@ test.describe('@regression @e2e @epic4b-block-tests @smoke Tests', async () => { await test.step(`the participant has been blocked`, async () => { - let blocked = false; - for(let i =0; i<10; i++) { - const resp = await getRecordsFromParticipantManagementService(request); - if (resp?.data?.[0]?.BlockedFlag === 1) { - blocked = true; - break; - } - console.log(`Waiting for participant to be blocked...(${i}/10)`); - await new Promise(res => setTimeout(res, 2000)); - } - expect(blocked).toBe(true); + + var response = await pollApiForOKResponse(() => getRecordsFromParticipantManagementService(request)); + + expect(response.status).toBe(200); + expect(response?.data?.[0]?.BlockedFlag).toBe(1); }); @@ -100,26 +94,20 @@ test.describe('@regression @e2e @epic4b-block-tests @smoke Tests', async () => { await processFileViaStorage(parquetFile); let validationExceptions; - for(let i =0; i<10; i++) + + var responseFromExceptions = await pollApiForOKResponse(() => getRecordsFromExceptionService(request)); + + if(responseFromExceptions.data !== null) { - const responseFromExceptions = await getRecordsFromExceptionService(request); - if(responseFromExceptions.data !== null) - { - validationExceptions = responseFromExceptions.data - break; - } - console.log(`waiting for exception for participant blocked to be added to exception table...({${i}/10)`); - await new Promise(res => setTimeout(res, 2000)); + validationExceptions = responseFromExceptions.data } let getUrl = `${config.endpointParticipantManagementDataService}api/${config.participantManagementService}`; var response = await sendHttpGet(getUrl); - - let cohortDistributionServiceUrl = `${config.endpointCohortDistributionDataService}api/${config.cohortDistributionService}` - var response = await sendHttpGet(cohortDistributionServiceUrl); + var jsonResponse = await response.json(); expect(response.status).toBe(200) diff --git a/tests/playwright-tests/src/tests/steps/steps.ts b/tests/playwright-tests/src/tests/steps/steps.ts index fca08b037..b64bfbbe9 100644 --- a/tests/playwright-tests/src/tests/steps/steps.ts +++ b/tests/playwright-tests/src/tests/steps/steps.ts @@ -4,10 +4,12 @@ import { InputData, ParticipantRecord } from "../../interface/InputData"; import { config } from "../../config/env"; import * as fs from 'fs'; import path from "path"; -import { validateApiResponse } from "../../api/apiHelper"; +//import { validateApiResponse } from "../../api/apiHelper"; + import { cleanDataBaseUsingServices } from "../../api/dataService/dataServiceCleaner"; import { ensureNhsNumbersStartWith999 } from "../fixtures/testDataHelper"; import { receiveParticipantViaServiceNow } from "../../api/distributionService/bsSelectService"; +import { validateApiResponse } from "../../api/RetryCore/Retry"; export async function cleanupDatabaseFromAPI(request: APIRequestContext, numbers: string[]) { From 792df7ab1960871c9000d39bdde499dd4d33006f Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Fri, 29 Aug 2025 13:05:15 +0100 Subject: [PATCH 02/13] fix: removing unwanted changes --- tests/playwright-tests/src/api/core/assertOnTypes.ts | 2 +- tests/playwright-tests/src/tests/steps/steps.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/playwright-tests/src/api/core/assertOnTypes.ts b/tests/playwright-tests/src/api/core/assertOnTypes.ts index a1363295f..67550b3ce 100644 --- a/tests/playwright-tests/src/api/core/assertOnTypes.ts +++ b/tests/playwright-tests/src/api/core/assertOnTypes.ts @@ -36,7 +36,7 @@ export function assertOnNhsNumber(expectedValue: unknown , nhsNumber: string) } export function assertOnRecordDateTimes(fieldName: string, expectedValue: unknown, nhsNumber: string, matchingObject: any ) { - console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); + console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); diff --git a/tests/playwright-tests/src/tests/steps/steps.ts b/tests/playwright-tests/src/tests/steps/steps.ts index b64bfbbe9..6e5644801 100644 --- a/tests/playwright-tests/src/tests/steps/steps.ts +++ b/tests/playwright-tests/src/tests/steps/steps.ts @@ -4,7 +4,6 @@ import { InputData, ParticipantRecord } from "../../interface/InputData"; import { config } from "../../config/env"; import * as fs from 'fs'; import path from "path"; -//import { validateApiResponse } from "../../api/apiHelper"; import { cleanDataBaseUsingServices } from "../../api/dataService/dataServiceCleaner"; import { ensureNhsNumbersStartWith999 } from "../fixtures/testDataHelper"; From 27dd36abcbbad84322b19a46ae8cfe50d41e70e0 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Mon, 1 Sep 2025 11:18:10 +0100 Subject: [PATCH 03/13] chore: addressing comments on PR --- tests/playwright-tests/src/api/RetryCore/Retry.ts | 8 ++++---- tests/playwright-tests/src/api/apiHelper.ts | 10 +++++----- tests/playwright-tests/src/config/env.ts | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/playwright-tests/src/api/RetryCore/Retry.ts b/tests/playwright-tests/src/api/RetryCore/Retry.ts index efbbef463..691840625 100644 --- a/tests/playwright-tests/src/api/RetryCore/Retry.ts +++ b/tests/playwright-tests/src/api/RetryCore/Retry.ts @@ -80,7 +80,7 @@ async function pollAPI(endpoint: string, apiValidation: any, request: any): Prom let maxTimeBetweenRequests = config.maxTimeBetweenRequests; console.info(`now trying request for ${maxNumberOfRetries} retries`); - while (i < maxNumberOfRetries) { + while (i < Number(maxNumberOfRetries)) { try { apiResponse = await fetchApiResponse(endpoint, request); if (apiResponse.status() == 200) { @@ -93,7 +93,7 @@ async function pollAPI(endpoint: string, apiValidation: any, request: any): Prom } i++; - console.info(`http response completed ${i}/${maxNumberOfRetries} of number of reties`); + console.info(`http response completed ${i}/${maxNumberOfRetries} of number of retries`); await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); } @@ -112,7 +112,7 @@ export async function pollApiForOKResponse(httpRequest: () => Promise Promise setTimeout(res, maxTimeBetweenRequests)); } diff --git a/tests/playwright-tests/src/api/apiHelper.ts b/tests/playwright-tests/src/api/apiHelper.ts index b5b56f034..2dbaa723c 100644 --- a/tests/playwright-tests/src/api/apiHelper.ts +++ b/tests/playwright-tests/src/api/apiHelper.ts @@ -51,17 +51,17 @@ export async function findMatchingObject(endpoint: string, responseBody: any[], } matchingObjects = responseBody.filter((item: Record) => - item[nhsNumberKey] === nhsNumber || - item.NhsNumber === nhsNumber || - item.NHSNumber === nhsNumber + item[nhsNumberKey] == nhsNumber || + item.NhsNumber == nhsNumber || + item.NHSNumber == nhsNumber ); matchingObject = matchingObjects[matchingObjects.length - 1]; if (endpoint.includes(config.exceptionManagementService) && (apiValidation.validations.RuleId !== undefined || apiValidation.validations.RuleDescription)) { - let ruleIdToFind = apiValidation.validations.RuleId; - let ruleDescToFind = apiValidation.validations.RuleDescription; + const ruleIdToFind = apiValidation.validations.RuleId; + const ruleDescToFind = apiValidation.validations.RuleDescription; let betterMatches = matchingObjects.filter(record => (ruleIdToFind === undefined || record.RuleId === ruleIdToFind) && diff --git a/tests/playwright-tests/src/config/env.ts b/tests/playwright-tests/src/config/env.ts index f74396e2f..8c4d435de 100644 --- a/tests/playwright-tests/src/config/env.ts +++ b/tests/playwright-tests/src/config/env.ts @@ -60,8 +60,8 @@ export const config = { apiTestFilesPath: 'api/testFiles', apiRetry: 8, apiWaitTime: 5000, - maxNumberOfRetries: 10, - timeBetweenRetriesInSeconds: 10, + maxNumberOfRetries: process.env.MAX_NUMBER_OF_RETRIES, + timeBetweenRetriesInSeconds: process.env.TIME_BETWEEN_RETRIES_IN_SECONDS, maxTimeBetweenRequests: 2500, nhsNumberKey: 'NHSNumber', nhsNumberKeyExceptionDemographic: 'NhsNumber', From d292ea8f941a13794444e2e7343d08094dab3717 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Mon, 1 Sep 2025 11:28:35 +0100 Subject: [PATCH 04/13] chore: adding new config items to env example --- application/CohortManager/.env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/CohortManager/.env.example b/application/CohortManager/.env.example index bcb23ac58..d049031d5 100644 --- a/application/CohortManager/.env.example +++ b/application/CohortManager/.env.example @@ -35,3 +35,6 @@ ENDPOINT_BS_SELECT_UPDATE_BLOCK_FLAG="" # "http://localhost:7026/" SERVICENOW_CLIENT_ID= SERVICENOW_CLIENT_SECRET= SERVICENOW_REFRESH_TOKEN= + +MAX_NUMBER_OF_RETRIES="" +TIME_BETWEEN_RETRIES_IN_SECONDS="" \ No newline at end of file From 856494eb9d38461a3c6c737b8a107d2d995d6d79 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Mon, 1 Sep 2025 14:05:58 +0100 Subject: [PATCH 05/13] fix: trying to make sure retry is actually working --- .../src/api/RetryCore/Retry.ts | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/tests/playwright-tests/src/api/RetryCore/Retry.ts b/tests/playwright-tests/src/api/RetryCore/Retry.ts index 691840625..78ee1f4b1 100644 --- a/tests/playwright-tests/src/api/RetryCore/Retry.ts +++ b/tests/playwright-tests/src/api/RetryCore/Retry.ts @@ -7,28 +7,14 @@ const NHS_NUMBER_KEY = config.nhsNumberKey; const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { - let status = false; let endpoint = ""; let errorTrace: any = undefined; - + let status = false; try { for (const apiValidation of validationJson) { endpoint = apiValidation.validations.apiEndpoint; - var response = await pollAPI(endpoint, apiValidation, request); - console.info(response); - switch(response.status()) { - case 204: - console.info("now handling no content response"); - const expectedCount = apiValidation.validations.expectedCount; - status = await HandleNoContentResponse(expectedCount, apiValidation, endpoint); - break; - case 200: - console.info("now handling OK response"); - status = await handleOKResponse(apiValidation, endpoint, response); - default: - console.error("there was an error when handling response from "); - break; - } + var res = await pollAPI(endpoint, apiValidation, request); + return res; } } @@ -37,7 +23,7 @@ export async function validateApiResponse(validationJson: any, request: any): Pr errorTrace = errorMsg; console.error(errorMsg); } - return { status, errorTrace }; + return {status, errorTrace }; } async function handleOKResponse(apiValidation: any, endpoint: string, response: any ) : Promise{ @@ -66,42 +52,52 @@ async function HandleNoContentResponse(expectedCount: number, apiValidation: any console.info(`From Response: null (204 No Content - 0 records as expected)`); return await validateFields(apiValidation, null, nhsNumber, []); } else { - // 204 is unexpected, throw error to trigger retry - throw new Error(`Status 204: No data found in the table using endpoint ${endpoint}`); + // 204 is unexpected, log error and return false to trigger retry + console.warn(`Status 204: No data found in the table using endpoint ${endpoint}`); + return false; } } -async function pollAPI(endpoint: string, apiValidation: any, request: any): Promise { +async function pollAPI(endpoint: string, apiValidation: any, request: any): Promise<{ apiResponse: APIResponse, status: boolean}> { let apiResponse: APIResponse | null = null; let i = 0; let maxNumberOfRetries = config.maxNumberOfRetries; let maxTimeBetweenRequests = config.maxTimeBetweenRequests; - + let status = false; console.info(`now trying request for ${maxNumberOfRetries} retries`); while (i < Number(maxNumberOfRetries)) { - try { - apiResponse = await fetchApiResponse(endpoint, request); - if (apiResponse.status() == 200) { - console.info("200 response found") - break; + apiResponse = await fetchApiResponse(endpoint, request); + switch(apiResponse.status()) { + case 204: + console.info("now handling no content response"); + const expectedCount = apiValidation.validations.expectedCount; + status = await HandleNoContentResponse(expectedCount, apiValidation, endpoint); + break; + case 200: + console.info("now handling OK response"); + status = await handleOKResponse(apiValidation, endpoint, apiResponse); + break; + default: + console.error("there was an error when handling response from "); + break; } - } - catch(exception) { - console.error("Error reading request body:", exception); + if(status) { + break; } i++; console.info(`http response completed ${i}/${maxNumberOfRetries} of number of retries`); await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); - } + } + if (!apiResponse) { throw new Error("apiResponse was never assigned"); } - return apiResponse; + return {apiResponse, status}; } From 2b1c594d7194e978066531eafad1e49cf0d82343 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Mon, 1 Sep 2025 14:16:34 +0100 Subject: [PATCH 06/13] fix: trying to make sure retry is actually working --- tests/playwright-tests/src/api/RetryCore/Retry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/playwright-tests/src/api/RetryCore/Retry.ts b/tests/playwright-tests/src/api/RetryCore/Retry.ts index 78ee1f4b1..17d881ca4 100644 --- a/tests/playwright-tests/src/api/RetryCore/Retry.ts +++ b/tests/playwright-tests/src/api/RetryCore/Retry.ts @@ -27,11 +27,11 @@ export async function validateApiResponse(validationJson: any, request: any): Pr } async function handleOKResponse(apiValidation: any, endpoint: string, response: any ) : Promise{ + expect(response.ok()).toBeTruthy(); const responseBody = await response.json(); expect(Array.isArray(responseBody)).toBeTruthy(); - const { matchingObject, nhsNumber, matchingObjects } = await findMatchingObject(endpoint, responseBody, apiValidation); - + console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); console.info(`From Response ${JSON.stringify(matchingObject, null, 2)}`); return await validateFields(apiValidation, matchingObject, nhsNumber, matchingObjects); From 9790d59d3904c50c1f46a756e54e62bc7866042e Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Thu, 4 Sep 2025 11:24:01 +0100 Subject: [PATCH 07/13] fix: making sure tests are not flaky --- ...c4b-7667-block-paricipant-tessuite.spec.ts | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts b/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts index 0f8bda137..c9cc8fb51 100644 --- a/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts +++ b/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts @@ -8,6 +8,9 @@ import { config } from '../../../config/env'; import { getRecordsFromExceptionService } from '../../../api/dataService/exceptionService'; import { sendHttpGet, sendHttpPOSTCall } from '../../../api/core/sendHTTPRequest'; import { pollApiForOKResponse } from '../../../api/RetryCore/Retry'; +import { ApiResponse } from '../../../api/core/types'; +import { fail } from 'assert'; +import { assert } from 'console'; annotation: [{ type: 'Requirement', @@ -78,10 +81,25 @@ test.describe('@regression @e2e @epic4b-block-tests @smoke Tests', async () => { await test.step(`the participant has been blocked`, async () => { - var response = await pollApiForOKResponse(() => getRecordsFromParticipantManagementService(request)); + let response: ApiResponse | null = null; + let RetryLimit = 2 + let retryCount = 0 + + while(retryCount < RetryLimit) + { + response = await pollApiForOKResponse(() => getRecordsFromParticipantManagementService(request)); + if (response?.data?.[0]?.BlockedFlag === 1) { + break; + } + } - expect(response.status).toBe(200); - expect(response?.data?.[0]?.BlockedFlag).toBe(1); + if(response !== null) { + expect(response.status).toBe(200); + expect(response?.data?.[0]?.BlockedFlag).toBe(1); + } + else { + fail; + } }); @@ -94,16 +112,18 @@ test.describe('@regression @e2e @epic4b-block-tests @smoke Tests', async () => { await processFileViaStorage(parquetFile); let validationExceptions; - for(let i =0; i<10; i++) + let RetryLimit = 2 + let retryCount = 0 + + while(retryCount < RetryLimit) { - const responseFromExceptions = await getRecordsFromExceptionService(request); + var responseFromExceptions = await pollApiForOKResponse(() => getRecordsFromExceptionService(request)); if(responseFromExceptions.data.length >= 3) { validationExceptions = responseFromExceptions.data break; } - console.log(`waiting for exception for participant blocked to be added to exception table...({${i}/10)`); - await new Promise(res => setTimeout(res, 2000)); + console.log(`waiting for exception for participant blocked to be added to exception table...`); } let getUrl = `${config.endpointParticipantManagementDataService}api/${config.participantManagementService}`; From 0522a0d3e8d27c747261b94b6f7b35057542b9e6 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Fri, 5 Sep 2025 10:53:46 +0100 Subject: [PATCH 08/13] fix: trying to make sure that both all tests run the same. (this commit is highly subject to change) --- .../src/api/RetryCore/Retry.ts | 443 +++++++++++++++--- .../src/api/RetryCore/retryNew.ts | 140 ++++++ .../src/api/RetryCore/retryOld.ts | 90 ++++ tests/playwright-tests/src/api/apiHelper.ts | 193 +++++++- .../src/api/core/assertOnTypes.ts | 109 +++-- ...c4b-7667-block-paricipant-tessuite.spec.ts | 16 +- .../epic4c-10011-10012-testsuite.spec.ts | 20 - 7 files changed, 859 insertions(+), 152 deletions(-) create mode 100644 tests/playwright-tests/src/api/RetryCore/retryNew.ts create mode 100644 tests/playwright-tests/src/api/RetryCore/retryOld.ts diff --git a/tests/playwright-tests/src/api/RetryCore/Retry.ts b/tests/playwright-tests/src/api/RetryCore/Retry.ts index 17d881ca4..ccbfda9fe 100644 --- a/tests/playwright-tests/src/api/RetryCore/Retry.ts +++ b/tests/playwright-tests/src/api/RetryCore/Retry.ts @@ -1,75 +1,60 @@ import { APIResponse, expect } from "@playwright/test"; import { config } from "../../config/env"; -import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; import { ApiResponse } from "../core/types"; + + +const apiRetry = Number(config.apiRetry); +const initialWaitTime = Number(config.apiWaitTime) || 2000; +const endpointCohortDistributionDataService = config.endpointCohortDistributionDataService; +const endpointParticipantManagementDataService = config.endpointParticipantManagementDataService; +const endpointExceptionManagementDataService = config.endpointExceptionManagementDataService; +const endpointParticipantDemographicDataService = config.endpointParticipantDemographicDataService; + +const COHORT_DISTRIBUTION_SERVICE = config.cohortDistributionService; +const PARTICIPANT_MANAGEMENT_SERVICE = config.participantManagementService; +const EXCEPTION_MANAGEMENT_SERVICE = config.exceptionManagementService; +const PARTICIPANT_DEMOGRAPHIC_SERVICE = config.participantDemographicDataService; const NHS_NUMBER_KEY = config.nhsNumberKey; const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; +const IGNORE_VALIDATION_KEY = config.ignoreValidationKey; + +let waitTime = initialWaitTime; +let response: APIResponse; export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { let endpoint = ""; let errorTrace: any = undefined; let status = false; - try { - for (const apiValidation of validationJson) { - endpoint = apiValidation.validations.apiEndpoint; - var res = await pollAPI(endpoint, apiValidation, request); - return res; - - } - } - catch (error) { - const errorMsg = `Endpoint: ${endpoint}, Error: ${error instanceof Error ? error.stack || error.message : error}`; - errorTrace = errorMsg; - console.error(errorMsg); - } - return {status, errorTrace }; -} -async function handleOKResponse(apiValidation: any, endpoint: string, response: any ) : Promise{ - expect(response.ok()).toBeTruthy(); - const responseBody = await response.json(); - expect(Array.isArray(responseBody)).toBeTruthy(); - const { matchingObject, nhsNumber, matchingObjects } = await findMatchingObject(endpoint, responseBody, apiValidation); - - console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); - console.info(`From Response ${JSON.stringify(matchingObject, null, 2)}`); - return await validateFields(apiValidation, matchingObject, nhsNumber, matchingObjects); -} - - -async function HandleNoContentResponse(expectedCount: number, apiValidation: any, endpoint: string): Promise { - if (expectedCount !== undefined && Number(expectedCount) === 0) { - console.info(`✅ Status 204: Expected 0 records for endpoint ${endpoint}`); + let resultFromPolling: { apiResponse: APIResponse, status: boolean, errorTrace: any} | null = null; - // Get NHS number for validation - const nhsNumber = apiValidation.validations.NHSNumber || - apiValidation.validations.NhsNumber || - apiValidation.validations[NHS_NUMBER_KEY] || - apiValidation.validations[NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC]; + for (const apiValidation of validationJson) { + endpoint = apiValidation.validations.apiEndpoint; + resultFromPolling = await pollAPI(endpoint, apiValidation, request); + } - console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); - console.info(`From Response: null (204 No Content - 0 records as expected)`); - return await validateFields(apiValidation, null, nhsNumber, []); - } else { - // 204 is unexpected, log error and return false to trigger retry - console.warn(`Status 204: No data found in the table using endpoint ${endpoint}`); - return false; + if(resultFromPolling) { + status = resultFromPolling.status + errorTrace = resultFromPolling.errorTrace } + return {status, errorTrace }; } -async function pollAPI(endpoint: string, apiValidation: any, request: any): Promise<{ apiResponse: APIResponse, status: boolean}> { +async function pollAPI(endpoint: string, apiValidation: any, request: any): Promise<{ apiResponse: APIResponse, status: boolean, errorTrace: any}> { let apiResponse: APIResponse | null = null; let i = 0; + let errorTrace: any = undefined; let maxNumberOfRetries = config.maxNumberOfRetries; let maxTimeBetweenRequests = config.maxTimeBetweenRequests; let status = false; console.info(`now trying request for ${maxNumberOfRetries} retries`); while (i < Number(maxNumberOfRetries)) { - apiResponse = await fetchApiResponse(endpoint, request); - switch(apiResponse.status()) { + try{ + apiResponse = await fetchApiResponse(endpoint, request); + switch(apiResponse.status()) { case 204: console.info("now handling no content response"); const expectedCount = apiValidation.validations.expectedCount; @@ -83,24 +68,372 @@ async function pollAPI(endpoint: string, apiValidation: any, request: any): Prom console.error("there was an error when handling response from "); break; } - if(status) { - break; - } - i++; + console.log("api status code is: ", apiResponse.status()); + if(status) { + break; + } + i++; - console.info(`http response completed ${i}/${maxNumberOfRetries} of number of retries`); - await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); + console.info(`http response completed ${i}/${maxNumberOfRetries} of number of retries`); + await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); + + } catch (error) { + const errorMsg = `Endpoint: ${endpoint}, Status: ${apiResponse?.status?.()}, Error: ${error instanceof Error ? error.stack || error.message : error}`; + errorTrace = errorMsg; + if (apiResponse?.status?.() === 204) { + console.info(`ℹ️\t Status 204: No data found in the table using endpoint ${endpoint}`); + } + } } - if (!apiResponse) { - throw new Error("apiResponse was never assigned"); + if (!apiResponse) { + throw new Error("apiResponse was never assigned"); + } + + return {apiResponse, status, errorTrace}; +} + +async function HandleNoContentResponse(expectedCount: number, apiValidation: any, endpoint: string): Promise { + if (expectedCount !== undefined && Number(expectedCount) === 0) { + console.info(`✅ Status 204: Expected 0 records for endpoint ${endpoint}`); + + // Get NHS number for validation + const nhsNumber = apiValidation.validations.NHSNumber || + apiValidation.validations.NhsNumber || + apiValidation.validations[NHS_NUMBER_KEY] || + apiValidation.validations[NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC]; + + console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); + console.info(`From Response: null (204 No Content - 0 records as expected)`); + let status = await validateFields(apiValidation, null, nhsNumber, []); + return status; + } else { + // 204 is unexpected, log error and return false to trigger retry + console.warn(`Status 204: No data found in the table using endpoint ${endpoint}`); + return false; + } +} + +async function handleOKResponse(apiValidation: any, endpoint: string, response: any ) : Promise{ + // Normal response handling (200, etc.) + expect(response.ok()).toBeTruthy(); + const responseBody = await response.json(); + expect(Array.isArray(responseBody)).toBeTruthy(); + const { matchingObject, nhsNumber, matchingObjects } = await findMatchingObject(endpoint, responseBody, apiValidation); + console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); + console.info(`From Response ${JSON.stringify(matchingObject, null, 2)}`); + let status = await validateFields(apiValidation, matchingObject, nhsNumber, matchingObjects); + + return status; +} + +export async function fetchApiResponse(endpoint: string, request: any): Promise { + + if (endpoint.includes(COHORT_DISTRIBUTION_SERVICE)) { + return await request.get(`${endpointCohortDistributionDataService}${endpoint.toLowerCase()}`); + } else if (endpoint.includes(PARTICIPANT_MANAGEMENT_SERVICE)) { + return await request.get(`${endpointParticipantManagementDataService}${endpoint.toLowerCase()}`); + } else if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE)) { + return await request.get(`${endpointExceptionManagementDataService}${endpoint.toLowerCase()}`); + } else if (endpoint.includes(PARTICIPANT_DEMOGRAPHIC_SERVICE)) { + return await request.get(`${endpointParticipantDemographicDataService}${endpoint.toLowerCase()}`); + } + throw new Error(`Unknown endpoint: ${endpoint}`); +} + +async function findMatchingObject(endpoint: string, responseBody: any[], apiValidation: any) { + let nhsNumber: any; + let matchingObjects: any[] = []; + let matchingObject: any; + + + let nhsNumberKey; + if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) || endpoint.includes(PARTICIPANT_DEMOGRAPHIC_SERVICE)) { + nhsNumberKey = NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC; + } else if (endpoint.includes("participantmanagementdataservice") || endpoint.includes("CohortDistributionDataService")) { + nhsNumberKey = "NHSNumber"; + } else { + nhsNumberKey = NHS_NUMBER_KEY; + } + + nhsNumber = apiValidation.validations[nhsNumberKey]; + + if (!nhsNumber) { + if (apiValidation.validations.NhsNumber) { + nhsNumber = apiValidation.validations.NhsNumber; + } else if (apiValidation.validations.NHSNumber) { + nhsNumber = apiValidation.validations.NHSNumber; } + } + + matchingObjects = responseBody.filter((item: Record) => + item[nhsNumberKey] == nhsNumber || + item.NhsNumber == nhsNumber || + item.NHSNumber == nhsNumber + ); + + matchingObject = matchingObjects[matchingObjects.length - 1]; + + if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) && + (apiValidation.validations.RuleId !== undefined || apiValidation.validations.RuleDescription)) { + const ruleIdToFind = apiValidation.validations.RuleId; + const ruleDescToFind = apiValidation.validations.RuleDescription; - return {apiResponse, status}; + const betterMatches = matchingObjects.filter(record => + (ruleIdToFind === undefined || record.RuleId === ruleIdToFind) && + (ruleDescToFind === undefined || record.RuleDescription === ruleDescToFind) + ); + + if (betterMatches.length > 0) { + matchingObject = betterMatches[0]; + console.log(`Found better matching record with NHS Number ${nhsNumber} and RuleId ${ruleIdToFind || 'any'}`); + } + } + + return { matchingObject, nhsNumber, matchingObjects }; } +async function validateFields(apiValidation: any, matchingObject: any, nhsNumber: any, matchingObjects: any): Promise { + const fieldsToValidate = Object.entries(apiValidation.validations).filter(([key]) => key !== IGNORE_VALIDATION_KEY); + + for (const [fieldName, expectedValue] of fieldsToValidate) { + if (fieldName === "expectedCount") { + console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); + + let actualCount = 0; + if (matchingObjects && Array.isArray(matchingObjects)) { + actualCount = matchingObjects.length; + } else if (matchingObjects === null || matchingObjects === undefined) { + actualCount = 0; + console.warn(`⚠️ matchingObjects is ${matchingObjects === null ? 'null' : 'undefined'} for NHS Number ${nhsNumber}`); + } else { + actualCount = 1; + console.warn(`⚠️ matchingObjects is not an array for NHS Number ${nhsNumber}, treating as single object`); + } + + console.info(`📊 Actual count: ${actualCount}, Expected count: ${expectedValue} for NHS Number ${nhsNumber}`); + + const expectedCount = Number(expectedValue); + + if (isNaN(expectedCount)) { + throw new Error(`❌ expectedCount value '${expectedValue}' is not a valid number for NHS Number ${nhsNumber}`); + } + + // Perform the assertion + try { + expect(actualCount).toBe(expectedCount); + console.info(`✅ Count check completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); + } catch (error) { + console.error(`❌ Count check failed for NHS Number ${nhsNumber}: Expected ${expectedCount}, but got ${actualCount}`); + throw error; + } + } + + // Handle NHS Number validation specially for 204 responses + else if ((fieldName === "NHSNumber" || fieldName === "NhsNumber") && !matchingObject) { + console.info(`🚧 Validating NHS Number field ${fieldName} for 204 response`); + + // For 204 responses, validate that we searched for the correct NHS number + const expectedNhsNumber = Number(expectedValue); + const actualNhsNumber = Number(nhsNumber); + + try { + expect(actualNhsNumber).toBe(expectedNhsNumber); + console.info(`✅ NHS Number validation completed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); + } catch (error) { + console.error(`❌ NHS Number validation failed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); + throw error; + } + } + + else if (fieldName === 'RecordInsertDateTime' || fieldName === 'RecordUpdateDateTime') { + console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); + + if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { + throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); + } + + if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { + console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); + continue; + } + + expect(matchingObject).toHaveProperty(fieldName); + const actualValue = matchingObject[fieldName]; + + if (typeof expectedValue === 'string' && expectedValue.startsWith('PATTERN:')) { + const pattern = expectedValue.substring('PATTERN:'.length); + console.info(`Validating timestamp against pattern: ${pattern}`); + + const formatMatch = validateTimestampFormat(actualValue, pattern); + + if (formatMatch) { + console.info(`✅ Timestamp matches pattern for ${fieldName}`); + } else { + console.error(`❌ Timestamp doesn't match pattern for ${fieldName}`); + expect(formatMatch).toBe(true); + } + } else { + if (expectedValue === actualValue) { + console.info(`✅ Timestamp exact match for ${fieldName}`); + } else { + try { + const expectedDate = new Date(expectedValue as string); + const actualDate = new Date(actualValue); + + const expectedTimeWithoutMs = new Date(expectedDate); + expectedTimeWithoutMs.setMilliseconds(0); + const actualTimeWithoutMs = new Date(actualDate); + actualTimeWithoutMs.setMilliseconds(0); + + if (expectedTimeWithoutMs.getTime() === actualTimeWithoutMs.getTime()) { + console.info(`✅ Timestamp matches (ignoring milliseconds) for ${fieldName}`); + } else { + const timeDiff = Math.abs(expectedDate.getTime() - actualDate.getTime()); + const oneMinute = 60 * 1000; + + if (timeDiff <= oneMinute) { + console.info(`✅ Timestamp within acceptable range (±1 minute) for ${fieldName}`); + } else { + expect(actualValue).toBe(expectedValue); + } + } + } catch (e) { + console.error(`Error validating timestamp: ${e}`); + expect(actualValue).toBe(expectedValue); + } + } + } + + console.info(`✅ Validation completed for timestamp field ${fieldName} for NHS Number ${nhsNumber}`); + } + + // ✅ Custom dynamic rule description handling + else if (fieldName === 'RuleDescriptionDynamic') { + const actualValue = matchingObject['RuleDescription']; + console.info(`Actual RuleDescription: "${actualValue}"`); + + // Regex based on message requirement + const dynamicPattern = /Unable to add to cohort distribution\. As participant \d+ has triggered a validation exception/; + + try { + expect(actualValue).toMatch(dynamicPattern); + console.info(`✅ Dynamic message validation passed for NHS Number ${nhsNumber}`); + } catch (error) { + console.info(`❌ Dynamic message validation failed!`); + throw error; + } + } + + else { + console.info(`🚧 Validating field ${fieldName} with expected value ${expectedValue} for NHS Number ${nhsNumber}`); + + if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { + throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); + } + + if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { + console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); + continue; + } + + expect(matchingObject).toHaveProperty(fieldName); + expect(matchingObject[fieldName]).toBe(expectedValue); + console.info(`✅ Validation completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); + } + } + return true; +} +// Helper function to validate timestamp format +function validateTimestampFormat(timestamp: string, pattern: string): boolean { + if (!timestamp) return false; + + console.info(`Actual timestamp: ${timestamp}`); + + + if (pattern === 'yyyy-MM-ddTHH:mm:ss' || pattern === 'yyyy-MM-ddTHH:mm:ss.SSS') { + + return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(timestamp); + } + else if (pattern === 'yyyy-MM-dd') { + + return /^\d{4}-\d{2}-\d{2}$/.test(timestamp); + } + else { + + return !isNaN(new Date(timestamp).getTime()); + } +} + +async function delayRetry() { + await new Promise((resolve) => setTimeout(resolve, waitTime)); + waitTime += 5000; +} + + +export async function checkMappingsByIndex( + original: Array<{ requestId: string; nhsNumber: string }>, + shifted: Array<{ requestId: string; nhsNumber: string }> +): Promise { + const uniqueOriginalRequestIds: string[] = []; + original.forEach(item => { + if (!uniqueOriginalRequestIds.includes(item.requestId)) { + uniqueOriginalRequestIds.push(item.requestId); + } + }); + const uniqueShiftedRequestIds: string[] = []; + shifted.forEach(item => { + if (!uniqueShiftedRequestIds.includes(item.requestId)) { + uniqueShiftedRequestIds.push(item.requestId); + } + }); + + let allMatched = true; + for (let i = 0; i < uniqueShiftedRequestIds.length; i++) { + const shiftedRequestId = uniqueShiftedRequestIds[i]; + const originalNextRequestId = uniqueOriginalRequestIds[i + 1]; + + if (!originalNextRequestId) { + console.info(`No next request ID for index ${i}`); + continue; + } + const shiftedNhsNumbers = shifted + .filter(item => item.requestId === shiftedRequestId) + .map(item => item.nhsNumber) + .sort((a, b) => a.localeCompare(b)); + + const originalNextNhsNumbers = original + .filter(item => item.requestId === originalNextRequestId) + .map(item => item.nhsNumber) + .sort((a, b) => a.localeCompare(b)); + + if (shiftedNhsNumbers.length !== originalNextNhsNumbers.length) { + console.info(`Length mismatch for index ${i}`); + console.info(`Shifted [${shiftedRequestId}]: ${shiftedNhsNumbers.length} items`); + console.info(`Original Next [${originalNextRequestId}]: ${originalNextNhsNumbers.length} items`); + allMatched = false; + continue; + } + + const allNhsNumbersMatch = shiftedNhsNumbers.every( + (nhsNumber, index) => nhsNumber === originalNextNhsNumbers[index] + ); + + if (!allNhsNumbersMatch) { + console.error(`❌ NHS numbers don't match for index ${i}`); + console.warn(`Shifted [${shiftedRequestId}]: ${shiftedNhsNumbers}`); + console.warn(`Original Next [${originalNextRequestId}]: ${originalNextNhsNumbers}`); + allMatched = false; + } else { + console.info(`✅ NHS numbers match for index ${i} (${shiftedRequestId} -> ${originalNextRequestId})`); + } + } + + return allMatched; +} + export async function pollApiForOKResponse(httpRequest: () => Promise): Promise{ let apiResponse: ApiResponse | null = null; let i = 0; diff --git a/tests/playwright-tests/src/api/RetryCore/retryNew.ts b/tests/playwright-tests/src/api/RetryCore/retryNew.ts new file mode 100644 index 000000000..2b34b01c6 --- /dev/null +++ b/tests/playwright-tests/src/api/RetryCore/retryNew.ts @@ -0,0 +1,140 @@ +import { APIResponse, expect } from "@playwright/test"; +import { config } from "../../config/env"; +//import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; + + +import { ApiResponse } from "../core/types"; +import { constants } from "buffer"; +import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; + +const NHS_NUMBER_KEY = config.nhsNumberKey; +const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; + +export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { + let endpoint = ""; + let errorTrace: any = undefined; + let status = false; + try { + for (const apiValidation of validationJson) { + endpoint = apiValidation.validations.apiEndpoint; + var res = await pollAPI(endpoint, apiValidation, request); + return res; + + } + } + catch (error) { + const errorMsg = `Endpoint: ${endpoint}, Error: ${error instanceof Error ? error.stack || error.message : error}`; + errorTrace = errorMsg; + console.error(errorMsg); + } + return {status, errorTrace }; +} + +async function handleOKResponse(apiValidation: any, endpoint: string, response: any ) : Promise{ + // Normal response handling (200, etc.) + expect(response.ok()).toBeTruthy(); + const responseBody = await response.json(); + expect(Array.isArray(responseBody)).toBeTruthy(); + const { matchingObject, nhsNumber, matchingObjects } = await findMatchingObject(endpoint, responseBody, apiValidation); + console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); + console.info(`From Response ${JSON.stringify(matchingObject, null, 2)}`); + let status = await validateFields(apiValidation, matchingObject, nhsNumber, matchingObjects); + + return status; +} + + +async function HandleNoContentResponse(expectedCount: number, apiValidation: any, endpoint: string): Promise { + if (expectedCount !== undefined && Number(expectedCount) === 0) { + console.info(`✅ Status 204: Expected 0 records for endpoint ${endpoint}`); + + // Get NHS number for validation + const nhsNumber = apiValidation.validations.NHSNumber || + apiValidation.validations.NhsNumber || + apiValidation.validations[NHS_NUMBER_KEY] || + apiValidation.validations[NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC]; + + console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); + console.info(`From Response: null (204 No Content - 0 records as expected)`); + let status = await validateFields(apiValidation, null, nhsNumber, []); + return status; + } else { + // 204 is unexpected, log error and return false to trigger retry + console.warn(`Status 204: No data found in the table using endpoint ${endpoint}`); + return false; + } +} + + +async function pollAPI(endpoint: string, apiValidation: any, request: any): Promise<{ apiResponse: APIResponse, status: boolean}> { + let apiResponse: APIResponse | null = null; + let i = 0; + + let maxNumberOfRetries = config.maxNumberOfRetries; + let maxTimeBetweenRequests = config.maxTimeBetweenRequests; + let status = false; + console.info(`now trying request for ${maxNumberOfRetries} retries`); + while (i < Number(maxNumberOfRetries)) { + apiResponse = await fetchApiResponse(endpoint, request); + switch(apiResponse.status()) { + case 204: + console.info("now handling no content response"); + const expectedCount = apiValidation.validations.expectedCount; + status = await HandleNoContentResponse(expectedCount, apiValidation, endpoint); + break; + case 200: + console.info("now handling OK response"); + status = await handleOKResponse(apiValidation, endpoint, apiResponse); + break; + default: + console.error("there was an error when handling response from "); + break; + } + console.log("api status code is: ", apiResponse.status()); + if(status) { + break; + } + i++; + + console.info(`http response completed ${i}/${maxNumberOfRetries} of number of retries`); + await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); + } + + + if (!apiResponse) { + throw new Error("apiResponse was never assigned"); + } + + return {apiResponse, status}; +} + + +export async function pollApiForOKResponse(httpRequest: () => Promise): Promise{ + let apiResponse: ApiResponse | null = null; + let i = 0; + let maxNumberOfRetries = config.maxNumberOfRetries; + let maxTimeBetweenRequests = config.maxTimeBetweenRequests; + + console.info(`now trying request for ${maxNumberOfRetries} retries`); + while (i < Number(maxNumberOfRetries)) { + try { + apiResponse = await httpRequest(); + if (apiResponse.status == 200) { + console.info("200 response found") + break; + } + } + catch(exception) { + console.error("Error reading request body:", exception); + } + i++; + + console.info(`http response completed ${i}/${maxNumberOfRetries} of number of retries`); + await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); + } + + if (!apiResponse) { + throw new Error("apiResponse was never assigned"); + } + return apiResponse; +}; diff --git a/tests/playwright-tests/src/api/RetryCore/retryOld.ts b/tests/playwright-tests/src/api/RetryCore/retryOld.ts new file mode 100644 index 000000000..bffeeeef7 --- /dev/null +++ b/tests/playwright-tests/src/api/RetryCore/retryOld.ts @@ -0,0 +1,90 @@ +import { APIResponse, expect } from "@playwright/test"; + +import { config } from "../config/env"; +import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; + +const apiRetry = Number(config.apiRetry); +const initialWaitTime = Number(config.apiWaitTime) || 2000; +const endpointCohortDistributionDataService = config.endpointCohortDistributionDataService; +const endpointParticipantManagementDataService = config.endpointParticipantManagementDataService; +const endpointExceptionManagementDataService = config.endpointExceptionManagementDataService; +const endpointParticipantDemographicDataService = config.endpointParticipantDemographicDataService; + +const COHORT_DISTRIBUTION_SERVICE = config.cohortDistributionService; +const PARTICIPANT_MANAGEMENT_SERVICE = config.participantManagementService; +const EXCEPTION_MANAGEMENT_SERVICE = config.exceptionManagementService; +const PARTICIPANT_DEMOGRAPHIC_SERVICE = config.participantDemographicDataService; +const NHS_NUMBER_KEY = config.nhsNumberKey; +const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; +const IGNORE_VALIDATION_KEY = config.ignoreValidationKey; + +let waitTime = initialWaitTime; +let response: APIResponse; + +export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { + + +let status = false; + let endpoint = ""; + let errorTrace: any = undefined; + + for (let attempt = 1; attempt <= apiRetry; attempt++) { + if (status) break; + + try { + for (const apiValidation of validationJson) { + endpoint = apiValidation.validations.apiEndpoint; + response = await fetchApiResponse(endpoint, request); + + // Handle 204 No Content responses BEFORE parsing JSON + if (response.status() === 204) { + // Check if 204 is expected (expectedCount: 0) + const expectedCount = apiValidation.validations.expectedCount; + + if (expectedCount !== undefined && Number(expectedCount) === 0) { + console.info(`✅ Status 204: Expected 0 records for endpoint ${endpoint}`); + + // Get NHS number for validation + const nhsNumber = apiValidation.validations.NHSNumber || + apiValidation.validations.NhsNumber || + apiValidation.validations[NHS_NUMBER_KEY] || + apiValidation.validations[NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC]; + + console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); + console.info(`From Response: null (204 No Content - 0 records as expected)`); + status = await validateFields(apiValidation, null, nhsNumber, []); + } else { + // 204 is unexpected, throw error to trigger retry + throw new Error(`Status 204: No data found in the table using endpoint ${endpoint}`); + } + } else { + // Normal response handling (200, etc.) + expect(response.ok()).toBeTruthy(); + const responseBody = await response.json(); + expect(Array.isArray(responseBody)).toBeTruthy(); + const { matchingObject, nhsNumber, matchingObjects } = await findMatchingObject(endpoint, responseBody, apiValidation); + console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); + console.info(`From Response ${JSON.stringify(matchingObject, null, 2)}`); + status = await validateFields(apiValidation, matchingObject, nhsNumber, matchingObjects); + } + } + } catch (error) { + const errorMsg = `Endpoint: ${endpoint}, Status: ${response?.status?.()}, Error: ${error instanceof Error ? error.stack || error.message : error}`; + errorTrace = errorMsg; + if (response?.status?.() === 204) { + console.info(`ℹ️\t Status 204: No data found in the table using endpoint ${endpoint}`); + } + } + + if (attempt < apiRetry && !status) { + console.info(`🚧 Function processing in progress; will check again using data service ${endpoint} in ${Math.round(waitTime / 1000)} seconds...`); + await delayRetry(); + } + } + waitTime = Number(config.apiWaitTime); + return { status, errorTrace }; +} +function delayRetry() { + throw new Error("Function not implemented."); +} + diff --git a/tests/playwright-tests/src/api/apiHelper.ts b/tests/playwright-tests/src/api/apiHelper.ts index 2dbaa723c..3bf7985b1 100644 --- a/tests/playwright-tests/src/api/apiHelper.ts +++ b/tests/playwright-tests/src/api/apiHelper.ts @@ -1,9 +1,10 @@ -import { APIResponse } from "@playwright/test"; +import { APIResponse, expect } from "@playwright/test"; import { config } from "../config/env"; import { assertOnCounts, assertOnRecordDateTimes, assertOnNhsNumber, MatchDynamicType, MatchOnRuleDescriptionDynamic } from "./core/assertOnTypes"; +const IGNORE_VALIDATION_KEY = config.ignoreValidationKey; export async function fetchApiResponse(endpoint: string, request: any): Promise { let currentEndPoint = endpoint.toLowerCase() @@ -63,14 +64,14 @@ export async function findMatchingObject(endpoint: string, responseBody: any[], const ruleIdToFind = apiValidation.validations.RuleId; const ruleDescToFind = apiValidation.validations.RuleDescription; - let betterMatches = matchingObjects.filter(record => + const betterMatches = matchingObjects.filter(record => (ruleIdToFind === undefined || record.RuleId === ruleIdToFind) && (ruleDescToFind === undefined || record.RuleDescription === ruleDescToFind) ); if (betterMatches.length > 0) { matchingObject = betterMatches[0]; - console.info(`Found better matching record with NHS Number ${nhsNumber} and RuleId ${ruleIdToFind || 'any'}`); + console.log(`Found better matching record with NHS Number ${nhsNumber} and RuleId ${ruleIdToFind || 'any'}`); } } @@ -79,36 +80,180 @@ export async function findMatchingObject(endpoint: string, responseBody: any[], export async function validateFields(apiValidation: any, matchingObject: any, nhsNumber: any, matchingObjects: any): Promise { - const fieldsToValidate = Object.entries(apiValidation.validations).filter(([key]) => key !== config.ignoreValidationKey); + const fieldsToValidate = Object.entries(apiValidation.validations).filter(([key]) => key !== IGNORE_VALIDATION_KEY); for (const [fieldName, expectedValue] of fieldsToValidate) { - switch(fieldName.toLowerCase()){ - case "expectedcount": - assertOnCounts(matchingObject, nhsNumber, matchingObjects, fieldName, expectedValue); - break; - case "nhsnumber": - if(!matchingObject) { - assertOnNhsNumber(expectedValue, nhsNumber); + if (fieldName === "expectedCount") { + console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); + + let actualCount = 0; + if (matchingObjects && Array.isArray(matchingObjects)) { + actualCount = matchingObjects.length; + } else if (matchingObjects === null || matchingObjects === undefined) { + actualCount = 0; + console.warn(`⚠️ matchingObjects is ${matchingObjects === null ? 'null' : 'undefined'} for NHS Number ${nhsNumber}`); + } else { + actualCount = 1; + console.warn(`⚠️ matchingObjects is not an array for NHS Number ${nhsNumber}, treating as single object`); + } + + console.info(`📊 Actual count: ${actualCount}, Expected count: ${expectedValue} for NHS Number ${nhsNumber}`); + + const expectedCount = Number(expectedValue); + + if (isNaN(expectedCount)) { + throw new Error(`❌ expectedCount value '${expectedValue}' is not a valid number for NHS Number ${nhsNumber}`); + } + + // Perform the assertion + try { + expect(actualCount).toBe(expectedCount); + console.info(`✅ Count check completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); + } catch (error) { + console.error(`❌ Count check failed for NHS Number ${nhsNumber}: Expected ${expectedCount}, but got ${actualCount}`); + throw error; + } + } + + // Handle NHS Number validation specially for 204 responses + else if ((fieldName === "NHSNumber" || fieldName === "NhsNumber") && !matchingObject) { + console.info(`🚧 Validating NHS Number field ${fieldName} for 204 response`); + + // For 204 responses, validate that we searched for the correct NHS number + const expectedNhsNumber = Number(expectedValue); + const actualNhsNumber = Number(nhsNumber); + + try { + expect(actualNhsNumber).toBe(expectedNhsNumber); + console.info(`✅ NHS Number validation completed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); + } catch (error) { + console.error(`❌ NHS Number validation failed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); + throw error; + } + } + + else if (fieldName === 'RecordInsertDateTime' || fieldName === 'RecordUpdateDateTime') { + console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); + + if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { + throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); + } + + if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { + console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); + continue; + } + + expect(matchingObject).toHaveProperty(fieldName); + const actualValue = matchingObject[fieldName]; + + if (typeof expectedValue === 'string' && expectedValue.startsWith('PATTERN:')) { + const pattern = expectedValue.substring('PATTERN:'.length); + console.info(`Validating timestamp against pattern: ${pattern}`); + + const formatMatch = validateTimestampFormat(actualValue, pattern); + + if (formatMatch) { + console.info(`✅ Timestamp matches pattern for ${fieldName}`); + } else { + console.error(`❌ Timestamp doesn't match pattern for ${fieldName}`); + expect(formatMatch).toBe(true); + } + } else { + if (expectedValue === actualValue) { + console.info(`✅ Timestamp exact match for ${fieldName}`); + } else { + try { + const expectedDate = new Date(expectedValue as string); + const actualDate = new Date(actualValue); + + const expectedTimeWithoutMs = new Date(expectedDate); + expectedTimeWithoutMs.setMilliseconds(0); + const actualTimeWithoutMs = new Date(actualDate); + actualTimeWithoutMs.setMilliseconds(0); + + if (expectedTimeWithoutMs.getTime() === actualTimeWithoutMs.getTime()) { + console.info(`✅ Timestamp matches (ignoring milliseconds) for ${fieldName}`); + } else { + const timeDiff = Math.abs(expectedDate.getTime() - actualDate.getTime()); + const oneMinute = 60 * 1000; + + if (timeDiff <= oneMinute) { + console.info(`✅ Timestamp within acceptable range (±1 minute) for ${fieldName}`); + } else { + expect(actualValue).toBe(expectedValue); + } + } + } catch (e) { + console.error(`Error validating timestamp: ${e}`); + expect(actualValue).toBe(expectedValue); + } } - break; - case "recordinsertdatetime": - assertOnRecordDateTimes(fieldName, expectedValue, nhsNumber, matchingObject); - break; - case "recordUpdatedatetime": - assertOnRecordDateTimes(fieldName, expectedValue, nhsNumber, matchingObject); - break; - case "ruledescriptiondynamic": - MatchOnRuleDescriptionDynamic(matchingObject, nhsNumber); - break; - default: - MatchDynamicType(matchingObject, nhsNumber, expectedValue, fieldName); - break; + } + + console.info(`✅ Validation completed for timestamp field ${fieldName} for NHS Number ${nhsNumber}`); + } + + // ✅ Custom dynamic rule description handling + else if (fieldName === 'RuleDescriptionDynamic') { + const actualValue = matchingObject['RuleDescription']; + console.info(`Actual RuleDescription: "${actualValue}"`); + + // Regex based on message requirement + const dynamicPattern = /Unable to add to cohort distribution\. As participant \d+ has triggered a validation exception/; + + try { + expect(actualValue).toMatch(dynamicPattern); + console.info(`✅ Dynamic message validation passed for NHS Number ${nhsNumber}`); + } catch (error) { + console.info(`❌ Dynamic message validation failed!`); + throw error; + } + } + + else { + console.info(`🚧 Validating field ${fieldName} with expected value ${expectedValue} for NHS Number ${nhsNumber}`); + + if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { + throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); + } + + if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { + console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); + continue; + } + + expect(matchingObject).toHaveProperty(fieldName); + expect(matchingObject[fieldName]).toBe(expectedValue); + console.info(`✅ Validation completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); } } return true; } +function validateTimestampFormat(timestamp: string, pattern: string): boolean { + if (!timestamp) return false; + + console.info(`Actual timestamp: ${timestamp}`); + + + if (pattern === 'yyyy-MM-ddTHH:mm:ss' || pattern === 'yyyy-MM-ddTHH:mm:ss.SSS') { + + return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(timestamp); + } + else if (pattern === 'yyyy-MM-dd') { + + return /^\d{4}-\d{2}-\d{2}$/.test(timestamp); + } + else { + + return !isNaN(new Date(timestamp).getTime()); + } +} + + + export async function checkMappingsByIndex( original: Array<{ requestId: string; nhsNumber: string }>, shifted: Array<{ requestId: string; nhsNumber: string }> diff --git a/tests/playwright-tests/src/api/core/assertOnTypes.ts b/tests/playwright-tests/src/api/core/assertOnTypes.ts index 67550b3ce..20682c5aa 100644 --- a/tests/playwright-tests/src/api/core/assertOnTypes.ts +++ b/tests/playwright-tests/src/api/core/assertOnTypes.ts @@ -1,68 +1,83 @@ import { expect } from "@playwright/test"; export function assertOnCounts(matchingObject: any, nhsNumber: any, matchingObjects: any, fieldName: string, expectedValue: unknown) { + console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); + let actualCount = 0; - console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); - if (matchingObjects && Array.isArray(matchingObjects)) { - actualCount = matchingObjects.length; + actualCount = matchingObjects.length; } else if (matchingObjects === null || matchingObjects === undefined) { - actualCount = 0; - console.warn(`⚠️ matchingObjects is ${matchingObjects === null ? 'null' : 'undefined'} for NHS Number ${nhsNumber}`); + actualCount = 0; + console.warn(`⚠️ matchingObjects is ${matchingObjects === null ? 'null' : 'undefined'} for NHS Number ${nhsNumber}`); } else { - actualCount = 1; - console.warn(`⚠️ matchingObjects is not an array for NHS Number ${nhsNumber}, treating as single object`); + actualCount = 1; + console.warn(`⚠️ matchingObjects is not an array for NHS Number ${nhsNumber}, treating as single object`); } console.info(`📊 Actual count: ${actualCount}, Expected count: ${expectedValue} for NHS Number ${nhsNumber}`); + const expectedCount = Number(expectedValue); if (isNaN(expectedCount)) { - throw new Error(`❌ expectedCount value '${expectedValue}' is not a valid number for NHS Number ${nhsNumber}`); + throw new Error(`❌ expectedCount value '${expectedValue}' is not a valid number for NHS Number ${nhsNumber}`); + } + + // Perform the assertion + try { + expect(actualCount).toBe(expectedCount); + console.info(`✅ Count check completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); + } catch (error) { + console.error(`❌ Count check failed for NHS Number ${nhsNumber}: Expected ${expectedCount}, but got ${actualCount}`); + throw error; } - expect(actualCount).toBe(expectedCount); - console.info(`✅ Count check completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`) } export function assertOnNhsNumber(expectedValue: unknown , nhsNumber: string) -{ - // For 204 responses, validate that we searched for the correct NHS number - const expectedNhsNumber = Number(expectedValue); - const actualNhsNumber = Number(nhsNumber); +{ + console.info(`🚧 Validating NHS Number field for 204 response`); + + // For 204 responses, validate that we searched for the correct NHS number + const expectedNhsNumber = Number(expectedValue); + const actualNhsNumber = Number(nhsNumber); + try { expect(actualNhsNumber).toBe(expectedNhsNumber); - console.info(`✅ NHS Number validation completed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); + console.info(`✅ NHS Number validation completed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); + } catch (error) { + console.error(`❌ NHS Number validation failed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); + throw error; + } } export function assertOnRecordDateTimes(fieldName: string, expectedValue: unknown, nhsNumber: string, matchingObject: any ) { - console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); + console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); - if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { - throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); - } + if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { + throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); + } - if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { - console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); - } + if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { + console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); + return; + } - expect(matchingObject).toHaveProperty(fieldName); - const actualValue = matchingObject[fieldName]; + expect(matchingObject).toHaveProperty(fieldName); + const actualValue = matchingObject[fieldName]; - if (typeof expectedValue === 'string' && expectedValue.startsWith('PATTERN:')) { - const pattern = expectedValue.substring('PATTERN:'.length); - console.info(`Validating timestamp against pattern: ${pattern}`); + if (typeof expectedValue === 'string' && expectedValue.startsWith('PATTERN:')) { + const pattern = expectedValue.substring('PATTERN:'.length); + console.info(`Validating timestamp against pattern: ${pattern}`); - const formatMatch = validateTimestampFormat(actualValue, pattern); + const formatMatch = validateTimestampFormat(actualValue, pattern); - if (formatMatch) { - console.info(`✅ Timestamp matches pattern for ${fieldName}`); - } else { - console.error(`❌ Timestamp doesn't match pattern for ${fieldName}`); - expect(formatMatch).toBe(true); - } - - }else { + if (formatMatch) { + console.info(`✅ Timestamp matches pattern for ${fieldName}`); + } else { + console.error(`❌ Timestamp doesn't match pattern for ${fieldName}`); + expect(formatMatch).toBe(true); + } + } else { if (expectedValue === actualValue) { console.info(`✅ Timestamp exact match for ${fieldName}`); } else { @@ -93,6 +108,7 @@ export function assertOnRecordDateTimes(fieldName: string, expectedValue: unknow } } } + console.info(`✅ Validation completed for timestamp field ${fieldName} for NHS Number ${nhsNumber}`); } @@ -100,7 +116,7 @@ export function assertOnRecordDateTimes(fieldName: string, expectedValue: unknow export function MatchOnRuleDescriptionDynamic(matchingObject: any, nhsNumber: string) { - const actualValue = matchingObject['RuleDescription']; + const actualValue = matchingObject['RuleDescription']; console.info(`Actual RuleDescription: "${actualValue}"`); // Regex based on message requirement @@ -116,20 +132,21 @@ export function MatchOnRuleDescriptionDynamic(matchingObject: any, nhsNumber: st } export function MatchDynamicType(matchingObject: any, nhsNumber: string, expectedValue: any, fieldName: string) { - console.info(`🚧 Validating field ${fieldName} with expected value ${expectedValue} for NHS Number ${nhsNumber}`); + console.info(`🚧 Validating field ${fieldName} with expected value ${expectedValue} for NHS Number ${nhsNumber}`); - if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { + if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); - } + } - if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { + if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); - } + return; + } - expect(matchingObject).toHaveProperty(fieldName); - expect(matchingObject[fieldName]).toBe(expectedValue); - console.info(`✅ Validation completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); -} + expect(matchingObject).toHaveProperty(fieldName); + expect(matchingObject[fieldName]).toBe(expectedValue); + console.info(`✅ Validation completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); + } function validateTimestampFormat(timestamp: string, pattern: string): boolean { diff --git a/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts b/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts index c9cc8fb51..07abee521 100644 --- a/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts +++ b/tests/playwright-tests/src/tests/e2e/epic4b-block-participant-tests/epic4b-7667-block-paricipant-tessuite.spec.ts @@ -20,7 +20,7 @@ annotation: [{ test.describe('@regression @e2e @epic4b-block-tests @smoke Tests', async () => { TestHooks.setupAllTestHooks(); - test('@DTOSS-7667-01 - AC1 - Verify participant is deleted from CohortDistributionDataService', async ({ request }: { request: APIRequestContext }, testInfo: TestInfo) => { + test ('@DTOSS-7667-01 - AC1 - Verify participant is deleted from CohortDistributionDataService', async ({ request }: { request: APIRequestContext }, testInfo: TestInfo) => { // Arrange: Get test data const [addValidations, inputParticipantRecord, nhsNumbers, testFilesPath] = await getApiTestData(testInfo.title, 'ADD_BLOCKED'); const nhsNumber = nhsNumbers[0]; @@ -82,15 +82,16 @@ test.describe('@regression @e2e @epic4b-block-tests @smoke Tests', async () => { await test.step(`the participant has been blocked`, async () => { let response: ApiResponse | null = null; - let RetryLimit = 2 - let retryCount = 0 + let RetryLimit = 2; + let retryCount = 0; while(retryCount < RetryLimit) { response = await pollApiForOKResponse(() => getRecordsFromParticipantManagementService(request)); if (response?.data?.[0]?.BlockedFlag === 1) { break; - } + } + retryCount++; } if(response !== null) { @@ -112,17 +113,18 @@ test.describe('@regression @e2e @epic4b-block-tests @smoke Tests', async () => { await processFileViaStorage(parquetFile); let validationExceptions; - let RetryLimit = 2 - let retryCount = 0 + let RetryLimit = 2; + let retryCount = 0; while(retryCount < RetryLimit) { var responseFromExceptions = await pollApiForOKResponse(() => getRecordsFromExceptionService(request)); - if(responseFromExceptions.data.length >= 3) + if(responseFromExceptions.data.length == 3) { validationExceptions = responseFromExceptions.data break; } + retryCount++; console.log(`waiting for exception for participant blocked to be added to exception table...`); } diff --git a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts index 8fe4f5427..4215bd8f4 100644 --- a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts +++ b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts @@ -58,25 +58,6 @@ test.describe('@DTOSS-3881-01 @e2e @epic4c- Cohort Manger subscribed the Added r expect(response?.data?.[0]?.ReferralFlag).toBe(1); }); -<<<<<<< HEAD:tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts - await test.step('DTOSS-10012 DTOSS-10012 verify NemsSubscription_Id in NEMS_SUBSCRIPTION table', async () => { - const nhsNumber = participantData['inputParticipantRecord'].u_case_variable_data.nhs_number; - - const response = await retry( - async () => { - const res = await getRecordsFromNemsSubscription(request, nhsNumber); - const validators = composeValidators(expectStatus(200)); - await validators(res); - - return res; - }, - (res) => { - const subscriptionID = extractSubscriptionID(res); - return subscriptionID !== null; - }, - { retries: 5, delayMs: 2000 } - ); -======= await test.step('DTOSS-10012 ADD verify NemsSubscription in NEMS_SUBSCRIPTION table', async () => { const nhsNumber = participantData['inputParticipantRecord'].u_case_variable_data.nhs_number; const response = await getRecordsFromNemsSubscription(request, nhsNumber); @@ -84,7 +65,6 @@ test.describe('@DTOSS-3881-01 @e2e @epic4c- Cohort Manger subscribed the Added r expectStatus(200) ); await validators(response); ->>>>>>> e9abb1665a60258957de73ecf486b199808e514a:tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-testsuite.spec.ts const subscriptionID = extractSubscriptionID(response); expect(subscriptionID).not.toBeNull(); console.log(`Extracted Subscription ID: ${subscriptionID} for number: ${nhsNumber}`); From f67a4f02ee1191e4decf01929c8f26e6c24ce37a Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Mon, 8 Sep 2025 10:39:33 +0100 Subject: [PATCH 09/13] fix: trying to get local debugging working --- .../src/api/RetryCore/Retry.ts | 316 +----------------- tests/playwright-tests/src/api/apiHelper.ts | 45 +-- .../src/api/core/assertOnTypes.ts | 2 +- tests/playwright-tests/src/config/env.ts | 2 +- .../ADD-participantPayload.json | 14 + .../epic4c-10011-10012-testsuite.spec.ts | 1 + .../epic4c-testsuite-migrated.ts | 10 +- ...1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json | 107 +----- .../tests/runner/runner-workflow-add.spec.ts | 4 +- .../runner/runner-workflow-amend.spec.ts | 42 ++- .../playwright-tests/src/tests/steps/steps.ts | 20 +- 11 files changed, 97 insertions(+), 466 deletions(-) create mode 100644 tests/playwright-tests/src/tests/api/testFiles/@DTOSS-5928-01/ADD-participantPayload.json diff --git a/tests/playwright-tests/src/api/RetryCore/Retry.ts b/tests/playwright-tests/src/api/RetryCore/Retry.ts index ccbfda9fe..816a8ed8d 100644 --- a/tests/playwright-tests/src/api/RetryCore/Retry.ts +++ b/tests/playwright-tests/src/api/RetryCore/Retry.ts @@ -1,27 +1,17 @@ import { APIResponse, expect } from "@playwright/test"; import { config } from "../../config/env"; import { ApiResponse } from "../core/types"; +import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; + -const apiRetry = Number(config.apiRetry); -const initialWaitTime = Number(config.apiWaitTime) || 2000; -const endpointCohortDistributionDataService = config.endpointCohortDistributionDataService; -const endpointParticipantManagementDataService = config.endpointParticipantManagementDataService; -const endpointExceptionManagementDataService = config.endpointExceptionManagementDataService; -const endpointParticipantDemographicDataService = config.endpointParticipantDemographicDataService; -const COHORT_DISTRIBUTION_SERVICE = config.cohortDistributionService; -const PARTICIPANT_MANAGEMENT_SERVICE = config.participantManagementService; -const EXCEPTION_MANAGEMENT_SERVICE = config.exceptionManagementService; const PARTICIPANT_DEMOGRAPHIC_SERVICE = config.participantDemographicDataService; const NHS_NUMBER_KEY = config.nhsNumberKey; const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; const IGNORE_VALIDATION_KEY = config.ignoreValidationKey; -let waitTime = initialWaitTime; -let response: APIResponse; - export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { let endpoint = ""; let errorTrace: any = undefined; @@ -128,311 +118,9 @@ async function handleOKResponse(apiValidation: any, endpoint: string, response: return status; } -export async function fetchApiResponse(endpoint: string, request: any): Promise { - - if (endpoint.includes(COHORT_DISTRIBUTION_SERVICE)) { - return await request.get(`${endpointCohortDistributionDataService}${endpoint.toLowerCase()}`); - } else if (endpoint.includes(PARTICIPANT_MANAGEMENT_SERVICE)) { - return await request.get(`${endpointParticipantManagementDataService}${endpoint.toLowerCase()}`); - } else if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE)) { - return await request.get(`${endpointExceptionManagementDataService}${endpoint.toLowerCase()}`); - } else if (endpoint.includes(PARTICIPANT_DEMOGRAPHIC_SERVICE)) { - return await request.get(`${endpointParticipantDemographicDataService}${endpoint.toLowerCase()}`); - } - throw new Error(`Unknown endpoint: ${endpoint}`); -} - -async function findMatchingObject(endpoint: string, responseBody: any[], apiValidation: any) { - let nhsNumber: any; - let matchingObjects: any[] = []; - let matchingObject: any; - - - let nhsNumberKey; - if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) || endpoint.includes(PARTICIPANT_DEMOGRAPHIC_SERVICE)) { - nhsNumberKey = NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC; - } else if (endpoint.includes("participantmanagementdataservice") || endpoint.includes("CohortDistributionDataService")) { - nhsNumberKey = "NHSNumber"; - } else { - nhsNumberKey = NHS_NUMBER_KEY; - } - - nhsNumber = apiValidation.validations[nhsNumberKey]; - - if (!nhsNumber) { - if (apiValidation.validations.NhsNumber) { - nhsNumber = apiValidation.validations.NhsNumber; - } else if (apiValidation.validations.NHSNumber) { - nhsNumber = apiValidation.validations.NHSNumber; - } - } - - matchingObjects = responseBody.filter((item: Record) => - item[nhsNumberKey] == nhsNumber || - item.NhsNumber == nhsNumber || - item.NHSNumber == nhsNumber - ); - - matchingObject = matchingObjects[matchingObjects.length - 1]; - - if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) && - (apiValidation.validations.RuleId !== undefined || apiValidation.validations.RuleDescription)) { - const ruleIdToFind = apiValidation.validations.RuleId; - const ruleDescToFind = apiValidation.validations.RuleDescription; - - const betterMatches = matchingObjects.filter(record => - (ruleIdToFind === undefined || record.RuleId === ruleIdToFind) && - (ruleDescToFind === undefined || record.RuleDescription === ruleDescToFind) - ); - - if (betterMatches.length > 0) { - matchingObject = betterMatches[0]; - console.log(`Found better matching record with NHS Number ${nhsNumber} and RuleId ${ruleIdToFind || 'any'}`); - } - } - - return { matchingObject, nhsNumber, matchingObjects }; -} - - -async function validateFields(apiValidation: any, matchingObject: any, nhsNumber: any, matchingObjects: any): Promise { - const fieldsToValidate = Object.entries(apiValidation.validations).filter(([key]) => key !== IGNORE_VALIDATION_KEY); - - for (const [fieldName, expectedValue] of fieldsToValidate) { - if (fieldName === "expectedCount") { - console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); - - let actualCount = 0; - if (matchingObjects && Array.isArray(matchingObjects)) { - actualCount = matchingObjects.length; - } else if (matchingObjects === null || matchingObjects === undefined) { - actualCount = 0; - console.warn(`⚠️ matchingObjects is ${matchingObjects === null ? 'null' : 'undefined'} for NHS Number ${nhsNumber}`); - } else { - actualCount = 1; - console.warn(`⚠️ matchingObjects is not an array for NHS Number ${nhsNumber}, treating as single object`); - } - - console.info(`📊 Actual count: ${actualCount}, Expected count: ${expectedValue} for NHS Number ${nhsNumber}`); - - const expectedCount = Number(expectedValue); - - if (isNaN(expectedCount)) { - throw new Error(`❌ expectedCount value '${expectedValue}' is not a valid number for NHS Number ${nhsNumber}`); - } - - // Perform the assertion - try { - expect(actualCount).toBe(expectedCount); - console.info(`✅ Count check completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); - } catch (error) { - console.error(`❌ Count check failed for NHS Number ${nhsNumber}: Expected ${expectedCount}, but got ${actualCount}`); - throw error; - } - } - - // Handle NHS Number validation specially for 204 responses - else if ((fieldName === "NHSNumber" || fieldName === "NhsNumber") && !matchingObject) { - console.info(`🚧 Validating NHS Number field ${fieldName} for 204 response`); - - // For 204 responses, validate that we searched for the correct NHS number - const expectedNhsNumber = Number(expectedValue); - const actualNhsNumber = Number(nhsNumber); - - try { - expect(actualNhsNumber).toBe(expectedNhsNumber); - console.info(`✅ NHS Number validation completed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); - } catch (error) { - console.error(`❌ NHS Number validation failed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); - throw error; - } - } - - else if (fieldName === 'RecordInsertDateTime' || fieldName === 'RecordUpdateDateTime') { - console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); - - if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { - throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); - } - - if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { - console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); - continue; - } - - expect(matchingObject).toHaveProperty(fieldName); - const actualValue = matchingObject[fieldName]; - - if (typeof expectedValue === 'string' && expectedValue.startsWith('PATTERN:')) { - const pattern = expectedValue.substring('PATTERN:'.length); - console.info(`Validating timestamp against pattern: ${pattern}`); - - const formatMatch = validateTimestampFormat(actualValue, pattern); - - if (formatMatch) { - console.info(`✅ Timestamp matches pattern for ${fieldName}`); - } else { - console.error(`❌ Timestamp doesn't match pattern for ${fieldName}`); - expect(formatMatch).toBe(true); - } - } else { - if (expectedValue === actualValue) { - console.info(`✅ Timestamp exact match for ${fieldName}`); - } else { - try { - const expectedDate = new Date(expectedValue as string); - const actualDate = new Date(actualValue); - - const expectedTimeWithoutMs = new Date(expectedDate); - expectedTimeWithoutMs.setMilliseconds(0); - const actualTimeWithoutMs = new Date(actualDate); - actualTimeWithoutMs.setMilliseconds(0); - - if (expectedTimeWithoutMs.getTime() === actualTimeWithoutMs.getTime()) { - console.info(`✅ Timestamp matches (ignoring milliseconds) for ${fieldName}`); - } else { - const timeDiff = Math.abs(expectedDate.getTime() - actualDate.getTime()); - const oneMinute = 60 * 1000; - - if (timeDiff <= oneMinute) { - console.info(`✅ Timestamp within acceptable range (±1 minute) for ${fieldName}`); - } else { - expect(actualValue).toBe(expectedValue); - } - } - } catch (e) { - console.error(`Error validating timestamp: ${e}`); - expect(actualValue).toBe(expectedValue); - } - } - } - - console.info(`✅ Validation completed for timestamp field ${fieldName} for NHS Number ${nhsNumber}`); - } - - // ✅ Custom dynamic rule description handling - else if (fieldName === 'RuleDescriptionDynamic') { - const actualValue = matchingObject['RuleDescription']; - console.info(`Actual RuleDescription: "${actualValue}"`); - - // Regex based on message requirement - const dynamicPattern = /Unable to add to cohort distribution\. As participant \d+ has triggered a validation exception/; - - try { - expect(actualValue).toMatch(dynamicPattern); - console.info(`✅ Dynamic message validation passed for NHS Number ${nhsNumber}`); - } catch (error) { - console.info(`❌ Dynamic message validation failed!`); - throw error; - } - } - - else { - console.info(`🚧 Validating field ${fieldName} with expected value ${expectedValue} for NHS Number ${nhsNumber}`); - - if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { - throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); - } - - if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { - console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); - continue; - } - - expect(matchingObject).toHaveProperty(fieldName); - expect(matchingObject[fieldName]).toBe(expectedValue); - console.info(`✅ Validation completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); - } - } - return true; -} -// Helper function to validate timestamp format -function validateTimestampFormat(timestamp: string, pattern: string): boolean { - if (!timestamp) return false; - - console.info(`Actual timestamp: ${timestamp}`); - - - if (pattern === 'yyyy-MM-ddTHH:mm:ss' || pattern === 'yyyy-MM-ddTHH:mm:ss.SSS') { - return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(timestamp); - } - else if (pattern === 'yyyy-MM-dd') { - return /^\d{4}-\d{2}-\d{2}$/.test(timestamp); - } - else { - return !isNaN(new Date(timestamp).getTime()); - } -} - -async function delayRetry() { - await new Promise((resolve) => setTimeout(resolve, waitTime)); - waitTime += 5000; -} - - -export async function checkMappingsByIndex( - original: Array<{ requestId: string; nhsNumber: string }>, - shifted: Array<{ requestId: string; nhsNumber: string }> -): Promise { - const uniqueOriginalRequestIds: string[] = []; - original.forEach(item => { - if (!uniqueOriginalRequestIds.includes(item.requestId)) { - uniqueOriginalRequestIds.push(item.requestId); - } - }); - const uniqueShiftedRequestIds: string[] = []; - shifted.forEach(item => { - if (!uniqueShiftedRequestIds.includes(item.requestId)) { - uniqueShiftedRequestIds.push(item.requestId); - } - }); - - let allMatched = true; - for (let i = 0; i < uniqueShiftedRequestIds.length; i++) { - const shiftedRequestId = uniqueShiftedRequestIds[i]; - const originalNextRequestId = uniqueOriginalRequestIds[i + 1]; - - if (!originalNextRequestId) { - console.info(`No next request ID for index ${i}`); - continue; - } - const shiftedNhsNumbers = shifted - .filter(item => item.requestId === shiftedRequestId) - .map(item => item.nhsNumber) - .sort((a, b) => a.localeCompare(b)); - - const originalNextNhsNumbers = original - .filter(item => item.requestId === originalNextRequestId) - .map(item => item.nhsNumber) - .sort((a, b) => a.localeCompare(b)); - - if (shiftedNhsNumbers.length !== originalNextNhsNumbers.length) { - console.info(`Length mismatch for index ${i}`); - console.info(`Shifted [${shiftedRequestId}]: ${shiftedNhsNumbers.length} items`); - console.info(`Original Next [${originalNextRequestId}]: ${originalNextNhsNumbers.length} items`); - allMatched = false; - continue; - } - - const allNhsNumbersMatch = shiftedNhsNumbers.every( - (nhsNumber, index) => nhsNumber === originalNextNhsNumbers[index] - ); - - if (!allNhsNumbersMatch) { - console.error(`❌ NHS numbers don't match for index ${i}`); - console.warn(`Shifted [${shiftedRequestId}]: ${shiftedNhsNumbers}`); - console.warn(`Original Next [${originalNextRequestId}]: ${originalNextNhsNumbers}`); - allMatched = false; - } else { - console.info(`✅ NHS numbers match for index ${i} (${shiftedRequestId} -> ${originalNextRequestId})`); - } - } - - return allMatched; -} export async function pollApiForOKResponse(httpRequest: () => Promise): Promise{ let apiResponse: ApiResponse | null = null; diff --git a/tests/playwright-tests/src/api/apiHelper.ts b/tests/playwright-tests/src/api/apiHelper.ts index 3bf7985b1..d02d6f44d 100644 --- a/tests/playwright-tests/src/api/apiHelper.ts +++ b/tests/playwright-tests/src/api/apiHelper.ts @@ -4,6 +4,10 @@ import { config } from "../config/env"; import { assertOnCounts, assertOnRecordDateTimes, assertOnNhsNumber, MatchDynamicType, MatchOnRuleDescriptionDynamic } from "./core/assertOnTypes"; +const EXCEPTION_MANAGEMENT_SERVICE = config.exceptionManagementService; +const PARTICIPANT_DEMOGRAPHIC_SERVICE = config.participantDemographicDataService; +const NHS_NUMBER_KEY = config.nhsNumberKey; +const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; const IGNORE_VALIDATION_KEY = config.ignoreValidationKey; export async function fetchApiResponse(endpoint: string, request: any): Promise { @@ -32,13 +36,14 @@ export async function findMatchingObject(endpoint: string, responseBody: any[], let matchingObjects: any[] = []; let matchingObject: any; + let nhsNumberKey; - if (endpoint.includes(config.exceptionManagementService) || endpoint.includes(config.participantDemographicDataService)) { - nhsNumberKey = config.nhsNumberKeyExceptionDemographic; + if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) || endpoint.includes(PARTICIPANT_DEMOGRAPHIC_SERVICE)) { + nhsNumberKey = NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC; } else if (endpoint.includes("participantmanagementdataservice") || endpoint.includes("CohortDistributionDataService")) { nhsNumberKey = "NHSNumber"; } else { - nhsNumberKey = config.nhsNumberKey; + nhsNumberKey = NHS_NUMBER_KEY; } nhsNumber = apiValidation.validations[nhsNumberKey]; @@ -53,13 +58,13 @@ export async function findMatchingObject(endpoint: string, responseBody: any[], matchingObjects = responseBody.filter((item: Record) => item[nhsNumberKey] == nhsNumber || - item.NhsNumber == nhsNumber || + item.NhsNumber == nhsNumber || item.NHSNumber == nhsNumber ); matchingObject = matchingObjects[matchingObjects.length - 1]; - if (endpoint.includes(config.exceptionManagementService) && + if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) && (apiValidation.validations.RuleId !== undefined || apiValidation.validations.RuleDescription)) { const ruleIdToFind = apiValidation.validations.RuleId; const ruleDescToFind = apiValidation.validations.RuleDescription; @@ -84,35 +89,7 @@ export async function validateFields(apiValidation: any, matchingObject: any, nh for (const [fieldName, expectedValue] of fieldsToValidate) { if (fieldName === "expectedCount") { - console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); - - let actualCount = 0; - if (matchingObjects && Array.isArray(matchingObjects)) { - actualCount = matchingObjects.length; - } else if (matchingObjects === null || matchingObjects === undefined) { - actualCount = 0; - console.warn(`⚠️ matchingObjects is ${matchingObjects === null ? 'null' : 'undefined'} for NHS Number ${nhsNumber}`); - } else { - actualCount = 1; - console.warn(`⚠️ matchingObjects is not an array for NHS Number ${nhsNumber}, treating as single object`); - } - - console.info(`📊 Actual count: ${actualCount}, Expected count: ${expectedValue} for NHS Number ${nhsNumber}`); - - const expectedCount = Number(expectedValue); - - if (isNaN(expectedCount)) { - throw new Error(`❌ expectedCount value '${expectedValue}' is not a valid number for NHS Number ${nhsNumber}`); - } - - // Perform the assertion - try { - expect(actualCount).toBe(expectedCount); - console.info(`✅ Count check completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); - } catch (error) { - console.error(`❌ Count check failed for NHS Number ${nhsNumber}: Expected ${expectedCount}, but got ${actualCount}`); - throw error; - } + assertOnCounts(matchingObject, nhsNumber, matchingObjects, fieldName, expectedValue); } // Handle NHS Number validation specially for 204 responses diff --git a/tests/playwright-tests/src/api/core/assertOnTypes.ts b/tests/playwright-tests/src/api/core/assertOnTypes.ts index 20682c5aa..e1f2f10b7 100644 --- a/tests/playwright-tests/src/api/core/assertOnTypes.ts +++ b/tests/playwright-tests/src/api/core/assertOnTypes.ts @@ -1,7 +1,7 @@ import { expect } from "@playwright/test"; export function assertOnCounts(matchingObject: any, nhsNumber: any, matchingObjects: any, fieldName: string, expectedValue: unknown) { - console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); + console.info(`🚧 Count check with expected value ${expectedValue} for NHS Number ${nhsNumber}`); let actualCount = 0; if (matchingObjects && Array.isArray(matchingObjects)) { diff --git a/tests/playwright-tests/src/config/env.ts b/tests/playwright-tests/src/config/env.ts index 7641adc1b..424816414 100644 --- a/tests/playwright-tests/src/config/env.ts +++ b/tests/playwright-tests/src/config/env.ts @@ -70,7 +70,7 @@ export const config = { apiWaitTime: 5000, maxNumberOfRetries: process.env.MAX_NUMBER_OF_RETRIES, timeBetweenRetriesInSeconds: process.env.TIME_BETWEEN_RETRIES_IN_SECONDS, - maxTimeBetweenRequests: 2500, + maxTimeBetweenRequests: 3500, nhsNumberKey: 'NHSNumber', nhsNumberKeyExceptionDemographic: 'NhsNumber', uniqueKeyCohortDistribution: 'CohortDistributionId', diff --git a/tests/playwright-tests/src/tests/api/testFiles/@DTOSS-5928-01/ADD-participantPayload.json b/tests/playwright-tests/src/tests/api/testFiles/@DTOSS-5928-01/ADD-participantPayload.json new file mode 100644 index 000000000..c46a7a022 --- /dev/null +++ b/tests/playwright-tests/src/tests/api/testFiles/@DTOSS-5928-01/ADD-participantPayload.json @@ -0,0 +1,14 @@ +{ + "inputParticipantRecord": { + "number": "CS0573846", + "u_case_variable_data": { + "nhs_number": "9997160908", + "forename_": "Jane", + "surname_family_name": "Doe", + "date_of_birth": "2010-10-22", + "enter_dummy_gp_code": "", + "BSO_code": "NORTH NOTTINGHAMSHIRE BREAST SCREENING OFFICE", + "reason_for_adding": "requires_ceasing_following_surgery_bilateral_mastectomy" + } + } +} diff --git a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts index 4215bd8f4..8c3156fcc 100644 --- a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts +++ b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts @@ -7,6 +7,7 @@ import { loadParticipantPayloads } from '../../fixtures/jsonDataReader'; test.describe('@DTOSS-3881-01 @e2e @epic4c- Cohort Manger subscribed the Added record with PDS', () => { let participantData: Record; + console.log("running from here"); test.beforeAll(async () => { const folderName = '@DTOSS-3881-01'; diff --git a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated.ts b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated.ts index 7c7745929..e21a7ac9e 100644 --- a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated.ts +++ b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated.ts @@ -26,10 +26,12 @@ // Epic-4c Epic : "https://nhsd-jira.digital.nhs.uk/browse/DTOSS-6041" has been tested as part of 3082 Epic 3 which are already has test coverage and track the migration of Epic 3 High Priority tests // The Epic 4c will be used to track the migration of all Epic 4c -export const runnerBasedEpic4cTestScenariosAdd = "@DTOSS-9337-01|@DTOSS-9750-01|@DTOSS-9750-02"; -export const runnerBasedEpic4cTestScenariosAmend = "@DTOSS-9337-01|@DTOSS-9750-02"; +//export const runnerBasedEpic4cTestScenariosAdd = "@DTOSS-9337-01|@DTOSS-9750-01|@DTOSS-9750-02"; +//export const runnerBasedEpic4cTestScenariosAmend = "@DTOSS-9337-01|@DTOSS-9750-02"; // export const runnerBasedEpic4cTestScenariosManualAdd = ""; -export const runnerBasedEpic4cTestScenariosManualAdd = "@DTOSS-3883-01|@DTOSS-8484-01|@DTOSS-9614-01|@DTOSS-8375-01|@DTOSS-3884-01|@DTOSS-9706-01|@DTOSS-9706-02|@DTOSS-3881-02"; -export const runnerBasedEpic4cTestScenariosManualAmend = "@DTOSS-8483-01|@DTOSS-9614-01|@DTOSS-9706-01|@DTOSS-3881-02"; + +//@DTOSS-3883-01 +export const runnerBasedEpic4cTestScenariosManualAdd = "@DTOSS-3883-01"/*@DTOSS-8484-01|@DTOSS-9614-01|@DTOSS-8375-01|@DTOSS-3884-01|@DTOSS-9706-01|@DTOSS-9706-02|@DTOSS-3881-02"; +export const runnerBasedEpic4cTestScenariosManualAmend = "@DTOSS-8483-01|@DTOSS-9614-01|@DTOSS-9706-01|@DTOSS-3881-02";*/ //export const runnerBasedEpic4cTestScenariosManualAmend = ""; diff --git a/tests/playwright-tests/src/tests/e2e/testFiles/@DTOSS-3883-01/ADDMANUAL1_1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json b/tests/playwright-tests/src/tests/e2e/testFiles/@DTOSS-3883-01/ADDMANUAL1_1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json index 85a160421..9d2dc2bca 100644 --- a/tests/playwright-tests/src/tests/e2e/testFiles/@DTOSS-3883-01/ADDMANUAL1_1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json +++ b/tests/playwright-tests/src/tests/e2e/testFiles/@DTOSS-3883-01/ADDMANUAL1_1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json @@ -10,85 +10,6 @@ "requirementJiraId": "@DTOSS-3883-01", "additionalTags": "@DTOSS-3883-01 @e2e @epic4c- Tests Listed Rule 2 validation is passed, Record exists in CohortDistribution" } - }, - { - "validations": { - "apiEndpoint": "api/ParticipantManagementDataService", - "RecordType": "ADD", - "NHSNumber": 9998380197, - "ReferralFlag": 1, - "ScreeningId": 1 - }, - "meta": { - "testJiraId": "@DTOSS-3883-02", - "requirementJiraId": "@DTOSS-3883-02", - "additionalTags": "@DTOSS-3883-02 @e2e @epic4c- Tests Record has screening ID = 1 and self referral flag is true" - } - }, - { - "validations": { - "apiEndpoint": "api/ParticipantManagementDataService", - "RecordType": "ADD", - "NHSNumber": 9992039310, - "ReferralFlag": 1, - "IsHigherRisk": 1, - "ScreeningId": 1 - }, - "meta": { - "testJiraId": "@DTOSS-3883-03", - "requirementJiraId": "@DTOSS-3883-03", - "additionalTags": "@DTOSS-3883-03 @e2e @epic4c- Tests VHR Record has screening ID = 1 and self referral flag is true and has IsHigherRIsk Set to true" - } - }, - { - "validations": { - "apiEndpoint": "api/CohortDistributionDataService", - "NHSNumber": 9992039310 - }, - "meta": { - "testJiraId": "@DTOSS-3883-04", - "requirementJiraId": "@DTOSS-3883-04", - "additionalTags": "@DTOSS-3883-04 @e2e @epic4c- Tests VHR Listed Rule 2 validation is passed, Record exists in CohortDistribution" - } - }, - { - "validations": { - "apiEndpoint": "api/ParticipantManagementDataService", - "RecordType": "ADD", - "NHSNumber": 9990237166, - "ReferralFlag": 1, - "ScreeningId": 1 - }, - "meta": { - "testJiraId": "@DTOSS-3883-05", - "requirementJiraId": "@DTOSS-3883-05", - "additionalTags": "@DTOSS-3883-06 @e2e @epic4c- Verify exception is raised when record fails to meet validation rules 2, Verify Participant exists in Participant Management" - } - }, - { - "validations": { - "apiEndpoint": "api/CohortDistributionDataService", - "NHSNumber": 9990237166, - "expectedCount": 0 - }, - "meta": { - "testJiraId": "@DTOSS-3883-06", - "requirementJiraId": "@DTOSS-3883-06", - "additionalTags": "@DTOSS-3883-06 @e2e @epic4c- Verify exception is raised when record fails to meet validation rules 2, Record does not exit exists in CohortDistribution" - } - }, - { - "validations": { - "apiEndpoint": "api/ExceptionManagementDataService", - "NhsNumber": "9990237166", - "RuleId": 30, - "RuleDescription": "Postcode invalid" - }, - "meta": { - "testJiraId": "@DTOSS-3883-07", - "requirementJiraId": "DTOSS-3883", - "additionalTags": "@DTOSS-3883-07 @e2e @epic4c- Verify exception is raised when record fails to meet validation rules 2, Record fails postcode validation logged in exception management" - } } ], "inputParticipantRecord": [ @@ -103,35 +24,9 @@ "BSO_code": "NORTH NOTTINGHAMSHIRE BREAST SCREENING OFFICE", "reason_for_adding": "requires_ceasing_following_surgery_bilateral_mastectomy" } - }, - { - "number": "CS0573848", - "u_case_variable_data": { - "nhs_number": "9992039310", - "forename_": "Anne", - "surname_family_name": "Smith", - "date_of_birth": "2010-10-22", - "enter_dummy_gp_code": "ZZZDKL", - "BSO_code": "NORTH NOTTINGHAMSHIRE BREAST SCREENING OFFICE", - "reason_for_adding": "eligible_for_very_high_risk_breast_screening_referral_form_must_be_attached" - } - }, - { - "number": "CS0573848", - "u_case_variable_data": { - "nhs_number": "9990237166", - "forename_": "Jane", - "surname_family_name": "Smith", - "date_of_birth": "1980-10-22", - "enter_dummy_gp_code": "ZZZDKL", - "BSO_code": "NORTH NOTTINGHAMSHIRE BREAST SCREENING OFFICE", - "reason_for_adding": "requires_ceasing_following_surgery_bilateral_mastectomy" - } } ], "nhsNumbers": [ - "9998380197", - "9992039310", - "9990237166" + "9998380197" ] } diff --git a/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts b/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts index 72f8ffe7a..891b19d48 100644 --- a/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts +++ b/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts @@ -11,6 +11,7 @@ import { runnerBasedEpic3MedTestScenariosAdd } from '../e2e/epic3-medpriority-te import { generateDynamicDateMap, replaceDynamicDatesInJson } from '../../../src/json/json-updater'; import { runnerBasedEpic4cTestScenariosAdd } from '../e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated'; import { runnerBasedEpic4dTestScenariosAdd } from '../e2e/epic4d-validation-tests/epic4d-6045-validation-testsuite-migrated'; +import { fail } from 'assert'; // Tests to run based on TEST_TYPE environment variable @@ -39,7 +40,8 @@ if (TEST_TYPE == 'RegressionEpic1') { if (!scopedTestScenario) { - throw new Error("No test scenario tags defined for the current TEST_TYPE. Please check the environment variable."); + console.error("No test scenario tags defined for the current TEST_TYPE. Please check the environment variable."); + fail; } let addData = getConsolidatedAllTestData(scopedTestScenario, "ADD"); diff --git a/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts b/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts index 06bdd85b9..2387df588 100644 --- a/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts +++ b/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts @@ -10,13 +10,38 @@ import { runnerBasedEpic3MedTestScenariosAmend } from '../e2e/epic3-medpriority- import { runnerBasedEpic4cTestScenariosAmend } from '../e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated'; import { runnerBasedEpic4dTestScenariosAmend } from '../e2e/epic4d-validation-tests/epic4d-6045-validation-testsuite-migrated'; import { generateDynamicDateMap, replaceDynamicDatesInJson } from '../../../src/json/json-updater'; - +import { fail } from 'assert'; +import { TIMEOUT } from 'dns'; // Tests to run based on TEST_TYPE environment variable + + let scopedTestScenario = ""; const TEST_TYPE = process.env.TEST_TYPE ?? 'SMOKE'; -if (TEST_TYPE == 'RegressionEpic1') { + +switch(TEST_TYPE) { + case 'RegressionEpic1': + scopedTestScenario = runnerBasedEpic1TestScenariosAmend; + break; + case 'RegressionEpic2': + scopedTestScenario = runnerBasedEpic2TestScenariosAmend; + break; + case 'RegressionEpic2Med': + scopedTestScenario = runnerBasedEpic2MedTestScenariosAmend; + case 'RegressionEpic3': + scopedTestScenario = runnerBasedEpic3TestScenariosAmend; + case 'RegressionEpic3Med': + scopedTestScenario = runnerBasedEpic3MedTestScenariosAmend; + case 'RegressionEpic4d': + scopedTestScenario = runnerBasedEpic4dTestScenariosAmend; + case 'RegressionEpic4c': + scopedTestScenario = runnerBasedEpic4cTestScenariosAmend; + default: + scopedTestScenario = runnerBasedEpic123TestScenariosAddAmend; + +} +/*if (TEST_TYPE == 'RegressionEpic1') { scopedTestScenario = runnerBasedEpic1TestScenariosAmend; } else if (TEST_TYPE == 'RegressionEpic2') { scopedTestScenario = runnerBasedEpic2TestScenariosAmend; @@ -32,10 +57,11 @@ if (TEST_TYPE == 'RegressionEpic1') { scopedTestScenario = runnerBasedEpic4cTestScenariosAmend; } else { scopedTestScenario = runnerBasedEpic123TestScenariosAddAmend; -} +}*/ if (!scopedTestScenario) { - throw new Error("No test scenario tags defined for the current TEST_TYPE. Please check the environment variable."); + console.error("No test scenario tags defined for the current TEST_TYPE. Please check the environment variable."); + fail; } let addData = getConsolidatedAllTestData(scopedTestScenario, "ADD"); @@ -43,6 +69,10 @@ let amendData = getConsolidatedAllTestData(scopedTestScenario, "AMENDED"); let apiContext: APIRequestContext; test.beforeAll(async () => { + setTimeout(() => { + console.log("running tests from here"); + }, 5000 + ); apiContext = await playwrightRequest.newContext(); console.log(`Running ${TEST_TYPE} tests with scenario tags: ${scopedTestScenario}`); await cleanupDatabaseFromAPI(apiContext, addData.nhsNumbers); @@ -77,3 +107,7 @@ amendData.validations.forEach((validations) => { await validateSqlDatabaseFromAPI(request, [validations]); }); }); + + + +const delay = (ms: number | undefined) => new Promise(res => setTimeout(res, ms)); \ No newline at end of file diff --git a/tests/playwright-tests/src/tests/steps/steps.ts b/tests/playwright-tests/src/tests/steps/steps.ts index 211012ccc..90bc97ab2 100644 --- a/tests/playwright-tests/src/tests/steps/steps.ts +++ b/tests/playwright-tests/src/tests/steps/steps.ts @@ -156,12 +156,30 @@ export function getConsolidatedAllTestData( scenarioFolderName: string, recordType: string = "ADD" ) { - const scenarioFolders = scenarioFolderName.split("|").map(name => name.trim()); + let testFilesPath: string = ""; let allValidations: any[] = []; let allInputParticipantRecords: any[] = []; let allNhsNumbers: any[] = []; let allServiceNowRequestValidations: any[] = []; + let scenarioFolders: string[] =[]; + + if(!scenarioFolderName) { + return { + validations: allValidations, + inputParticipantRecords: allInputParticipantRecords, + nhsNumbers: allNhsNumbers, + serviceNowRequestValidations: allServiceNowRequestValidations, + testFilesPath + } + } + + + if(scenarioFolderName.includes("|")) { + scenarioFolders = scenarioFolderName.split("|").map(name => name.trim()); + } else { + scenarioFolders[0] = scenarioFolderName; + } scenarioFolders.forEach(folder => { try { From 009671aedcf42d9b6adba6e4bbb1e82e1722db89 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Wed, 10 Sep 2025 09:23:29 +0100 Subject: [PATCH 10/13] fix: code clean up --- .../src/api/RetryCore/Retry.ts | 11 +- .../src/api/RetryCore/retryNew.ts | 140 -------------- .../src/api/RetryCore/retryOld.ts | 90 --------- tests/playwright-tests/src/api/apiHelper.ts | 180 +++--------------- .../epic4c-10011-10012-testsuite.spec.ts | 4 +- .../epic4c-testsuite-migrated.ts | 4 +- 6 files changed, 34 insertions(+), 395 deletions(-) delete mode 100644 tests/playwright-tests/src/api/RetryCore/retryNew.ts delete mode 100644 tests/playwright-tests/src/api/RetryCore/retryOld.ts diff --git a/tests/playwright-tests/src/api/RetryCore/Retry.ts b/tests/playwright-tests/src/api/RetryCore/Retry.ts index 816a8ed8d..2c8942554 100644 --- a/tests/playwright-tests/src/api/RetryCore/Retry.ts +++ b/tests/playwright-tests/src/api/RetryCore/Retry.ts @@ -3,14 +3,9 @@ import { config } from "../../config/env"; import { ApiResponse } from "../core/types"; import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; - - - - -const PARTICIPANT_DEMOGRAPHIC_SERVICE = config.participantDemographicDataService; const NHS_NUMBER_KEY = config.nhsNumberKey; const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; -const IGNORE_VALIDATION_KEY = config.ignoreValidationKey; + export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { let endpoint = ""; @@ -118,10 +113,6 @@ async function handleOKResponse(apiValidation: any, endpoint: string, response: return status; } - - - - export async function pollApiForOKResponse(httpRequest: () => Promise): Promise{ let apiResponse: ApiResponse | null = null; let i = 0; diff --git a/tests/playwright-tests/src/api/RetryCore/retryNew.ts b/tests/playwright-tests/src/api/RetryCore/retryNew.ts deleted file mode 100644 index 2b34b01c6..000000000 --- a/tests/playwright-tests/src/api/RetryCore/retryNew.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { APIResponse, expect } from "@playwright/test"; -import { config } from "../../config/env"; -//import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; - - -import { ApiResponse } from "../core/types"; -import { constants } from "buffer"; -import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; - -const NHS_NUMBER_KEY = config.nhsNumberKey; -const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; - -export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { - let endpoint = ""; - let errorTrace: any = undefined; - let status = false; - try { - for (const apiValidation of validationJson) { - endpoint = apiValidation.validations.apiEndpoint; - var res = await pollAPI(endpoint, apiValidation, request); - return res; - - } - } - catch (error) { - const errorMsg = `Endpoint: ${endpoint}, Error: ${error instanceof Error ? error.stack || error.message : error}`; - errorTrace = errorMsg; - console.error(errorMsg); - } - return {status, errorTrace }; -} - -async function handleOKResponse(apiValidation: any, endpoint: string, response: any ) : Promise{ - // Normal response handling (200, etc.) - expect(response.ok()).toBeTruthy(); - const responseBody = await response.json(); - expect(Array.isArray(responseBody)).toBeTruthy(); - const { matchingObject, nhsNumber, matchingObjects } = await findMatchingObject(endpoint, responseBody, apiValidation); - console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); - console.info(`From Response ${JSON.stringify(matchingObject, null, 2)}`); - let status = await validateFields(apiValidation, matchingObject, nhsNumber, matchingObjects); - - return status; -} - - -async function HandleNoContentResponse(expectedCount: number, apiValidation: any, endpoint: string): Promise { - if (expectedCount !== undefined && Number(expectedCount) === 0) { - console.info(`✅ Status 204: Expected 0 records for endpoint ${endpoint}`); - - // Get NHS number for validation - const nhsNumber = apiValidation.validations.NHSNumber || - apiValidation.validations.NhsNumber || - apiValidation.validations[NHS_NUMBER_KEY] || - apiValidation.validations[NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC]; - - console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); - console.info(`From Response: null (204 No Content - 0 records as expected)`); - let status = await validateFields(apiValidation, null, nhsNumber, []); - return status; - } else { - // 204 is unexpected, log error and return false to trigger retry - console.warn(`Status 204: No data found in the table using endpoint ${endpoint}`); - return false; - } -} - - -async function pollAPI(endpoint: string, apiValidation: any, request: any): Promise<{ apiResponse: APIResponse, status: boolean}> { - let apiResponse: APIResponse | null = null; - let i = 0; - - let maxNumberOfRetries = config.maxNumberOfRetries; - let maxTimeBetweenRequests = config.maxTimeBetweenRequests; - let status = false; - console.info(`now trying request for ${maxNumberOfRetries} retries`); - while (i < Number(maxNumberOfRetries)) { - apiResponse = await fetchApiResponse(endpoint, request); - switch(apiResponse.status()) { - case 204: - console.info("now handling no content response"); - const expectedCount = apiValidation.validations.expectedCount; - status = await HandleNoContentResponse(expectedCount, apiValidation, endpoint); - break; - case 200: - console.info("now handling OK response"); - status = await handleOKResponse(apiValidation, endpoint, apiResponse); - break; - default: - console.error("there was an error when handling response from "); - break; - } - console.log("api status code is: ", apiResponse.status()); - if(status) { - break; - } - i++; - - console.info(`http response completed ${i}/${maxNumberOfRetries} of number of retries`); - await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); - } - - - if (!apiResponse) { - throw new Error("apiResponse was never assigned"); - } - - return {apiResponse, status}; -} - - -export async function pollApiForOKResponse(httpRequest: () => Promise): Promise{ - let apiResponse: ApiResponse | null = null; - let i = 0; - let maxNumberOfRetries = config.maxNumberOfRetries; - let maxTimeBetweenRequests = config.maxTimeBetweenRequests; - - console.info(`now trying request for ${maxNumberOfRetries} retries`); - while (i < Number(maxNumberOfRetries)) { - try { - apiResponse = await httpRequest(); - if (apiResponse.status == 200) { - console.info("200 response found") - break; - } - } - catch(exception) { - console.error("Error reading request body:", exception); - } - i++; - - console.info(`http response completed ${i}/${maxNumberOfRetries} of number of retries`); - await new Promise(res => setTimeout(res, maxTimeBetweenRequests)); - } - - if (!apiResponse) { - throw new Error("apiResponse was never assigned"); - } - return apiResponse; -}; diff --git a/tests/playwright-tests/src/api/RetryCore/retryOld.ts b/tests/playwright-tests/src/api/RetryCore/retryOld.ts deleted file mode 100644 index bffeeeef7..000000000 --- a/tests/playwright-tests/src/api/RetryCore/retryOld.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { APIResponse, expect } from "@playwright/test"; - -import { config } from "../config/env"; -import { fetchApiResponse, findMatchingObject, validateFields } from "../apiHelper"; - -const apiRetry = Number(config.apiRetry); -const initialWaitTime = Number(config.apiWaitTime) || 2000; -const endpointCohortDistributionDataService = config.endpointCohortDistributionDataService; -const endpointParticipantManagementDataService = config.endpointParticipantManagementDataService; -const endpointExceptionManagementDataService = config.endpointExceptionManagementDataService; -const endpointParticipantDemographicDataService = config.endpointParticipantDemographicDataService; - -const COHORT_DISTRIBUTION_SERVICE = config.cohortDistributionService; -const PARTICIPANT_MANAGEMENT_SERVICE = config.participantManagementService; -const EXCEPTION_MANAGEMENT_SERVICE = config.exceptionManagementService; -const PARTICIPANT_DEMOGRAPHIC_SERVICE = config.participantDemographicDataService; -const NHS_NUMBER_KEY = config.nhsNumberKey; -const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; -const IGNORE_VALIDATION_KEY = config.ignoreValidationKey; - -let waitTime = initialWaitTime; -let response: APIResponse; - -export async function validateApiResponse(validationJson: any, request: any): Promise<{ status: boolean; errorTrace?: any }> { - - -let status = false; - let endpoint = ""; - let errorTrace: any = undefined; - - for (let attempt = 1; attempt <= apiRetry; attempt++) { - if (status) break; - - try { - for (const apiValidation of validationJson) { - endpoint = apiValidation.validations.apiEndpoint; - response = await fetchApiResponse(endpoint, request); - - // Handle 204 No Content responses BEFORE parsing JSON - if (response.status() === 204) { - // Check if 204 is expected (expectedCount: 0) - const expectedCount = apiValidation.validations.expectedCount; - - if (expectedCount !== undefined && Number(expectedCount) === 0) { - console.info(`✅ Status 204: Expected 0 records for endpoint ${endpoint}`); - - // Get NHS number for validation - const nhsNumber = apiValidation.validations.NHSNumber || - apiValidation.validations.NhsNumber || - apiValidation.validations[NHS_NUMBER_KEY] || - apiValidation.validations[NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC]; - - console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); - console.info(`From Response: null (204 No Content - 0 records as expected)`); - status = await validateFields(apiValidation, null, nhsNumber, []); - } else { - // 204 is unexpected, throw error to trigger retry - throw new Error(`Status 204: No data found in the table using endpoint ${endpoint}`); - } - } else { - // Normal response handling (200, etc.) - expect(response.ok()).toBeTruthy(); - const responseBody = await response.json(); - expect(Array.isArray(responseBody)).toBeTruthy(); - const { matchingObject, nhsNumber, matchingObjects } = await findMatchingObject(endpoint, responseBody, apiValidation); - console.info(`Validating fields using 🅰️\t🅿️\tℹ️\t ${endpoint}`); - console.info(`From Response ${JSON.stringify(matchingObject, null, 2)}`); - status = await validateFields(apiValidation, matchingObject, nhsNumber, matchingObjects); - } - } - } catch (error) { - const errorMsg = `Endpoint: ${endpoint}, Status: ${response?.status?.()}, Error: ${error instanceof Error ? error.stack || error.message : error}`; - errorTrace = errorMsg; - if (response?.status?.() === 204) { - console.info(`ℹ️\t Status 204: No data found in the table using endpoint ${endpoint}`); - } - } - - if (attempt < apiRetry && !status) { - console.info(`🚧 Function processing in progress; will check again using data service ${endpoint} in ${Math.round(waitTime / 1000)} seconds...`); - await delayRetry(); - } - } - waitTime = Number(config.apiWaitTime); - return { status, errorTrace }; -} -function delayRetry() { - throw new Error("Function not implemented."); -} - diff --git a/tests/playwright-tests/src/api/apiHelper.ts b/tests/playwright-tests/src/api/apiHelper.ts index d02d6f44d..2dbaa723c 100644 --- a/tests/playwright-tests/src/api/apiHelper.ts +++ b/tests/playwright-tests/src/api/apiHelper.ts @@ -1,14 +1,9 @@ -import { APIResponse, expect } from "@playwright/test"; +import { APIResponse } from "@playwright/test"; import { config } from "../config/env"; import { assertOnCounts, assertOnRecordDateTimes, assertOnNhsNumber, MatchDynamicType, MatchOnRuleDescriptionDynamic } from "./core/assertOnTypes"; -const EXCEPTION_MANAGEMENT_SERVICE = config.exceptionManagementService; -const PARTICIPANT_DEMOGRAPHIC_SERVICE = config.participantDemographicDataService; -const NHS_NUMBER_KEY = config.nhsNumberKey; -const NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC = config.nhsNumberKeyExceptionDemographic; -const IGNORE_VALIDATION_KEY = config.ignoreValidationKey; export async function fetchApiResponse(endpoint: string, request: any): Promise { let currentEndPoint = endpoint.toLowerCase() @@ -36,14 +31,13 @@ export async function findMatchingObject(endpoint: string, responseBody: any[], let matchingObjects: any[] = []; let matchingObject: any; - let nhsNumberKey; - if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) || endpoint.includes(PARTICIPANT_DEMOGRAPHIC_SERVICE)) { - nhsNumberKey = NHS_NUMBER_KEY_EXCEPTION_DEMOGRAPHIC; + if (endpoint.includes(config.exceptionManagementService) || endpoint.includes(config.participantDemographicDataService)) { + nhsNumberKey = config.nhsNumberKeyExceptionDemographic; } else if (endpoint.includes("participantmanagementdataservice") || endpoint.includes("CohortDistributionDataService")) { nhsNumberKey = "NHSNumber"; } else { - nhsNumberKey = NHS_NUMBER_KEY; + nhsNumberKey = config.nhsNumberKey; } nhsNumber = apiValidation.validations[nhsNumberKey]; @@ -58,25 +52,25 @@ export async function findMatchingObject(endpoint: string, responseBody: any[], matchingObjects = responseBody.filter((item: Record) => item[nhsNumberKey] == nhsNumber || - item.NhsNumber == nhsNumber || + item.NhsNumber == nhsNumber || item.NHSNumber == nhsNumber ); matchingObject = matchingObjects[matchingObjects.length - 1]; - if (endpoint.includes(EXCEPTION_MANAGEMENT_SERVICE) && + if (endpoint.includes(config.exceptionManagementService) && (apiValidation.validations.RuleId !== undefined || apiValidation.validations.RuleDescription)) { const ruleIdToFind = apiValidation.validations.RuleId; const ruleDescToFind = apiValidation.validations.RuleDescription; - const betterMatches = matchingObjects.filter(record => + let betterMatches = matchingObjects.filter(record => (ruleIdToFind === undefined || record.RuleId === ruleIdToFind) && (ruleDescToFind === undefined || record.RuleDescription === ruleDescToFind) ); if (betterMatches.length > 0) { matchingObject = betterMatches[0]; - console.log(`Found better matching record with NHS Number ${nhsNumber} and RuleId ${ruleIdToFind || 'any'}`); + console.info(`Found better matching record with NHS Number ${nhsNumber} and RuleId ${ruleIdToFind || 'any'}`); } } @@ -85,152 +79,36 @@ export async function findMatchingObject(endpoint: string, responseBody: any[], export async function validateFields(apiValidation: any, matchingObject: any, nhsNumber: any, matchingObjects: any): Promise { - const fieldsToValidate = Object.entries(apiValidation.validations).filter(([key]) => key !== IGNORE_VALIDATION_KEY); + const fieldsToValidate = Object.entries(apiValidation.validations).filter(([key]) => key !== config.ignoreValidationKey); for (const [fieldName, expectedValue] of fieldsToValidate) { - if (fieldName === "expectedCount") { - assertOnCounts(matchingObject, nhsNumber, matchingObjects, fieldName, expectedValue); - } - - // Handle NHS Number validation specially for 204 responses - else if ((fieldName === "NHSNumber" || fieldName === "NhsNumber") && !matchingObject) { - console.info(`🚧 Validating NHS Number field ${fieldName} for 204 response`); - - // For 204 responses, validate that we searched for the correct NHS number - const expectedNhsNumber = Number(expectedValue); - const actualNhsNumber = Number(nhsNumber); - - try { - expect(actualNhsNumber).toBe(expectedNhsNumber); - console.info(`✅ NHS Number validation completed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); - } catch (error) { - console.error(`❌ NHS Number validation failed: searched for ${actualNhsNumber}, expected ${expectedNhsNumber}`); - throw error; - } - } - - else if (fieldName === 'RecordInsertDateTime' || fieldName === 'RecordUpdateDateTime') { - console.info(`🚧 Validating timestamp field ${fieldName} for NHS Number ${nhsNumber}`); - - if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { - throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); - } - - if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { - console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); - continue; - } - - expect(matchingObject).toHaveProperty(fieldName); - const actualValue = matchingObject[fieldName]; - - if (typeof expectedValue === 'string' && expectedValue.startsWith('PATTERN:')) { - const pattern = expectedValue.substring('PATTERN:'.length); - console.info(`Validating timestamp against pattern: ${pattern}`); - - const formatMatch = validateTimestampFormat(actualValue, pattern); - - if (formatMatch) { - console.info(`✅ Timestamp matches pattern for ${fieldName}`); - } else { - console.error(`❌ Timestamp doesn't match pattern for ${fieldName}`); - expect(formatMatch).toBe(true); - } - } else { - if (expectedValue === actualValue) { - console.info(`✅ Timestamp exact match for ${fieldName}`); - } else { - try { - const expectedDate = new Date(expectedValue as string); - const actualDate = new Date(actualValue); - - const expectedTimeWithoutMs = new Date(expectedDate); - expectedTimeWithoutMs.setMilliseconds(0); - const actualTimeWithoutMs = new Date(actualDate); - actualTimeWithoutMs.setMilliseconds(0); - - if (expectedTimeWithoutMs.getTime() === actualTimeWithoutMs.getTime()) { - console.info(`✅ Timestamp matches (ignoring milliseconds) for ${fieldName}`); - } else { - const timeDiff = Math.abs(expectedDate.getTime() - actualDate.getTime()); - const oneMinute = 60 * 1000; - - if (timeDiff <= oneMinute) { - console.info(`✅ Timestamp within acceptable range (±1 minute) for ${fieldName}`); - } else { - expect(actualValue).toBe(expectedValue); - } - } - } catch (e) { - console.error(`Error validating timestamp: ${e}`); - expect(actualValue).toBe(expectedValue); - } + switch(fieldName.toLowerCase()){ + case "expectedcount": + assertOnCounts(matchingObject, nhsNumber, matchingObjects, fieldName, expectedValue); + break; + case "nhsnumber": + if(!matchingObject) { + assertOnNhsNumber(expectedValue, nhsNumber); } - } - - console.info(`✅ Validation completed for timestamp field ${fieldName} for NHS Number ${nhsNumber}`); - } - - // ✅ Custom dynamic rule description handling - else if (fieldName === 'RuleDescriptionDynamic') { - const actualValue = matchingObject['RuleDescription']; - console.info(`Actual RuleDescription: "${actualValue}"`); - - // Regex based on message requirement - const dynamicPattern = /Unable to add to cohort distribution\. As participant \d+ has triggered a validation exception/; - - try { - expect(actualValue).toMatch(dynamicPattern); - console.info(`✅ Dynamic message validation passed for NHS Number ${nhsNumber}`); - } catch (error) { - console.info(`❌ Dynamic message validation failed!`); - throw error; - } - } - - else { - console.info(`🚧 Validating field ${fieldName} with expected value ${expectedValue} for NHS Number ${nhsNumber}`); - - if (!matchingObject && expectedValue !== null && expectedValue !== undefined) { - throw new Error(`❌ No matching object found for NHS Number ${nhsNumber} but expected to validate field ${fieldName}`); - } - - if (!matchingObject && (expectedValue === null || expectedValue === undefined)) { - console.info(`ℹ️ Skipping validation for ${fieldName} as no matching object found and no expected value for NHS Number ${nhsNumber}`); - continue; - } - - expect(matchingObject).toHaveProperty(fieldName); - expect(matchingObject[fieldName]).toBe(expectedValue); - console.info(`✅ Validation completed for field ${fieldName} with value ${expectedValue} for NHS Number ${nhsNumber}`); + break; + case "recordinsertdatetime": + assertOnRecordDateTimes(fieldName, expectedValue, nhsNumber, matchingObject); + break; + case "recordUpdatedatetime": + assertOnRecordDateTimes(fieldName, expectedValue, nhsNumber, matchingObject); + break; + case "ruledescriptiondynamic": + MatchOnRuleDescriptionDynamic(matchingObject, nhsNumber); + break; + default: + MatchDynamicType(matchingObject, nhsNumber, expectedValue, fieldName); + break; } } return true; } -function validateTimestampFormat(timestamp: string, pattern: string): boolean { - if (!timestamp) return false; - - console.info(`Actual timestamp: ${timestamp}`); - - - if (pattern === 'yyyy-MM-ddTHH:mm:ss' || pattern === 'yyyy-MM-ddTHH:mm:ss.SSS') { - - return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(timestamp); - } - else if (pattern === 'yyyy-MM-dd') { - - return /^\d{4}-\d{2}-\d{2}$/.test(timestamp); - } - else { - - return !isNaN(new Date(timestamp).getTime()); - } -} - - - export async function checkMappingsByIndex( original: Array<{ requestId: string; nhsNumber: string }>, shifted: Array<{ requestId: string; nhsNumber: string }> diff --git a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts index e27065556..22436cdcb 100644 --- a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts +++ b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-10011-10012-testsuite.spec.ts @@ -60,7 +60,7 @@ test.describe.serial('@DTOSS-3881-01 @e2e @epic4c- Cohort Manger subscribed the if (!success) { throw new Error('Participant response data was not available after 10 retries (20 seconds).'); } - expect(response?.data?.[0]?.NhsNumber).toBe(9997160908); + expect(response?.data?.[0]?.NhsNumber).toBe(9998380197); expect(response?.data?.[0]?.GivenName).toBe(givenName); expect(response?.data?.[0]?.FamilyName).toBe(familyName); }); @@ -81,7 +81,7 @@ test.describe.serial('@DTOSS-3881-01 @e2e @epic4c- Cohort Manger subscribed the if (!success) { throw new Error('Participant response data was not available after 10 retries (20 seconds).'); } - expect(response?.data?.[0]?.NHSNumber).toBe(9997160908); + expect(response?.data?.[0]?.NHSNumber).toBe(9998380197); expect(response?.data?.[0]?.ScreeningId).toBe(1); expect(response?.data?.[0]?.ReferralFlag).toBe(1); }); diff --git a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated.ts b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated.ts index 75da8d9fe..d91c9eec3 100644 --- a/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated.ts +++ b/tests/playwright-tests/src/tests/e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated.ts @@ -30,6 +30,6 @@ //export const runnerBasedEpic4cTestScenariosAmend = "@DTOSS-9337-01|@DTOSS-9750-02"; // export const runnerBasedEpic4cTestScenariosManualAdd = ""; -export const runnerBasedEpic4cTestScenariosManualAdd = "@DTOSS-3883-01|@DTOSS-8484-01|@DTOSS-9614-01|@DTOSS-8375-01|@DTOSS-3884-01|@DTOSS-9706-01|@DTOSS-9706-02|@DTOSS-3881-02|@DTOSS-8425-01|@DTOSS-8569-01|@DTOSS-8569-02|@DTOSS-8569-03|@DTOSS-8569-04|@DTOSS-8569-05"; -export const runnerBasedEpic4cTestScenariosManualAmend = "@DTOSS-8483-01|@DTOSS-9614-01|@DTOSS-9706-01|@DTOSS-3881-02"; +export const runnerBasedEpic4cTestScenariosManualAdd = "@DTOSS-3883-01";//|@DTOSS-8484-01|@DTOSS-9614-01|@DTOSS-8375-01|@DTOSS-3884-01|@DTOSS-9706-01|@DTOSS-9706-02|@DTOSS-3881-02|@DTOSS-8425-01|@DTOSS-8569-01|@DTOSS-8569-02|@DTOSS-8569-03|@DTOSS-8569-04|@DTOSS-8569-05"; +//export const runnerBasedEpic4cTestScenariosManualAmend = "@DTOSS-8483-01|@DTOSS-9614-01|@DTOSS-9706-01|@DTOSS-3881-02"; //export const runnerBasedEpic4cTestScenariosManualAmend = ""; From e7fa30ea5c3a5363897193f4c7d5878db91dd55a Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Thu, 11 Sep 2025 14:31:56 +0100 Subject: [PATCH 11/13] fix: adding tests back in --- ...1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json | 107 +++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/tests/playwright-tests/src/tests/e2e/testFiles/@DTOSS-3883-01/ADDMANUAL1_1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json b/tests/playwright-tests/src/tests/e2e/testFiles/@DTOSS-3883-01/ADDMANUAL1_1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json index 9d2dc2bca..85a160421 100644 --- a/tests/playwright-tests/src/tests/e2e/testFiles/@DTOSS-3883-01/ADDMANUAL1_1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json +++ b/tests/playwright-tests/src/tests/e2e/testFiles/@DTOSS-3883-01/ADDMANUAL1_1B8F53_-_CAAS_BREAST_SCREENING_COHORT.json @@ -10,6 +10,85 @@ "requirementJiraId": "@DTOSS-3883-01", "additionalTags": "@DTOSS-3883-01 @e2e @epic4c- Tests Listed Rule 2 validation is passed, Record exists in CohortDistribution" } + }, + { + "validations": { + "apiEndpoint": "api/ParticipantManagementDataService", + "RecordType": "ADD", + "NHSNumber": 9998380197, + "ReferralFlag": 1, + "ScreeningId": 1 + }, + "meta": { + "testJiraId": "@DTOSS-3883-02", + "requirementJiraId": "@DTOSS-3883-02", + "additionalTags": "@DTOSS-3883-02 @e2e @epic4c- Tests Record has screening ID = 1 and self referral flag is true" + } + }, + { + "validations": { + "apiEndpoint": "api/ParticipantManagementDataService", + "RecordType": "ADD", + "NHSNumber": 9992039310, + "ReferralFlag": 1, + "IsHigherRisk": 1, + "ScreeningId": 1 + }, + "meta": { + "testJiraId": "@DTOSS-3883-03", + "requirementJiraId": "@DTOSS-3883-03", + "additionalTags": "@DTOSS-3883-03 @e2e @epic4c- Tests VHR Record has screening ID = 1 and self referral flag is true and has IsHigherRIsk Set to true" + } + }, + { + "validations": { + "apiEndpoint": "api/CohortDistributionDataService", + "NHSNumber": 9992039310 + }, + "meta": { + "testJiraId": "@DTOSS-3883-04", + "requirementJiraId": "@DTOSS-3883-04", + "additionalTags": "@DTOSS-3883-04 @e2e @epic4c- Tests VHR Listed Rule 2 validation is passed, Record exists in CohortDistribution" + } + }, + { + "validations": { + "apiEndpoint": "api/ParticipantManagementDataService", + "RecordType": "ADD", + "NHSNumber": 9990237166, + "ReferralFlag": 1, + "ScreeningId": 1 + }, + "meta": { + "testJiraId": "@DTOSS-3883-05", + "requirementJiraId": "@DTOSS-3883-05", + "additionalTags": "@DTOSS-3883-06 @e2e @epic4c- Verify exception is raised when record fails to meet validation rules 2, Verify Participant exists in Participant Management" + } + }, + { + "validations": { + "apiEndpoint": "api/CohortDistributionDataService", + "NHSNumber": 9990237166, + "expectedCount": 0 + }, + "meta": { + "testJiraId": "@DTOSS-3883-06", + "requirementJiraId": "@DTOSS-3883-06", + "additionalTags": "@DTOSS-3883-06 @e2e @epic4c- Verify exception is raised when record fails to meet validation rules 2, Record does not exit exists in CohortDistribution" + } + }, + { + "validations": { + "apiEndpoint": "api/ExceptionManagementDataService", + "NhsNumber": "9990237166", + "RuleId": 30, + "RuleDescription": "Postcode invalid" + }, + "meta": { + "testJiraId": "@DTOSS-3883-07", + "requirementJiraId": "DTOSS-3883", + "additionalTags": "@DTOSS-3883-07 @e2e @epic4c- Verify exception is raised when record fails to meet validation rules 2, Record fails postcode validation logged in exception management" + } } ], "inputParticipantRecord": [ @@ -24,9 +103,35 @@ "BSO_code": "NORTH NOTTINGHAMSHIRE BREAST SCREENING OFFICE", "reason_for_adding": "requires_ceasing_following_surgery_bilateral_mastectomy" } + }, + { + "number": "CS0573848", + "u_case_variable_data": { + "nhs_number": "9992039310", + "forename_": "Anne", + "surname_family_name": "Smith", + "date_of_birth": "2010-10-22", + "enter_dummy_gp_code": "ZZZDKL", + "BSO_code": "NORTH NOTTINGHAMSHIRE BREAST SCREENING OFFICE", + "reason_for_adding": "eligible_for_very_high_risk_breast_screening_referral_form_must_be_attached" + } + }, + { + "number": "CS0573848", + "u_case_variable_data": { + "nhs_number": "9990237166", + "forename_": "Jane", + "surname_family_name": "Smith", + "date_of_birth": "1980-10-22", + "enter_dummy_gp_code": "ZZZDKL", + "BSO_code": "NORTH NOTTINGHAMSHIRE BREAST SCREENING OFFICE", + "reason_for_adding": "requires_ceasing_following_surgery_bilateral_mastectomy" + } } ], "nhsNumbers": [ - "9998380197" + "9998380197", + "9992039310", + "9990237166" ] } From 0b69ff44c810403a8b0e985732841d1124ac9be4 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Thu, 11 Sep 2025 16:15:31 +0100 Subject: [PATCH 12/13] fix: creating pick test types function --- .../src/tests/runner/TestTypePicker.ts | 34 ++++++++++++ .../tests/runner/runner-workflow-add.spec.ts | 32 ++---------- .../runner/runner-workflow-amend.spec.ts | 52 ++----------------- 3 files changed, 42 insertions(+), 76 deletions(-) create mode 100644 tests/playwright-tests/src/tests/runner/TestTypePicker.ts diff --git a/tests/playwright-tests/src/tests/runner/TestTypePicker.ts b/tests/playwright-tests/src/tests/runner/TestTypePicker.ts new file mode 100644 index 000000000..585b22ab7 --- /dev/null +++ b/tests/playwright-tests/src/tests/runner/TestTypePicker.ts @@ -0,0 +1,34 @@ +import { fail } from "assert"; +import { runnerBasedEpic1TestScenariosAmend } from "../e2e/epic1-highpriority-tests/epic1-high-priority-testsuite-migrated"; +import { runnerBasedEpic123TestScenariosAddAmend } from "../e2e/epic123-smoke-tests/epic123-smoke-tests-migrated"; +import { runnerBasedEpic2TestScenariosAmend } from "../e2e/epic2-highpriority-tests/epic2-high-priority-testsuite-migrated"; +import { runnerBasedEpic2MedTestScenariosAmend } from "../e2e/epic2-medpriority-tests/epic2-med-priority-testsuite-migrated"; +import { runnerBasedEpic3TestScenariosAmend } from "../e2e/epic3-highpriority-tests/epic3-high-priority-testsuite-migrated"; +import { runnerBasedEpic3MedTestScenariosAmend } from "../e2e/epic3-medpriority-tests/epic3-med-priority-testsuite-migrated"; +import { runnerBasedEpic4dTestScenariosAmend } from "../e2e/epic4d-validation-tests/epic4d-6045-validation-testsuite-migrated"; + + +export function TestTypePicker( + TEST_TYPE: string +) +{ + switch(TEST_TYPE) { + case 'RegressionEpic1': + return runnerBasedEpic1TestScenariosAmend; + case 'RegressionEpic2': + return runnerBasedEpic2TestScenariosAmend; + case 'RegressionEpic2Med': + return runnerBasedEpic2MedTestScenariosAmend; + case 'RegressionEpic3': + return runnerBasedEpic3TestScenariosAmend; + case 'RegressionEpic3Med': + return runnerBasedEpic3MedTestScenariosAmend; + case 'RegressionEpic4d': + return runnerBasedEpic4dTestScenariosAmend; + case 'RegressionEpic4c': + return runnerBasedEpic4dTestScenariosAmend; + default: + return runnerBasedEpic123TestScenariosAddAmend; + } + +} diff --git a/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts b/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts index 891b19d48..9dbd6db75 100644 --- a/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts +++ b/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts @@ -1,43 +1,17 @@ import { test, request as playwrightRequest, APIRequestContext } from '@playwright/test' import { cleanupDatabaseFromAPI, getConsolidatedAllTestData, processFileViaStorage, validateSqlDatabaseFromAPI } from '../steps/steps'; import { createParquetFromJson } from '../../parquet/parquet-multiplier'; -import { runnerBasedEpic123TestScenariosAdd } from '../e2e/epic123-smoke-tests/epic123-smoke-tests-migrated'; -import { runnerBasedEpic1TestScenariosAdd } from '../e2e/epic1-highpriority-tests/epic1-high-priority-testsuite-migrated'; -import { runnerBasedEpic1MedTestScenariosAdd } from '../e2e/epic1-medpriority-tests/epic1-med-priority-testsuite-migrated'; -import { runnerBasedEpic2TestScenariosAdd } from '../e2e/epic2-highpriority-tests/epic2-high-priority-testsuite-migrated'; -import { runnerBasedEpic2MedTestScenariosAdd } from '../e2e/epic2-medpriority-tests/epic2-med-priority-testsuite-migrated'; -import { runnerBasedEpic3TestScenariosAdd } from '../e2e/epic3-highpriority-tests/epic3-high-priority-testsuite-migrated'; -import { runnerBasedEpic3MedTestScenariosAdd } from '../e2e/epic3-medpriority-tests/epic3-med-priority-testsuite-migrated'; import { generateDynamicDateMap, replaceDynamicDatesInJson } from '../../../src/json/json-updater'; -import { runnerBasedEpic4cTestScenariosAdd } from '../e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated'; -import { runnerBasedEpic4dTestScenariosAdd } from '../e2e/epic4d-validation-tests/epic4d-6045-validation-testsuite-migrated'; import { fail } from 'assert'; +import { TestTypePicker } from './TestTypePicker'; // Tests to run based on TEST_TYPE environment variable +const TEST_TYPE = process.env.TEST_TYPE ?? 'SMOKE'; let scopedTestScenario = ""; -const TEST_TYPE = process.env.TEST_TYPE ?? 'SMOKE'; -if (TEST_TYPE == 'RegressionEpic1') { - scopedTestScenario = runnerBasedEpic1TestScenariosAdd; -} else if (TEST_TYPE == 'RegressionEpic1Med') { - scopedTestScenario = runnerBasedEpic1MedTestScenariosAdd; -} else if (TEST_TYPE == 'RegressionEpic2') { - scopedTestScenario = runnerBasedEpic2TestScenariosAdd; -} else if (TEST_TYPE == 'RegressionEpic2Med') { - scopedTestScenario = runnerBasedEpic2MedTestScenariosAdd; -} else if (TEST_TYPE == 'RegressionEpic3') { - scopedTestScenario = runnerBasedEpic3TestScenariosAdd; -} else if (TEST_TYPE == 'RegressionEpic3Med') { - scopedTestScenario = runnerBasedEpic3MedTestScenariosAdd; -} else if (TEST_TYPE == 'RegressionEpic4d') { - scopedTestScenario = runnerBasedEpic4dTestScenariosAdd; -} else if (TEST_TYPE == 'RegressionEpic4c') { - scopedTestScenario = runnerBasedEpic4cTestScenariosAdd; -} else { - scopedTestScenario = runnerBasedEpic123TestScenariosAdd; -} +scopedTestScenario = TestTypePicker(TEST_TYPE) if (!scopedTestScenario) { console.error("No test scenario tags defined for the current TEST_TYPE. Please check the environment variable."); diff --git a/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts b/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts index 2387df588..13860bcdf 100644 --- a/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts +++ b/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts @@ -1,64 +1,22 @@ import { test, request as playwrightRequest, APIRequestContext } from '@playwright/test' import { createParquetFromJson } from '../../parquet/parquet-multiplier'; import { cleanupDatabaseFromAPI, getConsolidatedAllTestData, processFileViaStorage, validateSqlDatabaseFromAPI } from '../steps/steps'; -import { runnerBasedEpic123TestScenariosAddAmend } from '../e2e/epic123-smoke-tests/epic123-smoke-tests-migrated'; -import { runnerBasedEpic1TestScenariosAmend } from '../e2e/epic1-highpriority-tests/epic1-high-priority-testsuite-migrated'; -import { runnerBasedEpic2TestScenariosAmend } from '../e2e/epic2-highpriority-tests/epic2-high-priority-testsuite-migrated'; -import { runnerBasedEpic2MedTestScenariosAmend } from '../e2e/epic2-medpriority-tests/epic2-med-priority-testsuite-migrated'; -import { runnerBasedEpic3TestScenariosAmend } from '../e2e/epic3-highpriority-tests/epic3-high-priority-testsuite-migrated'; -import { runnerBasedEpic3MedTestScenariosAmend } from '../e2e/epic3-medpriority-tests/epic3-med-priority-testsuite-migrated'; -import { runnerBasedEpic4cTestScenariosAmend } from '../e2e/epic4c-add-participant-tests/epic4c-testsuite-migrated'; -import { runnerBasedEpic4dTestScenariosAmend } from '../e2e/epic4d-validation-tests/epic4d-6045-validation-testsuite-migrated'; import { generateDynamicDateMap, replaceDynamicDatesInJson } from '../../../src/json/json-updater'; import { fail } from 'assert'; -import { TIMEOUT } from 'dns'; +import { TestTypePicker } from './TestTypePicker'; // Tests to run based on TEST_TYPE environment variable let scopedTestScenario = ""; - const TEST_TYPE = process.env.TEST_TYPE ?? 'SMOKE'; -switch(TEST_TYPE) { - case 'RegressionEpic1': - scopedTestScenario = runnerBasedEpic1TestScenariosAmend; - break; - case 'RegressionEpic2': - scopedTestScenario = runnerBasedEpic2TestScenariosAmend; - break; - case 'RegressionEpic2Med': - scopedTestScenario = runnerBasedEpic2MedTestScenariosAmend; - case 'RegressionEpic3': - scopedTestScenario = runnerBasedEpic3TestScenariosAmend; - case 'RegressionEpic3Med': - scopedTestScenario = runnerBasedEpic3MedTestScenariosAmend; - case 'RegressionEpic4d': - scopedTestScenario = runnerBasedEpic4dTestScenariosAmend; - case 'RegressionEpic4c': - scopedTestScenario = runnerBasedEpic4cTestScenariosAmend; - default: - scopedTestScenario = runnerBasedEpic123TestScenariosAddAmend; +scopedTestScenario = TestTypePicker(TEST_TYPE) +if (!scopedTestScenario) { + console.error("No test scenario tags defined for the current TEST_TYPE. Please check the environment variable."); + fail; } -/*if (TEST_TYPE == 'RegressionEpic1') { - scopedTestScenario = runnerBasedEpic1TestScenariosAmend; -} else if (TEST_TYPE == 'RegressionEpic2') { - scopedTestScenario = runnerBasedEpic2TestScenariosAmend; -} else if (TEST_TYPE == 'RegressionEpic2Med') { - scopedTestScenario = runnerBasedEpic2MedTestScenariosAmend; -} else if (TEST_TYPE == 'RegressionEpic3') { - scopedTestScenario = runnerBasedEpic3TestScenariosAmend; -} else if (TEST_TYPE == 'RegressionEpic3Med') { - scopedTestScenario = runnerBasedEpic3MedTestScenariosAmend; -} else if (TEST_TYPE == 'RegressionEpic4d') { - scopedTestScenario = runnerBasedEpic4dTestScenariosAmend; -} else if (TEST_TYPE == 'RegressionEpic4c') { - scopedTestScenario = runnerBasedEpic4cTestScenariosAmend; -} else { - scopedTestScenario = runnerBasedEpic123TestScenariosAddAmend; -}*/ - if (!scopedTestScenario) { console.error("No test scenario tags defined for the current TEST_TYPE. Please check the environment variable."); fail; From fe6b06122d8b22032ff5e0114571692c3112968f Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Mon, 15 Sep 2025 10:37:52 +0100 Subject: [PATCH 13/13] fix: removing unwanted code --- .../src/tests/runner/runner-workflow-add.spec.ts | 1 - .../src/tests/runner/runner-workflow-amend.spec.ts | 4 ---- 2 files changed, 5 deletions(-) diff --git a/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts b/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts index 9dbd6db75..0c147933d 100644 --- a/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts +++ b/tests/playwright-tests/src/tests/runner/runner-workflow-add.spec.ts @@ -10,7 +10,6 @@ import { TestTypePicker } from './TestTypePicker'; const TEST_TYPE = process.env.TEST_TYPE ?? 'SMOKE'; let scopedTestScenario = ""; - scopedTestScenario = TestTypePicker(TEST_TYPE) if (!scopedTestScenario) { diff --git a/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts b/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts index 13860bcdf..d00c85057 100644 --- a/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts +++ b/tests/playwright-tests/src/tests/runner/runner-workflow-amend.spec.ts @@ -13,10 +13,6 @@ const TEST_TYPE = process.env.TEST_TYPE ?? 'SMOKE'; scopedTestScenario = TestTypePicker(TEST_TYPE) -if (!scopedTestScenario) { - console.error("No test scenario tags defined for the current TEST_TYPE. Please check the environment variable."); - fail; -} if (!scopedTestScenario) { console.error("No test scenario tags defined for the current TEST_TYPE. Please check the environment variable."); fail;