From 02d27626fc20fb97ef8dbe6696719a07a3d307bb Mon Sep 17 00:00:00 2001 From: Lilach Date: Tue, 30 Jun 2026 11:23:41 +0100 Subject: [PATCH 01/18] add pipeline for running the move test with a large file --- devops-pipelines/move-playwright-test.yml | 86 +++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 devops-pipelines/move-playwright-test.yml diff --git a/devops-pipelines/move-playwright-test.yml b/devops-pipelines/move-playwright-test.yml new file mode 100644 index 000000000..efd29bb3e --- /dev/null +++ b/devops-pipelines/move-playwright-test.yml @@ -0,0 +1,86 @@ +schedules: + - cron: '0 5 * * Mon-Fri' + displayName: 'Weekday 5am UTC test' + branches: + include: + - task/FCT2-20209_Test-Move-functionality + always: true + +trigger: none + +parameters: + - name: environment + type: string + default: staging + - name: agentPool + type: string + default: "LACC PreProd Pool" + - name: testFileSizeMb + type: number + default: 1024 + +jobs: + - job: runMoveTest + displayName: "Run Move Test with Playwright" + pool: ${{ parameters.agentPool }} + variables: + - group: lacc-automated-testing-${{ parameters.environment }} + - group: lacc-backend-config-${{ parameters.environment }} + - name: nodeVersion + value: "20.x" + - name: workingDir + value: "$(System.DefaultWorkingDirectory)/e2e/pw" + + steps: + - task: UseNode@1 + displayName: "Use Node.js" + inputs: + version: "$(nodeVersion)" + + - task: Npm@1 + displayName: "Install NPM Dependencies" + inputs: + command: "ci" + workingDir: "$(workingDir)" + + - script: node_modules/.bin/playwright install --with-deps chromium + displayName: "Install Browsers" + workingDirectory: $(workingDir) + + - script: | + npx --no-install --ignore-scripts \ + playwright test egress-to-netapp-move-default.spec.ts + workingDirectory: $(workingDir) + displayName: "Run E2E Tests" + env: + CI: true + TEST_FILE_SIZE_MB: ${{ parameters.testFileSizeMb }} + BASE_URL: $(RedirectUrlLccUi) + CMS_LOGIN_PAGE: "$(LccApiBaseUrl)/api/tactical/login" + EGRESS_BASE_URL: $(EgressOptionsUrl) + TENANT_ID: $(TenantId) + LCC_API_CLIENT_ID: $(CallingAppValidAudience) + E2E_AD_USER: $(AadUserName) + E2E_AD_PASSWORD: $(AadUserPassword) + CMS_USERNAME: $(CmsUserName) + CMS_PASSWORD: $(CmsUserPassword) + EGRESS_SERVICE_ACCOUNT_AUTH: $(EgressServiceAccountAuth) + DEFAULT_WORKSPACE_ID: $(E2eEgressWorkspaceId) + DEFAULT_WORKSPACE_NAME: $(E2eEgressWorkspaceName) + DEFAULT_CASE_URN: $(E2eTestsCaseUrn) + DEFAULT_CASE_ID: $(E2eTestsCaseId) + LCC_API_BASE_URL: $(LccApiBaseUrl) + NETAPP_OPERATION_NAME: $(E2eNetAppFolderName) + + - task: PublishTestResults@2 + displayName: "Publish Test Results (JUnit)" + inputs: + testResultsFormat: JUnit + testResultsFiles: "$(workingDir)/playwright-report/e2e-test-report.xml" + publishRunAttachments: false + condition: succeededOrFailed() + + - publish: "$(workingDir)/playwright-report" + artifact: "$(System.JobId)-e2e-playwright-report" + displayName: "Publish Report Artifact" + condition: succeededOrFailed() From db171cb869ca9d3a2e1c6500b9d6dc681edba63a Mon Sep 17 00:00:00 2001 From: Lilach Date: Tue, 30 Jun 2026 17:48:47 +0100 Subject: [PATCH 02/18] add checks for Move test - complete files exist in netapp, files removed from Egress. --- .gitignore | 4 +- devops-pipelines/move-playwright-test.yml | 8 +- e2e/pw/fixtures/setup-helper-default.ts | 2 + e2e/pw/helpers/egress-api.ts | 17 +++ e2e/pw/helpers/types.ts | 2 + e2e/pw/pages/TransferMaterialsTab.ts | 65 +++++++++++ ...gress-to-netapp-move-large-default.spec.ts | 106 ++++++++++++++++++ 7 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts diff --git a/.gitignore b/.gitignore index 22d1d31ef..4b1d8ce4b 100644 --- a/.gitignore +++ b/.gitignore @@ -334,8 +334,7 @@ nuget.config # postman *.postman_environment.json -# E2E Postman Tests -e2e/*/secrets.config.ps1 +# E2E Tests e2e/*/*.secrets.config.ps1 e2e/postman/*.postman_environment !e2e/postman/*.postman_environment.template @@ -343,6 +342,7 @@ e2e/postman/newman-reports/ e2e/postman/LCCTestEnvironment_updated.* e2e/postman/*_updated.postman_collection.json e2e/postman/*postman_collection_updated.json +e2e/postman/secrets.config.ps1 # Environment files — keep templates tracked, ignore environment-specific # values anywhere in the repo (e.g. e2e/pw/.env.local). diff --git a/devops-pipelines/move-playwright-test.yml b/devops-pipelines/move-playwright-test.yml index efd29bb3e..019ad02d6 100644 --- a/devops-pipelines/move-playwright-test.yml +++ b/devops-pipelines/move-playwright-test.yml @@ -1,6 +1,6 @@ schedules: - - cron: '0 5 * * Mon-Fri' - displayName: 'Weekday 5am UTC test' + - cron: '0 1 * * Mon-Fri' + displayName: 'Weekday 1am UTC test' branches: include: - task/FCT2-20209_Test-Move-functionality @@ -17,7 +17,7 @@ parameters: default: "LACC PreProd Pool" - name: testFileSizeMb type: number - default: 1024 + default: 2048 # 2GB jobs: - job: runMoveTest @@ -49,7 +49,7 @@ jobs: - script: | npx --no-install --ignore-scripts \ - playwright test egress-to-netapp-move-default.spec.ts + playwright test egress-to-netapp-move-large-default.spec.ts workingDirectory: $(workingDir) displayName: "Run E2E Tests" env: diff --git a/e2e/pw/fixtures/setup-helper-default.ts b/e2e/pw/fixtures/setup-helper-default.ts index aa9598bac..4998863f1 100644 --- a/e2e/pw/fixtures/setup-helper-default.ts +++ b/e2e/pw/fixtures/setup-helper-default.ts @@ -178,5 +178,7 @@ export async function setupDefaultTestData( caseId: Number.isFinite(caseIdNum) ? caseIdNum : undefined, uploadSubfolder, destinationSubfolderId, + egressToken, + egressBaseUrl: config.egressBaseUrl, }; } diff --git a/e2e/pw/helpers/egress-api.ts b/e2e/pw/helpers/egress-api.ts index 006ac7e63..9e1a0116a 100644 --- a/e2e/pw/helpers/egress-api.ts +++ b/e2e/pw/helpers/egress-api.ts @@ -494,3 +494,20 @@ export async function deleteWorkspace( } } +// ---------------- Added for Move test ------------------ +export async function isFileInEgress( + baseUrl: string, + token: string, + workspaceId: string, + folderId: string, + fileName: string +): Promise { + const files = await listEgressWorkspaceFilesByFolderId( + baseUrl, + token, + workspaceId, + folderId + ); + + return files.some(f => f.fileName === fileName); +} diff --git a/e2e/pw/helpers/types.ts b/e2e/pw/helpers/types.ts index 5e431f024..0cfbed895 100644 --- a/e2e/pw/helpers/types.ts +++ b/e2e/pw/helpers/types.ts @@ -32,6 +32,8 @@ export interface TestSetupResult { // when the folder already existed at setup time (rare timestamp // collision); teardown skips destination cleanup in that case. destinationSubfolderId?: string; + egressToken: string; + egressBaseUrl: string; } export interface AuthTokens { diff --git a/e2e/pw/pages/TransferMaterialsTab.ts b/e2e/pw/pages/TransferMaterialsTab.ts index 634f61306..f39969cff 100644 --- a/e2e/pw/pages/TransferMaterialsTab.ts +++ b/e2e/pw/pages/TransferMaterialsTab.ts @@ -241,4 +241,69 @@ export class TransferMaterialsTab `Timed out waiting for ${expectedCount} files (timeout: ${timeout}ms)`, ); } + + // ------------------- Added for Move Test----------------------------------- + // Confirm a file exists and is of the expected size (in MB) + async verifyNetAppFileSizeByExactName( + fileName: string, + expectedSizeMB: number, + ): Promise { + const row = this.page + .getByTestId("netapp-table-wrapper") + .locator("tbody tr", { hasText: fileName }); + + if ((await row.count()) === 0) { + throw new Error( + `A file named '${fileName}' was not found in the NetApp panel.` + ); + } + + const sizeCell = row.locator("td").nth(1); + await sizeCell.scrollIntoViewIfNeeded(); + + const sizeText = (await sizeCell.textContent())?.trim(); + + if (!sizeText) { + throw new Error( + `File '${fileName}' was found but its size could not be determined.` + ); + } + + // Convert to MB + const match = sizeText.match(/^(\d+(?:\.\d+)?)\s?(KB|MB|GB)$/i); + + if (!match) { + throw new Error(`Invalid size format for '${fileName}': ${sizeText}`); + } + + const value = parseFloat(match[1]); + const unit = match[2].toUpperCase(); + + let actualSizeMB: number; + + switch (unit) { + case "KB": + actualSizeMB = value / 1000; + break; + case "MB": + actualSizeMB = value; + break; + case "GB": + actualSizeMB = value * 1000; + break; + default: + throw new Error(`Unsupported unit for '${fileName}': ${unit}`); + } + + // Compare (with tolerance) + const tolerance = 0.1; + + expect( + Math.abs(actualSizeMB - expectedSizeMB), + `File size check for '${fileName}'.\n` + + `Expected: ${expectedSizeMB.toFixed(2)} MB\n` + + `Actual: ${actualSizeMB} MB` + ).toBeLessThanOrEqual(tolerance); + } + // -------------------------------------------------------------------------- } diff --git a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts new file mode 100644 index 000000000..bcf0618d7 --- /dev/null +++ b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts @@ -0,0 +1,106 @@ +import { test } from "../fixtures/test-fixtures-default"; +import { CaseSearchPage } from "../pages/CaseSearchPage"; +import { SearchResultsPage } from "../pages/SearchResultsPage"; +import { CaseManagementPage } from "../pages/CaseManagementPage"; +import { TransferMaterialsTab } from "../pages/TransferMaterialsTab"; +import { ActivityLogTab } from "../pages/ActivityLogTab"; +import { isFileInEgress } from '../helpers/egress-api'; +import { expect } from "@playwright/test"; + +test.describe("Egress to NetApp Move (Default Mode)", () => { + test("should move files from Egress to NetApp using existing case", async ({ + page, + testData, + }) => { + test.setTimeout(900_000); + const { caseUrn, uploadSubfolder } = testData; + + // Step 1: Search for case by URN + const caseSearch = new CaseSearchPage(page); + await caseSearch.searchByUrn(caseUrn); + + // Step 2: Click View on already-connected case + const searchResults = new SearchResultsPage(page); + await searchResults.waitForResults(); + await searchResults.clickCaseAction(caseUrn); + + // Step 3: Navigate to Transfer Materials tab + const caseMgmt = new CaseManagementPage(page); + await caseMgmt.waitForLoad(); + await caseMgmt.switchToTab("transfer-materials"); + + // Step 4: Select files from Egress panel and initiate Move + const transferTab = new TransferMaterialsTab(page); + await transferTab.waitForEgressFiles(); + await transferTab.navigateToFolder("4. Served Evidence"); + await transferTab.waitForEgressFiles(); + if (uploadSubfolder) { + await transferTab.navigateToFolder(uploadSubfolder); + await transferTab.waitForEgressFiles(); + } + + // Wait for the just-uploaded file to be indexed before selecting. + // Egress doesn't auto-refresh the file list, so the helper reloads + + // re-navigates on each retry. + const sourceFolderPath = uploadSubfolder + ? ["4. Served Evidence", uploadSubfolder] + : ["4. Served Evidence"]; + await transferTab.waitForEgressFileByName( + testData.files[testData.files.length - 1].fileName, + sourceFolderPath + ); + + // Select by name (not index) so we don't pick a stranger's old file + // that happened to land at row 0. + for (const file of testData.files) { + await transferTab.selectEgressFileByName(file.fileName); + } + + await transferTab.selectAction("Move"); + + // Step 5: Confirm transfer + await transferTab.confirmTransfer(); + + // Step 6: Wait for transfer to complete (10 min timeout) + await transferTab.waitForTransferComplete(600_000); + + // Step 7: Verify in Activity Log + await caseMgmt.switchToTab("activity-log"); + const activityLog = new ActivityLogTab(page); + await activityLog.waitForLogs(); + await activityLog.verifyTransferLogged("Move", uploadSubfolder!); + + // Step 8: Download CSV and verify + await activityLog.expandFileList(); + await activityLog.downloadCsv(); + await activityLog.verifyDownloadSuccess(); + + // Step 9: Confirm complete files appear in Shared Drive panel + await caseMgmt.switchToTab("transfer-materials"); + for (const file of testData.files) { + const fileSizeMb = file.fileSize / 1000000 + await transferTab.verifyNetAppFileSizeByExactName(file.fileName, fileSizeMb); + } + + // Step 10: Confirm files removed from Egress + for (const file of testData.files) { + await test.step( + `Verify file '${file.fileName}' is no longer present in Egress`, + async () => { + const exists = await isFileInEgress( + testData.egressBaseUrl, + testData.egressToken, + testData.workspace.id, + testData.destinationSubfolderId!, + file.fileName + ); + + expect( + exists, + `File '${file.fileName}' still exists in Egress` + ).toBeFalsy(); + } + ); + } + }); +}); From fdb7ee78e14600531f9511fc8cff3a8f7220d285 Mon Sep 17 00:00:00 2001 From: Lilach Date: Wed, 1 Jul 2026 11:30:07 +0000 Subject: [PATCH 03/18] fix page.reload redirect to landing page for long waits --- e2e/pw/pages/TransferMaterialsTab.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/e2e/pw/pages/TransferMaterialsTab.ts b/e2e/pw/pages/TransferMaterialsTab.ts index f39969cff..90ab520e2 100644 --- a/e2e/pw/pages/TransferMaterialsTab.ts +++ b/e2e/pw/pages/TransferMaterialsTab.ts @@ -296,13 +296,15 @@ export class TransferMaterialsTab } // Compare (with tolerance) - const tolerance = 0.1; + const diff = Math.abs(actualSizeMB - expectedSizeMB); + const tolerance = expectedSizeMB * 0.01; expect( - Math.abs(actualSizeMB - expectedSizeMB), + diff, `File size check for '${fileName}'.\n` + `Expected: ${expectedSizeMB.toFixed(2)} MB\n` + - `Actual: ${actualSizeMB} MB` + `Actual: ${actualSizeMB} MB\n` + + `Diff: ${diff.toFixed(2)} MB (should not exceed ${tolerance.toFixed(2)} MB)` ).toBeLessThanOrEqual(tolerance); } // -------------------------------------------------------------------------- From ff93f1fa9a731abeafd84873140bae2e2dbff45e Mon Sep 17 00:00:00 2001 From: Lilach Date: Wed, 1 Jul 2026 12:08:17 +0000 Subject: [PATCH 04/18] update cron schedule, resolve sonarqube-flagged issue --- devops-pipelines/move-playwright-test.yml | 2 +- e2e/pw/pages/TransferMaterialsTab.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devops-pipelines/move-playwright-test.yml b/devops-pipelines/move-playwright-test.yml index 019ad02d6..3915f7467 100644 --- a/devops-pipelines/move-playwright-test.yml +++ b/devops-pipelines/move-playwright-test.yml @@ -1,5 +1,5 @@ schedules: - - cron: '0 1 * * Mon-Fri' + - cron: '0 1 * * Tue-Sat' displayName: 'Weekday 1am UTC test' branches: include: diff --git a/e2e/pw/pages/TransferMaterialsTab.ts b/e2e/pw/pages/TransferMaterialsTab.ts index 90ab520e2..210d7d8b0 100644 --- a/e2e/pw/pages/TransferMaterialsTab.ts +++ b/e2e/pw/pages/TransferMaterialsTab.ts @@ -276,7 +276,7 @@ export class TransferMaterialsTab throw new Error(`Invalid size format for '${fileName}': ${sizeText}`); } - const value = parseFloat(match[1]); + const value = Number.parseFloat(match[1]); const unit = match[2].toUpperCase(); let actualSizeMB: number; From d6345fde985264bccdf1ee85e752b1dca5df3b47 Mon Sep 17 00:00:00 2001 From: Lilach Date: Wed, 1 Jul 2026 17:01:06 +0000 Subject: [PATCH 05/18] address PR comments --- .gitignore | 3 ++- e2e/pw/fixtures/setup-helper-default.ts | 4 +++- e2e/pw/helpers/types.ts | 4 ++++ e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts | 3 ++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 4b1d8ce4b..0c415f700 100644 --- a/.gitignore +++ b/.gitignore @@ -335,6 +335,7 @@ nuget.config *.postman_environment.json # E2E Tests +e2e/*/secrets.config.ps1 e2e/*/*.secrets.config.ps1 e2e/postman/*.postman_environment !e2e/postman/*.postman_environment.template @@ -342,7 +343,7 @@ e2e/postman/newman-reports/ e2e/postman/LCCTestEnvironment_updated.* e2e/postman/*_updated.postman_collection.json e2e/postman/*postman_collection_updated.json -e2e/postman/secrets.config.ps1 + # Environment files — keep templates tracked, ignore environment-specific # values anywhere in the repo (e.g. e2e/pw/.env.local). diff --git a/e2e/pw/fixtures/setup-helper-default.ts b/e2e/pw/fixtures/setup-helper-default.ts index 4998863f1..d8a540457 100644 --- a/e2e/pw/fixtures/setup-helper-default.ts +++ b/e2e/pw/fixtures/setup-helper-default.ts @@ -75,7 +75,8 @@ export async function setupDefaultTestData( console.log( `[2/3] Ensuring subfolder ${uploadSubfolder} exists in source + destination...` ); - await createFolder( + // Capture the source folder id for Move test verification + const sourceSubfolderId = await createFolder( config.egressBaseUrl, egressToken, workspaceId, @@ -177,6 +178,7 @@ export async function setupDefaultTestData( caseUrn, caseId: Number.isFinite(caseIdNum) ? caseIdNum : undefined, uploadSubfolder, + sourceSubfolderId, destinationSubfolderId, egressToken, egressBaseUrl: config.egressBaseUrl, diff --git a/e2e/pw/helpers/types.ts b/e2e/pw/helpers/types.ts index 0cfbed895..bb1b92a60 100644 --- a/e2e/pw/helpers/types.ts +++ b/e2e/pw/helpers/types.ts @@ -26,6 +26,10 @@ export interface TestSetupResult { // "4. Served Evidence//", NetApp->Egress copies land in // "2. Counsel only//". Undefined for register-case mode. uploadSubfolder?: string; + // Egress folder id for "4. Counsel only//", captured at + // createFolder time. Used by the Move (large file) test to verify files were + // deleted from the source folder after transfer. + sourceSubfolderId?: string; // Egress folder id for "2. Counsel only//", captured at // createFolder time. Used by per-test teardown to list and delete files // the LCC backend wrote there during NetApp->Egress copy specs. Undefined diff --git a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts index bcf0618d7..ac1cfdd13 100644 --- a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts @@ -83,6 +83,7 @@ test.describe("Egress to NetApp Move (Default Mode)", () => { } // Step 10: Confirm files removed from Egress + console.log(`Source Folder ID: ${testData.sourceSubfolderId}`) for (const file of testData.files) { await test.step( `Verify file '${file.fileName}' is no longer present in Egress`, @@ -91,7 +92,7 @@ test.describe("Egress to NetApp Move (Default Mode)", () => { testData.egressBaseUrl, testData.egressToken, testData.workspace.id, - testData.destinationSubfolderId!, + testData.sourceSubfolderId!, file.fileName ); From 4da34e9479ef53458f00bf8947161db34d30f68c Mon Sep 17 00:00:00 2001 From: Lilach Date: Thu, 2 Jul 2026 09:08:27 +0000 Subject: [PATCH 06/18] remove unnecessary logging line --- e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts index ac1cfdd13..ea7137c05 100644 --- a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts @@ -82,8 +82,7 @@ test.describe("Egress to NetApp Move (Default Mode)", () => { await transferTab.verifyNetAppFileSizeByExactName(file.fileName, fileSizeMb); } - // Step 10: Confirm files removed from Egress - console.log(`Source Folder ID: ${testData.sourceSubfolderId}`) + // Step 10: Confirm files removed from Egres for (const file of testData.files) { await test.step( `Verify file '${file.fileName}' is no longer present in Egress`, From 05445f0e06bd9f52515e0bbb2c03415579590c64 Mon Sep 17 00:00:00 2001 From: Lilach Date: Thu, 2 Jul 2026 10:12:19 +0000 Subject: [PATCH 07/18] fix ref to newly typed TestSetupResult --- e2e/pw/fixtures/test-fixtures-register-case.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/e2e/pw/fixtures/test-fixtures-register-case.ts b/e2e/pw/fixtures/test-fixtures-register-case.ts index ac49a0fb1..cc5c9dce0 100644 --- a/e2e/pw/fixtures/test-fixtures-register-case.ts +++ b/e2e/pw/fixtures/test-fixtures-register-case.ts @@ -119,6 +119,8 @@ export const test = base.extend< caseUrn: shared.caseUrn, files, uploadSubfolder, + egressBaseUrl: config.egressBaseUrl, + egressToken: token }); // Per-test teardown. On failure we leave the uploaded files in the @@ -134,7 +136,7 @@ export const test = base.extend< netAppFolder: REGISTER_CASE_NETAPP_FOLDER, caseId: shared.caseId, testInfo, - egressToken: token, + egressToken: token }); }, { timeout: 300_000 }], }); From cccf33f86a1a83e519c002579aa5b1690f1bd7a6 Mon Sep 17 00:00:00 2001 From: Lilach Date: Thu, 2 Jul 2026 10:34:34 +0000 Subject: [PATCH 08/18] fix newly typed TestSetupResult, make auth vars oprional --- e2e/pw/fixtures/test-fixtures-register-case.ts | 2 -- e2e/pw/helpers/types.ts | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/e2e/pw/fixtures/test-fixtures-register-case.ts b/e2e/pw/fixtures/test-fixtures-register-case.ts index cc5c9dce0..29b68f7cc 100644 --- a/e2e/pw/fixtures/test-fixtures-register-case.ts +++ b/e2e/pw/fixtures/test-fixtures-register-case.ts @@ -119,8 +119,6 @@ export const test = base.extend< caseUrn: shared.caseUrn, files, uploadSubfolder, - egressBaseUrl: config.egressBaseUrl, - egressToken: token }); // Per-test teardown. On failure we leave the uploaded files in the diff --git a/e2e/pw/helpers/types.ts b/e2e/pw/helpers/types.ts index bb1b92a60..37b66b521 100644 --- a/e2e/pw/helpers/types.ts +++ b/e2e/pw/helpers/types.ts @@ -36,8 +36,8 @@ export interface TestSetupResult { // when the folder already existed at setup time (rare timestamp // collision); teardown skips destination cleanup in that case. destinationSubfolderId?: string; - egressToken: string; - egressBaseUrl: string; + egressToken?: string; + egressBaseUrl?: string; } export interface AuthTokens { From 41312bed71aae1ce4685b1b5d0ebf214b4fb28b3 Mon Sep 17 00:00:00 2001 From: Lilach Date: Fri, 3 Jul 2026 09:55:40 +0000 Subject: [PATCH 09/18] Add logging for post-move checks --- e2e/pw/fixtures/setup-helper-default.ts | 1 + e2e/pw/helpers/types.ts | 3 +++ e2e/pw/pages/TransferMaterialsTab.ts | 15 ++++++++------- .../egress-to-netapp-move-large-default.spec.ts | 9 +++++++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/e2e/pw/fixtures/setup-helper-default.ts b/e2e/pw/fixtures/setup-helper-default.ts index d8a540457..d798c41ad 100644 --- a/e2e/pw/fixtures/setup-helper-default.ts +++ b/e2e/pw/fixtures/setup-helper-default.ts @@ -178,6 +178,7 @@ export async function setupDefaultTestData( caseUrn, caseId: Number.isFinite(caseIdNum) ? caseIdNum : undefined, uploadSubfolder, + uploadPath, sourceSubfolderId, destinationSubfolderId, egressToken, diff --git a/e2e/pw/helpers/types.ts b/e2e/pw/helpers/types.ts index 37b66b521..e5692b6d2 100644 --- a/e2e/pw/helpers/types.ts +++ b/e2e/pw/helpers/types.ts @@ -26,6 +26,9 @@ export interface TestSetupResult { // "4. Served Evidence//", NetApp->Egress copies land in // "2. Counsel only//". Undefined for register-case mode. uploadSubfolder?: string; + // Egress folder path for "4. Counsel only//". + // Used by the Move (large file) test for clear logging. + uploadPath?: string; // Egress folder id for "4. Counsel only//", captured at // createFolder time. Used by the Move (large file) test to verify files were // deleted from the source folder after transfer. diff --git a/e2e/pw/pages/TransferMaterialsTab.ts b/e2e/pw/pages/TransferMaterialsTab.ts index 210d7d8b0..0bf754ab5 100644 --- a/e2e/pw/pages/TransferMaterialsTab.ts +++ b/e2e/pw/pages/TransferMaterialsTab.ts @@ -299,13 +299,14 @@ export class TransferMaterialsTab const diff = Math.abs(actualSizeMB - expectedSizeMB); const tolerance = expectedSizeMB * 0.01; - expect( - diff, - `File size check for '${fileName}'.\n` + - `Expected: ${expectedSizeMB.toFixed(2)} MB\n` + - `Actual: ${actualSizeMB} MB\n` + - `Diff: ${diff.toFixed(2)} MB (should not exceed ${tolerance.toFixed(2)} MB)` - ).toBeLessThanOrEqual(tolerance); + const message = `File size check for '${fileName}'.\n` + + ` Expected: ${expectedSizeMB.toFixed(2)} MB\n` + + ` Actual: ${actualSizeMB} MB\n` + + ` Diff: ${diff.toFixed(2)} MB (should not exceed ${tolerance.toFixed(2)} MB)` + + console.log(`\n${message}`) + + expect(diff, message).toBeLessThanOrEqual(tolerance); } // -------------------------------------------------------------------------- } diff --git a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts index ea7137c05..40abe60e6 100644 --- a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts @@ -84,17 +84,22 @@ test.describe("Egress to NetApp Move (Default Mode)", () => { // Step 10: Confirm files removed from Egres for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' has been deleted from source '${testData.uploadPath}'...`) await test.step( `Verify file '${file.fileName}' is no longer present in Egress`, async () => { const exists = await isFileInEgress( - testData.egressBaseUrl, - testData.egressToken, + testData.egressBaseUrl!, + testData.egressToken!, testData.workspace.id, testData.sourceSubfolderId!, file.fileName ); + if (!exists) { + console.log(`File '${file.fileName}' was not found in '${testData.uploadPath}'`) + } + expect( exists, `File '${file.fileName}' still exists in Egress` From ec27a20c12bd1333d2aef1f2b147247110dce754 Mon Sep 17 00:00:00 2001 From: Lilach Date: Mon, 13 Jul 2026 12:54:21 +0100 Subject: [PATCH 10/18] use backend-only transfer verification --- .gitignore | 3 +- e2e/pw/fixtures/setup-helper-default.ts | 24 ++++- e2e/pw/fixtures/setup-helper.ts | 23 +++- e2e/pw/fixtures/teardown-helper.ts | 4 +- .../fixtures/test-fixtures-register-case.ts | 25 ++++- e2e/pw/helpers/egress-api.ts | 84 +++++++++++---- e2e/pw/helpers/transfer-verify.ts | 100 ++++++++++++++++++ e2e/pw/helpers/types.ts | 4 +- e2e/pw/playwright.config.ts | 1 + .../egress-to-netapp-copy-default.spec.ts | 12 +++ ...gress-to-netapp-copy-large-default.spec.ts | 11 ++ .../tests/egress-to-netapp-copy-large.spec.ts | 12 +++ .../egress-to-netapp-copy-multifile.spec.ts | 12 +++ e2e/pw/tests/egress-to-netapp-copy.spec.ts | 12 +++ .../egress-to-netapp-move-default.spec.ts | 33 ++++++ ...gress-to-netapp-move-large-default.spec.ts | 25 ++--- e2e/pw/tests/egress-to-netapp-move.spec.ts | 33 ++++++ .../netapp-to-egress-copy-default.spec.ts | 25 +++++ e2e/pw/tests/netapp-to-egress-copy.spec.ts | 4 + 19 files changed, 394 insertions(+), 53 deletions(-) create mode 100644 e2e/pw/helpers/transfer-verify.ts diff --git a/.gitignore b/.gitignore index 0c415f700..97e584a41 100644 --- a/.gitignore +++ b/.gitignore @@ -304,8 +304,8 @@ coverage *.sw? # Playwright +playwright-report/ ui-spa/playwright/test-results/ -ui-spa/playwright/playwright-report/ ui-spa/blob-report/ ui-spa/playwright/.cache/ ui-spa/playwright-report/ @@ -313,7 +313,6 @@ ui-spa/playwright/integration-test-results.xml ui-spa/playwright/.nyc_output ui-spa/unit-test-results.xml e2e/pw/test-results/ -e2e/pw/playwright-report/ e2e/pw/.auth/ e2e/pw/.state/ e2e/pw/test-evidence/ diff --git a/e2e/pw/fixtures/setup-helper-default.ts b/e2e/pw/fixtures/setup-helper-default.ts index d798c41ad..1f1ba0bd2 100644 --- a/e2e/pw/fixtures/setup-helper-default.ts +++ b/e2e/pw/fixtures/setup-helper-default.ts @@ -4,6 +4,7 @@ import { authenticateEgress, createFolder, uploadFile, + getUploadedFile, } from "../helpers/egress-api"; import { TacticalLoginPage } from "../pages/TacticalLoginPage"; import { AzureADLoginPage } from "../pages/AzureADLoginPage"; @@ -99,7 +100,7 @@ export async function setupDefaultTestData( `[3/3] Uploading ${fileCount} test file(s) of ${fileSizeMb}MB to ${workspaceName} (${workspaceId}) at ${uploadPath}...` ); const fileSizeBytes = fileSizeMb * 1024 * 1024; - const files: UploadedFile[] = []; + const uploadIds: string[] = []; for (let i = 1; i <= fileCount; i++) { const timestamp = new Date() @@ -108,7 +109,7 @@ export async function setupDefaultTestData( .slice(0, 19); const fileName = `generated-${fileSizeMb}MB-${timestamp}-file${i}.txt`; console.log(` Uploading ${fileName} (${i}/${fileCount})...`); - const file = await uploadFile( + const uploadId = await uploadFile( config.egressBaseUrl, egressToken, workspaceId, @@ -116,9 +117,25 @@ export async function setupDefaultTestData( fileName, uploadPath ); - files.push(file); + uploadIds.push(uploadId); } + console.log (" Getting the uploaded file ID(s)...") + const files = await Promise.all( + uploadIds.map(uploadId => + getUploadedFile( + config.egressBaseUrl, + egressToken, + workspaceId, + uploadId, + { + timeoutMs: Math.max(30000, fileSizeMb * 15000), + retryDelay: Math.min(10000,Math.max(2000, fileSizeMb * 5)), + } + ) + ) + ); + console.log("=== Upload Complete ===\n"); console.log( @@ -182,6 +199,5 @@ export async function setupDefaultTestData( sourceSubfolderId, destinationSubfolderId, egressToken, - egressBaseUrl: config.egressBaseUrl, }; } diff --git a/e2e/pw/fixtures/setup-helper.ts b/e2e/pw/fixtures/setup-helper.ts index afa4316a0..596cbf8a3 100644 --- a/e2e/pw/fixtures/setup-helper.ts +++ b/e2e/pw/fixtures/setup-helper.ts @@ -6,6 +6,7 @@ import { createWorkspace, addUserToWorkspace, uploadFile, + getUploadedFile, } from "../helpers/egress-api"; import { getAuthTokens } from "../helpers/auth-api"; import { registerCase } from "../helpers/case-api"; @@ -73,7 +74,7 @@ export async function setupTestData( `[5/5] Uploading ${fileCount} test file(s) of ${fileSizeMb}MB each...`, ); const fileSizeBytes = fileSizeMb * 1024 * 1024; - const files: UploadedFile[] = []; + const uploadIds: string[] = []; for (let i = 1; i <= fileCount; i++) { const timestamp = new Date() @@ -82,16 +83,32 @@ export async function setupTestData( .slice(0, 19); const fileName = `generated-${fileSizeMb}MB-${timestamp}-file${i}.txt`; console.log(` Uploading ${fileName} (${i}/${fileCount})...`); - const file = await uploadFile( + const uploadId = await uploadFile( config.egressBaseUrl, egressToken, workspaceId, fileSizeBytes, fileName, ); - files.push(file); + uploadIds.push(uploadId); } + console.log (" Getting the uploaded file ID(s)...") + const files = await Promise.all( + uploadIds.map(uploadId => + getUploadedFile( + config.egressBaseUrl, + egressToken, + workspaceId, + uploadId, + { + timeoutMs: Math.max(30000, fileSizeMb * 15000), + retryDelay: Math.min(10000,Math.max(2000, fileSizeMb * 5)), + } + ) + ) + ); + console.log("=== Workspace Setup Complete ===\n"); // Step 2: Get auth tokens and register a fresh case diff --git a/e2e/pw/fixtures/teardown-helper.ts b/e2e/pw/fixtures/teardown-helper.ts index 589220de0..ce144aa5c 100644 --- a/e2e/pw/fixtures/teardown-helper.ts +++ b/e2e/pw/fixtures/teardown-helper.ts @@ -56,8 +56,8 @@ export async function teardownTestData(ctx: TeardownContext): Promise { )); const fileIds = ctx.files - .map((f) => f.id) - .filter((id): id is string => !!id); + .map((f) => f.fileId) + .filter((fileId): fileId is string => !!fileId); await deleteFiles(config.egressBaseUrl, token, ctx.workspaceId, fileIds); if (ctx.destinationSubfolderId) { diff --git a/e2e/pw/fixtures/test-fixtures-register-case.ts b/e2e/pw/fixtures/test-fixtures-register-case.ts index 29b68f7cc..fada1af80 100644 --- a/e2e/pw/fixtures/test-fixtures-register-case.ts +++ b/e2e/pw/fixtures/test-fixtures-register-case.ts @@ -6,6 +6,7 @@ import { authenticateEgress, createFolder, uploadFile, + getUploadedFile, } from "../helpers/egress-api"; import { REGISTER_CASE_NETAPP_FOLDER } from "../helpers/constants"; import type { TestSetupResult, UploadedFile } from "../helpers/types"; @@ -90,14 +91,15 @@ export const test = base.extend< ` Uploading ${testOptions.fileCount} x ${testOptions.fileSizeMb}MB file(s) to ${uploadPath}...` ); const fileSizeBytes = testOptions.fileSizeMb * 1024 * 1024; - const files: UploadedFile[] = []; + const uploadIds: string[] = []; + for (let i = 1; i <= testOptions.fileCount; i++) { const timestamp = new Date() .toISOString() .replace(/[:.]/g, "-") .slice(0, 19); const fileName = `generated-${testOptions.fileSizeMb}MB-${timestamp}-file${i}.txt`; - const file = await uploadFile( + const uploadId = await uploadFile( config.egressBaseUrl, token, shared.workspace.id, @@ -105,9 +107,25 @@ export const test = base.extend< fileName, uploadPath ); - files.push(file); + uploadIds.push(uploadId); } + console.log (" Getting the uploaded file ID(s)...\n") + const files = await Promise.all( + uploadIds.map(uploadId => + getUploadedFile( + config.egressBaseUrl, + token, + shared.workspace.id, + uploadId, + { + timeoutMs: Math.max(30000, testOptions.fileSizeMb * 15000), + retryDelay: Math.min(10000,Math.max(2000, testOptions.fileSizeMb * 5)), + }, + ) + ) + ); + // Refresh the tactical + AD session per test and wait for the search // radios to be enabled before handing control to the spec. This mirrors // the manual flow and avoids HTTP 400 on /api/v1/case-search when @@ -119,6 +137,7 @@ export const test = base.extend< caseUrn: shared.caseUrn, files, uploadSubfolder, + caseId: shared.caseId, }); // Per-test teardown. On failure we leave the uploaded files in the diff --git a/e2e/pw/helpers/egress-api.ts b/e2e/pw/helpers/egress-api.ts index 9e1a0116a..c8d415944 100644 --- a/e2e/pw/helpers/egress-api.ts +++ b/e2e/pw/helpers/egress-api.ts @@ -234,7 +234,7 @@ export async function uploadFile( fileName: string, folderPath: string = "4. Served Evidence/", chunkSizeMB: number = 5 -): Promise { +): Promise { // Step 1: Initiate upload const initiateResponse = await fetch( `${baseUrl}/api/v1/workspaces/${workspaceId}/uploads`, @@ -339,10 +339,66 @@ export async function uploadFile( // the response shape changes so callers that need an id for teardown // always get something to work with. const completeData = await completeResponse.json().catch(() => ({})); - const fileId: string = completeData?.id ?? uploadId; - console.log(` Upload complete: ${fileId}`); - return { id: fileId, fileName, fileSize: fileSizeBytes }; + console.log(` Upload complete: ${uploadId}`); + return uploadId; +} + +export async function getUploadedFile( + baseUrl: string, + token: string, + workspaceId: string, + uploadId: string, + { + timeoutMs = 60000, + retryDelay = 2000, + }: { + timeoutMs?: number, + retryDelay?: number, + } = {} +): Promise{ + const start = Date.now(); + let i = 1 + + while (Date.now() - start < timeoutMs) { + console.log(` Attempt number ${i}...`) + + const response = await fetch( + `${baseUrl}/api/v1/workspaces/${workspaceId}/uploads/${uploadId}?view=full`, + { + headers: { + Authorization: `Basic ${token}`, + }, + } + ); + + if (!response.ok) { + throw new Error( + ` Failed to get upload status (${response.status})` + ); + } + + const status = await response.json(); + + if (status.file_id) { + console.log(` Upload complete. File ID found: ${status.file_id}`) + return { + fileId: status.file_id, + fileName: status.file_name, + fileSize: status.file_size, + parentFolderId: status.parent_folder_id + }; + } + + i++; + + console.log(` Retrying in ${retryDelay / 1000}s...`) + await new Promise(r => setTimeout(r, retryDelay)); + } + + throw new Error( + ` Timed out waiting for upload ${uploadId}` + ); } /** @@ -382,7 +438,7 @@ export async function listEgressWorkspaceFilesByFolderId( try { for (let page = 0; page < maxPages; page++) { const skip = page * pageSize; - const url = `${baseUrl}/api/v1/workspaces/${workspaceId}/files?view=full&skip=${skip}&limit=${pageSize}&folder=${encodeURIComponent(folderId)}`; + const url = `${baseUrl}/api/v1/workspaces/${workspaceId}/files?folder=${encodeURIComponent(folderId)}`; const response = await fetch(url, { headers: { Authorization: `Basic ${token}` }, }); @@ -493,21 +549,3 @@ export async function deleteWorkspace( ); } } - -// ---------------- Added for Move test ------------------ -export async function isFileInEgress( - baseUrl: string, - token: string, - workspaceId: string, - folderId: string, - fileName: string -): Promise { - const files = await listEgressWorkspaceFilesByFolderId( - baseUrl, - token, - workspaceId, - folderId - ); - - return files.some(f => f.fileName === fileName); -} diff --git a/e2e/pw/helpers/transfer-verify.ts b/e2e/pw/helpers/transfer-verify.ts new file mode 100644 index 000000000..407ae8141 --- /dev/null +++ b/e2e/pw/helpers/transfer-verify.ts @@ -0,0 +1,100 @@ +import { loadEnvConfig } from "../helpers/env-config"; +import { listEgressWorkspaceFilesByFolderId, authenticateEgress } from "./egress-api"; +import { getAzureADToken } from "./auth-api"; +import { expect } from "@playwright/test"; + +const config =loadEnvConfig() + +export async function verifyNetAppFileSizeByName( + filePath: string, + caseId: number, + expectedSizeBytes: number, + netAppOperationName: string = config.netAppOperationName, + accessToken?: string | undefined, +): Promise { + if (!accessToken) { + accessToken = await getAzureADToken( + config.tenantId, + config.lccApiClientId, + config.e2eAdUser, + config.e2eAdPassword, + ); + } + + const response = await fetch( + `${config.lccApiBaseUrl}/api/v1/netapp/search?case-id=${caseId}&query=${encodeURIComponent(filePath)}`, + { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + } + } + ); + + if (!response.ok) { + const text = await response.text(); + throw new Error( + `NetApp search failed (${response.status}) for '${filePath}': ${text.slice(0, 200)}` + ); + } + + const data = (await response.json()).data; + let file: { + key: string; + type: "File" | "Folder"; + size: number; + lastModified: string; + }; + + switch (data.length) { + case 0: + throw new Error(`No match found for '${filePath}'`); + case 1: + file = data[0]; + break; + default: + throw new Error(`Search response must not match more than a single file.`); + } + + const folderPrefix = netAppOperationName.endsWith("/") + ? netAppOperationName + : `${netAppOperationName}/`; + + const fullPath = `${folderPrefix}${filePath}`; + + if (file.key !== fullPath) { + throw new Error( + `The file path returned does not match '${fullPath}'.\n` + + `Returned: '${file.key}'.` + ); + } + + expect( + file.size, + `NetApp file '${filePath}' has unexpected size` + ).toBe(expectedSizeBytes); +} + +export async function isFileInEgress( + workspaceId: string, + folderId: string, + fileName: string, + egressToken?: string | undefined, +): Promise { + if (!egressToken) { + egressToken = await authenticateEgress( + config.egressBaseUrl, + config.egressServiceAccountAuth, + ) + } + + const files = await listEgressWorkspaceFilesByFolderId( + config.egressBaseUrl, + egressToken, + workspaceId, + folderId, + true, + ); + + return files.some(f => f.fileName === fileName); +} \ No newline at end of file diff --git a/e2e/pw/helpers/types.ts b/e2e/pw/helpers/types.ts index e5692b6d2..976a16051 100644 --- a/e2e/pw/helpers/types.ts +++ b/e2e/pw/helpers/types.ts @@ -7,9 +7,10 @@ export interface UploadedFile { // Egress file id from the completed-upload response. Used by teardown // (deleteFile) to remove the file after the test passes. Optional because // older callers may not capture it. - id?: string; + fileId: string; fileName: string; fileSize: number; + parentFolderId: string; } export interface TestSetupResult { @@ -40,7 +41,6 @@ export interface TestSetupResult { // collision); teardown skips destination cleanup in that case. destinationSubfolderId?: string; egressToken?: string; - egressBaseUrl?: string; } export interface AuthTokens { diff --git a/e2e/pw/playwright.config.ts b/e2e/pw/playwright.config.ts index bbcce38b0..eeca0e7e5 100644 --- a/e2e/pw/playwright.config.ts +++ b/e2e/pw/playwright.config.ts @@ -67,6 +67,7 @@ export default defineConfig({ { name: "default-mode-tests", testMatch: "**/*-default.spec.ts", + testIgnore: "egress-to-netapp-move-large-default.spec.ts", use: { ...devices["Desktop Chrome"] }, }, diff --git a/e2e/pw/tests/egress-to-netapp-copy-default.spec.ts b/e2e/pw/tests/egress-to-netapp-copy-default.spec.ts index 32d5981e0..ab8fb78d7 100644 --- a/e2e/pw/tests/egress-to-netapp-copy-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-copy-default.spec.ts @@ -4,6 +4,7 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; +import { verifyNetAppFileSizeByName } from "../helpers/transfer-verify"; test.describe("Egress to NetApp Copy (Default Mode)", () => { test("should copy files from Egress to NetApp using existing case", async ({ @@ -71,5 +72,16 @@ test.describe("Egress to NetApp Copy (Default Mode)", () => { await activityLog.expandFileList(); await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); + + // Step 9: Confirm complete files exist in shared drive + for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' exists in NetApp in its original size (${file.fileSize} bytes)`) + await verifyNetAppFileSizeByName( + file.fileName, + testData.caseId!, + file.fileSize, + "Automation-Testing" + ); + } }); }); diff --git a/e2e/pw/tests/egress-to-netapp-copy-large-default.spec.ts b/e2e/pw/tests/egress-to-netapp-copy-large-default.spec.ts index fb75adb9e..911d4fcf4 100644 --- a/e2e/pw/tests/egress-to-netapp-copy-large-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-copy-large-default.spec.ts @@ -5,6 +5,7 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; +import { verifyNetAppFileSizeByName } from "../helpers/transfer-verify"; const fileSize = loadEnvConfig().largeTestFileSizeMb; @@ -69,5 +70,15 @@ test.describe(`Egress to NetApp Copy - Large File ${fileSize}MB (Default Mode)`, await activityLog.expandFileList(); await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); + + // Step 9: Confirm complete files exist in shared drive + for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' exists in NetApp in its original size (${file.fileSize} bytes)`) + await verifyNetAppFileSizeByName( + file.fileName, + testData.caseId!, + file.fileSize, + ); + } }); }); diff --git a/e2e/pw/tests/egress-to-netapp-copy-large.spec.ts b/e2e/pw/tests/egress-to-netapp-copy-large.spec.ts index ce04dc7ec..6b3c84602 100644 --- a/e2e/pw/tests/egress-to-netapp-copy-large.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-copy-large.spec.ts @@ -4,6 +4,7 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; +import { verifyNetAppFileSizeByName } from "../helpers/transfer-verify"; test.describe("Egress to NetApp Copy - Large File (200MB)", () => { test.use({ testOptions: { fileSizeMb: 200, fileCount: 1 } }); @@ -62,5 +63,16 @@ test.describe("Egress to NetApp Copy - Large File (200MB)", () => { await activityLog.expandFileList(); await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); + + // Step 6: Confirm complete files exist in shared drive + for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' exists in NetApp in its original size (${file.fileSize} bytes)`) + await verifyNetAppFileSizeByName( + file.fileName, + testData.caseId!, + file.fileSize, + "Automation-Testing", + ); + } }); }); diff --git a/e2e/pw/tests/egress-to-netapp-copy-multifile.spec.ts b/e2e/pw/tests/egress-to-netapp-copy-multifile.spec.ts index 23ce9aec5..0e18407ea 100644 --- a/e2e/pw/tests/egress-to-netapp-copy-multifile.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-copy-multifile.spec.ts @@ -4,6 +4,7 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; +import { verifyNetAppFileSizeByName } from "../helpers/transfer-verify"; test.describe("Egress to NetApp Copy - Multiple Files (10MB x 3)", () => { test.use({ testOptions: { fileSizeMb: 10, fileCount: 3 } }); @@ -67,5 +68,16 @@ test.describe("Egress to NetApp Copy - Multiple Files (10MB x 3)", () => { await activityLog.expandFileList(); await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); + + // Step 6: Confirm complete files exist in shared drive + for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' exists in NetApp in its original size (${file.fileSize} bytes)`) + await verifyNetAppFileSizeByName( + file.fileName, + testData.caseId!, + file.fileSize, + "Automation-Testing", + ); + } }); }); diff --git a/e2e/pw/tests/egress-to-netapp-copy.spec.ts b/e2e/pw/tests/egress-to-netapp-copy.spec.ts index c98ed9014..90bc8d0e8 100644 --- a/e2e/pw/tests/egress-to-netapp-copy.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-copy.spec.ts @@ -4,6 +4,7 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; +import { verifyNetAppFileSizeByName } from "../helpers/transfer-verify"; test.describe("Egress to NetApp Copy", () => { test.use({ testOptions: { fileSizeMb: 100, fileCount: 1 } }); @@ -54,5 +55,16 @@ test.describe("Egress to NetApp Copy", () => { await activityLog.expandFileList(); await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); + + // Step 6: Confirm complete files exist in shared drive + for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' exists in NetApp in its original size (${file.fileSize} bytes)`) + await verifyNetAppFileSizeByName( + file.fileName, + testData.caseId!, + file.fileSize, + "Automation-Testing", + ); + } }); }); diff --git a/e2e/pw/tests/egress-to-netapp-move-default.spec.ts b/e2e/pw/tests/egress-to-netapp-move-default.spec.ts index 4cea05cca..fc00c2ea1 100644 --- a/e2e/pw/tests/egress-to-netapp-move-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-move-default.spec.ts @@ -4,6 +4,9 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; +import { verifyNetAppFileSizeByName, isFileInEgress } from "../helpers/transfer-verify"; +import { expect } from "@playwright/test"; + test.describe("Egress to NetApp Move (Default Mode)", () => { test("should move files from Egress to NetApp using existing case", async ({ @@ -72,5 +75,35 @@ test.describe("Egress to NetApp Move (Default Mode)", () => { await activityLog.expandFileList(); await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); + + // Step 9: Confirm complete files exist in shared drive + for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' exists in NetApp in its original size (${file.fileSize} bytes)`) + await verifyNetAppFileSizeByName( + file.fileName, + testData.caseId!, + file.fileSize, + ); + } + + // Step 10: Confirm files removed from Egress + for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' has been deleted from source '${testData.uploadPath}'.`) + await test.step( + `Verify file '${file.fileName}' is no longer present in Egress`, + async () => { + const exists = await isFileInEgress( + testData.workspace.id, + testData.sourceSubfolderId!, + file.fileName, + ); + + expect( + exists, + `File '${file.fileName}' still exists in Egress` + ).toBeFalsy(); + } + ); + } }); }); diff --git a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts index 40abe60e6..c46eaee0d 100644 --- a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts @@ -4,7 +4,7 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { TransferMaterialsTab } from "../pages/TransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; -import { isFileInEgress } from '../helpers/egress-api'; +import { verifyNetAppFileSizeByName, isFileInEgress } from "../helpers/transfer-verify"; import { expect } from "@playwright/test"; test.describe("Egress to NetApp Move (Default Mode)", () => { @@ -75,31 +75,28 @@ test.describe("Egress to NetApp Move (Default Mode)", () => { await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); - // Step 9: Confirm complete files appear in Shared Drive panel - await caseMgmt.switchToTab("transfer-materials"); + // Step 9: Confirm complete files exist in shared drive for (const file of testData.files) { - const fileSizeMb = file.fileSize / 1000000 - await transferTab.verifyNetAppFileSizeByExactName(file.fileName, fileSizeMb); + console.log(`\nVerifying file '${file.fileName}' exists in NetApp in its original size (${file.fileSize} bytes)`) + await verifyNetAppFileSizeByName( + file.fileName, + testData.caseId!, + file.fileSize, + ); } - // Step 10: Confirm files removed from Egres + // Step 10: Confirm files removed from Egress for (const file of testData.files) { - console.log(`\nVerifying file '${file.fileName}' has been deleted from source '${testData.uploadPath}'...`) + console.log(`\nVerifying file '${file.fileName}' has been deleted from source '${testData.uploadPath}'.`) await test.step( `Verify file '${file.fileName}' is no longer present in Egress`, async () => { const exists = await isFileInEgress( - testData.egressBaseUrl!, - testData.egressToken!, testData.workspace.id, testData.sourceSubfolderId!, - file.fileName + file.fileName, ); - if (!exists) { - console.log(`File '${file.fileName}' was not found in '${testData.uploadPath}'`) - } - expect( exists, `File '${file.fileName}' still exists in Egress` diff --git a/e2e/pw/tests/egress-to-netapp-move.spec.ts b/e2e/pw/tests/egress-to-netapp-move.spec.ts index 95302b623..16b9a1a77 100644 --- a/e2e/pw/tests/egress-to-netapp-move.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-move.spec.ts @@ -4,6 +4,8 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; +import { verifyNetAppFileSizeByName, isFileInEgress } from "../helpers/transfer-verify"; +import { expect } from "@playwright/test"; test.describe("Egress to NetApp Move", () => { test.use({ testOptions: { fileSizeMb: 100, fileCount: 1 } }); @@ -57,5 +59,36 @@ test.describe("Egress to NetApp Move", () => { await activityLog.expandFileList(); await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); + + // Step 6: Confirm complete files exist in shared drive + for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' exists in NetApp in its original size (${file.fileSize} bytes)`) + await verifyNetAppFileSizeByName( + file.fileName, + testData.caseId!, + file.fileSize, + "Automation-Testing", + ); + } + + // Step 7: Confirm files removed from Egress + for (const file of testData.files) { + console.log(`\nVerifying file '${file.fileName}' has been deleted from source '${testData.uploadPath}'.`) + await test.step( + `Verify file '${file.fileName}' is no longer present in Egress`, + async () => { + const exists = await isFileInEgress( + testData.workspace.id, + testData.sourceSubfolderId!, + file.fileName, + ); + + expect( + exists, + `File '${file.fileName}' still exists in Egress` + ).toBeFalsy(); + } + ); + } }); }); diff --git a/e2e/pw/tests/netapp-to-egress-copy-default.spec.ts b/e2e/pw/tests/netapp-to-egress-copy-default.spec.ts index 033dd21a7..ee63a5551 100644 --- a/e2e/pw/tests/netapp-to-egress-copy-default.spec.ts +++ b/e2e/pw/tests/netapp-to-egress-copy-default.spec.ts @@ -7,6 +7,9 @@ import { TransferDestinationPage } from "../pages/TransferDestinationPage"; import { ActivityLogTab } from "../pages/ActivityLogTab"; import { loadEnvConfig } from "../helpers/env-config"; import { NETAPP_FIXTURE_FILENAME } from "../helpers/constants"; +import { isFileInEgress } from "../helpers/transfer-verify"; +import { expect } from "@playwright/test"; + test.describe("NetApp to Egress Copy (Default Mode)", () => { test("should copy files from NetApp to Egress using existing case", async ({ @@ -71,5 +74,27 @@ test.describe("NetApp to Egress Copy (Default Mode)", () => { await activityLog.expandFileList(); await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); + + console.log(`Subfolder ID: ${testData.destinationSubfolderId}`) + + // Confirm files exist in Egress + for (const file of testData.files) { + console.log(`\nVerifying file '${NETAPP_FIXTURE_FILENAME}' exists in destination '2. Counsel only/${testData.uploadSubfolder}'.`) + await test.step( + `Verify file '${NETAPP_FIXTURE_FILENAME}' is present in Egress`, + async () => { + const exists = await isFileInEgress( + testData.workspace.id, + testData.destinationSubfolderId!, + NETAPP_FIXTURE_FILENAME, + ); + + expect( + exists, + `File '${NETAPP_FIXTURE_FILENAME}' could not be found in destination.` + ).toBeTruthy(); + } + ); + } }); }); diff --git a/e2e/pw/tests/netapp-to-egress-copy.spec.ts b/e2e/pw/tests/netapp-to-egress-copy.spec.ts index eb20ecb23..4ca14bb24 100644 --- a/e2e/pw/tests/netapp-to-egress-copy.spec.ts +++ b/e2e/pw/tests/netapp-to-egress-copy.spec.ts @@ -6,6 +6,8 @@ import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { TransferDestinationPage } from "../pages/TransferDestinationPage"; import { ActivityLogTab } from "../pages/ActivityLogTab"; +import { isFileInEgress } from "../helpers/transfer-verify"; +import { expect } from "@playwright/test"; test.describe("NetApp to Egress Copy", () => { test.use({ testOptions: { fileSizeMb: 100, fileCount: 1 } }); @@ -80,5 +82,7 @@ test.describe("NetApp to Egress Copy", () => { await activityLog.expandFileList(); await activityLog.downloadCsv(); await activityLog.verifyDownloadSuccess(); + + // No further verification added as currently we're not using a predictable file name }); }); From 4c2cffc448148d072592720a21c63956751a8b09 Mon Sep 17 00:00:00 2001 From: Lilach Date: Wed, 15 Jul 2026 15:40:15 +0100 Subject: [PATCH 11/18] update nightly pipeline run to use main branch --- devops-pipelines/move-playwright-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devops-pipelines/move-playwright-test.yml b/devops-pipelines/move-playwright-test.yml index 3915f7467..58498a83c 100644 --- a/devops-pipelines/move-playwright-test.yml +++ b/devops-pipelines/move-playwright-test.yml @@ -3,7 +3,7 @@ schedules: displayName: 'Weekday 1am UTC test' branches: include: - - task/FCT2-20209_Test-Move-functionality + - main always: true trigger: none From 2d5c210c166854ec8d3d6217d5e7c979b63688de Mon Sep 17 00:00:00 2001 From: Lilach Date: Wed, 15 Jul 2026 16:10:44 +0100 Subject: [PATCH 12/18] fix wrong netAppOperationName injection. Remove debug logging --- .gitignore | 1 - e2e/pw/helpers/egress-api.ts | 5 ----- e2e/pw/tests/egress-to-netapp-copy-default.spec.ts | 3 +-- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 97e584a41..10257ffc8 100644 --- a/.gitignore +++ b/.gitignore @@ -308,7 +308,6 @@ playwright-report/ ui-spa/playwright/test-results/ ui-spa/blob-report/ ui-spa/playwright/.cache/ -ui-spa/playwright-report/ ui-spa/playwright/integration-test-results.xml ui-spa/playwright/.nyc_output ui-spa/unit-test-results.xml diff --git a/e2e/pw/helpers/egress-api.ts b/e2e/pw/helpers/egress-api.ts index c8d415944..44221cf3f 100644 --- a/e2e/pw/helpers/egress-api.ts +++ b/e2e/pw/helpers/egress-api.ts @@ -361,8 +361,6 @@ export async function getUploadedFile( let i = 1 while (Date.now() - start < timeoutMs) { - console.log(` Attempt number ${i}...`) - const response = await fetch( `${baseUrl}/api/v1/workspaces/${workspaceId}/uploads/${uploadId}?view=full`, { @@ -390,9 +388,6 @@ export async function getUploadedFile( }; } - i++; - - console.log(` Retrying in ${retryDelay / 1000}s...`) await new Promise(r => setTimeout(r, retryDelay)); } diff --git a/e2e/pw/tests/egress-to-netapp-copy-default.spec.ts b/e2e/pw/tests/egress-to-netapp-copy-default.spec.ts index ab8fb78d7..512cf036a 100644 --- a/e2e/pw/tests/egress-to-netapp-copy-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-copy-default.spec.ts @@ -79,8 +79,7 @@ test.describe("Egress to NetApp Copy (Default Mode)", () => { await verifyNetAppFileSizeByName( file.fileName, testData.caseId!, - file.fileSize, - "Automation-Testing" + file.fileSize ); } }); From 61170d11f9cb8dad49e259eb6fc6b1a277f1e30a Mon Sep 17 00:00:00 2001 From: Lilach Date: Wed, 15 Jul 2026 17:08:54 +0100 Subject: [PATCH 13/18] remove unnecessary loop --- .../netapp-to-egress-copy-default.spec.ts | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/e2e/pw/tests/netapp-to-egress-copy-default.spec.ts b/e2e/pw/tests/netapp-to-egress-copy-default.spec.ts index ee63a5551..19f0246b1 100644 --- a/e2e/pw/tests/netapp-to-egress-copy-default.spec.ts +++ b/e2e/pw/tests/netapp-to-egress-copy-default.spec.ts @@ -77,24 +77,22 @@ test.describe("NetApp to Egress Copy (Default Mode)", () => { console.log(`Subfolder ID: ${testData.destinationSubfolderId}`) - // Confirm files exist in Egress - for (const file of testData.files) { - console.log(`\nVerifying file '${NETAPP_FIXTURE_FILENAME}' exists in destination '2. Counsel only/${testData.uploadSubfolder}'.`) - await test.step( - `Verify file '${NETAPP_FIXTURE_FILENAME}' is present in Egress`, - async () => { - const exists = await isFileInEgress( - testData.workspace.id, - testData.destinationSubfolderId!, - NETAPP_FIXTURE_FILENAME, - ); + // Confirm file exists in Egress + console.log(`\nVerifying file '${NETAPP_FIXTURE_FILENAME}' exists in destination '2. Counsel only/${testData.uploadSubfolder}'.`) + await test.step( + `Verify file '${NETAPP_FIXTURE_FILENAME}' is present in Egress`, + async () => { + const exists = await isFileInEgress( + testData.workspace.id, + testData.destinationSubfolderId!, + NETAPP_FIXTURE_FILENAME, + ); - expect( - exists, - `File '${NETAPP_FIXTURE_FILENAME}' could not be found in destination.` - ).toBeTruthy(); - } - ); - } + expect( + exists, + `File '${NETAPP_FIXTURE_FILENAME}' could not be found in destination.` + ).toBeTruthy(); + } + ); }); }); From 2b0a07ea8601574f6ec6b86123cee5e693041dbe Mon Sep 17 00:00:00 2001 From: Lilach Date: Thu, 16 Jul 2026 15:00:15 +0100 Subject: [PATCH 14/18] remove redundant check --- e2e/pw/pages/TransferMaterialsTab.ts | 68 ---------------------------- 1 file changed, 68 deletions(-) diff --git a/e2e/pw/pages/TransferMaterialsTab.ts b/e2e/pw/pages/TransferMaterialsTab.ts index 0bf754ab5..634f61306 100644 --- a/e2e/pw/pages/TransferMaterialsTab.ts +++ b/e2e/pw/pages/TransferMaterialsTab.ts @@ -241,72 +241,4 @@ export class TransferMaterialsTab `Timed out waiting for ${expectedCount} files (timeout: ${timeout}ms)`, ); } - - // ------------------- Added for Move Test----------------------------------- - // Confirm a file exists and is of the expected size (in MB) - async verifyNetAppFileSizeByExactName( - fileName: string, - expectedSizeMB: number, - ): Promise { - const row = this.page - .getByTestId("netapp-table-wrapper") - .locator("tbody tr", { hasText: fileName }); - - if ((await row.count()) === 0) { - throw new Error( - `A file named '${fileName}' was not found in the NetApp panel.` - ); - } - - const sizeCell = row.locator("td").nth(1); - await sizeCell.scrollIntoViewIfNeeded(); - - const sizeText = (await sizeCell.textContent())?.trim(); - - if (!sizeText) { - throw new Error( - `File '${fileName}' was found but its size could not be determined.` - ); - } - - // Convert to MB - const match = sizeText.match(/^(\d+(?:\.\d+)?)\s?(KB|MB|GB)$/i); - - if (!match) { - throw new Error(`Invalid size format for '${fileName}': ${sizeText}`); - } - - const value = Number.parseFloat(match[1]); - const unit = match[2].toUpperCase(); - - let actualSizeMB: number; - - switch (unit) { - case "KB": - actualSizeMB = value / 1000; - break; - case "MB": - actualSizeMB = value; - break; - case "GB": - actualSizeMB = value * 1000; - break; - default: - throw new Error(`Unsupported unit for '${fileName}': ${unit}`); - } - - // Compare (with tolerance) - const diff = Math.abs(actualSizeMB - expectedSizeMB); - const tolerance = expectedSizeMB * 0.01; - - const message = `File size check for '${fileName}'.\n` + - ` Expected: ${expectedSizeMB.toFixed(2)} MB\n` + - ` Actual: ${actualSizeMB} MB\n` + - ` Diff: ${diff.toFixed(2)} MB (should not exceed ${tolerance.toFixed(2)} MB)` - - console.log(`\n${message}`) - - expect(diff, message).toBeLessThanOrEqual(tolerance); - } - // -------------------------------------------------------------------------- } From f15e5b6b81300e58bb3fe52cd119f441b4c1aec5 Mon Sep 17 00:00:00 2001 From: Lilach Date: Thu, 16 Jul 2026 15:02:02 +0100 Subject: [PATCH 15/18] revert change to listEgressWorkspaceFilesByFolderId --- e2e/pw/helpers/egress-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/pw/helpers/egress-api.ts b/e2e/pw/helpers/egress-api.ts index 44221cf3f..12733d67e 100644 --- a/e2e/pw/helpers/egress-api.ts +++ b/e2e/pw/helpers/egress-api.ts @@ -433,7 +433,7 @@ export async function listEgressWorkspaceFilesByFolderId( try { for (let page = 0; page < maxPages; page++) { const skip = page * pageSize; - const url = `${baseUrl}/api/v1/workspaces/${workspaceId}/files?folder=${encodeURIComponent(folderId)}`; + const url = `${baseUrl}/api/v1/workspaces/${workspaceId}/files?view=full&skip=${skip}&limit=${pageSize}&folder=${encodeURIComponent(folderId)}`; const response = await fetch(url, { headers: { Authorization: `Basic ${token}` }, }); From c29d55367588e7043e89ffa5996a7f1e480c3e32 Mon Sep 17 00:00:00 2001 From: "fcteas\\KameshRaj.Rajendran" Date: Fri, 24 Jul 2026 12:42:19 +0100 Subject: [PATCH 16/18] FCT2-20588 update TransferMaterialsTabV1 to match deployed inline transfer flow The deployed Shared Drive transfer screen replaced the "Move selected" button and destination-tree page with an inline destination-inset control (" to ") and a "Transfer confirmation alert modal" whose Continue button is gated on an acknowledgement checkbox. - clickTransferControl: click the inset button (netapp-inset-text / egress-inset-text) by its " to " accessible-name prefix - confirmTransfer: tick the "I want to N file(s) to " checkbox, then Continue; drop the destination-tree flow - remove the now-unused TransferDestinationPage import and refresh the class doc comment --- e2e/pw/pages/TransferMaterialsTabV1.ts | 59 ++++++++++++++++---------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/e2e/pw/pages/TransferMaterialsTabV1.ts b/e2e/pw/pages/TransferMaterialsTabV1.ts index 860b8e7c4..4d6c4b9b3 100644 --- a/e2e/pw/pages/TransferMaterialsTabV1.ts +++ b/e2e/pw/pages/TransferMaterialsTabV1.ts @@ -1,6 +1,5 @@ import { expect, Locator } from "@playwright/test"; import { TransferMaterialsTabApi } from "./TransferMaterialsTabApi"; -import { TransferDestinationPage } from "./TransferDestinationPage"; import { BaseTransferMaterialsTab } from "./BaseTransferMaterialsTab"; // The new screen navigates to one of these routes on a failed or @@ -11,11 +10,11 @@ const TRANSFER_ERROR_ROUTE = /** * New-screen (v1) Transfer Materials page object, selected by * `getTransferMaterialsTab` when `TRANSFER_MATERIALS_V1` is on. Differs from the - * old screen: NetApp table renamed "shared drive"; Copy/Move are - * `Copy selected` / `Move selected` buttons; direction toggles via a - * `View Shared Drive` / `View Egress` link; no confirm modal (Copy/Move navigate - * to a destination-tree page, driven by `TransferDestinationPage`); errors use - * the routes above. Egress-side helpers come from `BaseTransferMaterialsTab`. + * old screen: NetApp table renamed "shared drive"; direction toggles via a + * `View Shared Drive` / `View Egress` link; errors use the routes above. + * Copy/Move is driven from the destination panel's inline inset button + * (` to `) and confirmed via the "Transfer confirmation alert + * modal" ("Continue"). Egress-side helpers come from `BaseTransferMaterialsTab`. */ export class TransferMaterialsTabV1 extends BaseTransferMaterialsTab @@ -30,11 +29,20 @@ export class TransferMaterialsTabV1 .first(); } - /** Click the shared Copy/Move control. Renders in a top and bottom bar (target - * the first); Move only appears when the source is Egress. */ - private async clickTransferControl(action: "Copy" | "Move"): Promise { + /** Click the inline Copy/Move control in the destination panel's inset. Each + * panel carries its own inset: the shared-drive (netapp) inset drives + * Egress -> Shared Drive, the egress inset drives Shared Drive -> Egress. The + * button's accessible name is ` to ` (e.g. + * "Move to Automation-Testing"), so match on the ` to ` prefix. */ + private async clickTransferControl( + action: "Copy" | "Move", + direction: "egressToNetApp" | "netAppToEgress", + ): Promise { + const inset = + direction === "egressToNetApp" ? "netapp-inset-text" : "egress-inset-text"; await this.page - .getByRole("button", { name: `${action} selected` }) + .getByTestId(inset) + .getByRole("button", { name: new RegExp(`^${action} to `) }) .first() .click(); } @@ -116,24 +124,29 @@ export class TransferMaterialsTabV1 async selectAction( action: "Copy" | "Move", - _direction?: "egressToNetApp" | "netAppToEgress", + direction: "egressToNetApp" | "netAppToEgress" = "egressToNetApp", ): Promise { - // New screen: one shared control for both directions (Move renders only when - // the source is Egress), so the direction is implied by the current source. - await this.clickTransferControl(action); + // The transfer target is the folder currently shown in the destination + // panel (its inset reads " to "); the direction selects + // which panel's inset holds the control. + await this.clickTransferControl(action, direction); } /** - * Confirm the transfer. No modal on the new screen: Copy/Move already - * navigated to the destination tree — pick the first selectable folder (the - * connected root) and click the ` to ` button. `action` must - * match the Copy/Move just initiated (the button label depends on it). + * Confirm the transfer. Clicking Copy/Move opens a "Transfer confirmation + * alert modal" (dialog, named via aria-labelledby) — there is no + * destination-tree page. "Continue" stays disabled until the "I want to + * N file(s) to " acknowledgement checkbox is ticked + * (present on every transfer, not just duplicates), so check it first, then + * Continue. */ - async confirmTransfer(action: "Copy" | "Move"): Promise { - const destination = new TransferDestinationPage(this.page); - await destination.waitForLoaded(); - await destination.selectFirstSelectableFolder(); - await destination.confirm(action); + async confirmTransfer(_action: "Copy" | "Move"): Promise { + const modal = this.page.getByRole("dialog", { + name: "Transfer confirmation alert modal", + }); + await modal.waitFor({ state: "visible", timeout: 30_000 }); + await modal.getByRole("checkbox").check(); + await modal.getByRole("button", { name: "Continue" }).click(); } /** From 6abe52cfc605e98870419fa76418f83f8c6eaa2d Mon Sep 17 00:00:00 2001 From: "fcteas\\KameshRaj.Rajendran" Date: Mon, 27 Jul 2026 10:22:27 +0000 Subject: [PATCH 17/18] FCT2-20588 Check Egress source file removal by file id --- e2e/pw/helpers/egress-api.ts | 31 ++++++++++++++++ e2e/pw/helpers/transfer-verify.ts | 37 ++++++++++++++++++- e2e/pw/scripts/upload-to-workspace.ts | 4 +- .../egress-to-netapp-move-default.spec.ts | 8 ++-- ...gress-to-netapp-move-large-default.spec.ts | 10 ++--- e2e/pw/tests/egress-to-netapp-move.spec.ts | 8 ++-- e2e/pw/tests/seed-netapp-fixture.setup.ts | 6 +-- 7 files changed, 84 insertions(+), 20 deletions(-) diff --git a/e2e/pw/helpers/egress-api.ts b/e2e/pw/helpers/egress-api.ts index 12733d67e..05f8cc94f 100644 --- a/e2e/pw/helpers/egress-api.ts +++ b/e2e/pw/helpers/egress-api.ts @@ -476,6 +476,37 @@ export async function listEgressWorkspaceFilesByFolderId( return []; } +/** + * Checks whether a specific file still exists in a workspace by its file id. + * GET /api/v1/workspaces/{workspaceId}/files/{fileId}: 200 => exists, + * 404 => gone. (Same endpoint the backend uses to open a document stream — + * see EgressRequestFactory.GetWorkspaceDocumentRequest.) + * + * Unlike listing a folder, this is a single deterministic call keyed on the + * exact id, so it needs no retry/settle — ideal for asserting a Move removed + * its source. It's fast when the file is gone (immediate 404 instead of the + * folder-listing retries) and it won't mask a genuine delete miss the way a + * retrying listing would. + */ +export async function egressFileExistsById( + baseUrl: string, + token: string, + workspaceId: string, + fileId: string +): Promise { + const response = await fetch( + `${baseUrl}/api/v1/workspaces/${workspaceId}/files/${fileId}`, + { headers: { Authorization: `Basic ${token}` } } + ); + if (response.status === 404) return false; + if (response.ok) return true; + + const text = await response.text(); + throw new Error( + `Egress file lookup failed (${response.status}) for '${fileId}': ${text.slice(0, 200)}` + ); +} + /** * Best-effort bulk file delete. Logs and swallows errors so teardown never * fails a passing test — the dated subfolder + manual sweep acts as a diff --git a/e2e/pw/helpers/transfer-verify.ts b/e2e/pw/helpers/transfer-verify.ts index 407ae8141..a724399d9 100644 --- a/e2e/pw/helpers/transfer-verify.ts +++ b/e2e/pw/helpers/transfer-verify.ts @@ -1,5 +1,9 @@ import { loadEnvConfig } from "../helpers/env-config"; -import { listEgressWorkspaceFilesByFolderId, authenticateEgress } from "./egress-api"; +import { + listEgressWorkspaceFilesByFolderId, + egressFileExistsById, + authenticateEgress, +} from "./egress-api"; import { getAzureADToken } from "./auth-api"; import { expect } from "@playwright/test"; @@ -97,4 +101,33 @@ export async function isFileInEgress( ); return files.some(f => f.fileName === fileName); -} \ No newline at end of file +} + +/** + * Whether a file still exists in Egress, checked by its exact file id rather + * than by listing a folder and matching names. Prefer this for "the Move + * removed its source" assertions: it's a single deterministic call (no + * folder-listing retries), so it returns immediately when the file is gone and + * doesn't mask a genuine delete miss. Only usable where the file id is known + * (e.g. the source file captured at upload) — the copy-destination existence + * check has no id and must still list by name. + */ +export async function isFileInEgressById( + workspaceId: string, + fileId: string, + egressToken?: string | undefined, +): Promise { + if (!egressToken) { + egressToken = await authenticateEgress( + config.egressBaseUrl, + config.egressServiceAccountAuth, + ); + } + + return egressFileExistsById( + config.egressBaseUrl, + egressToken, + workspaceId, + fileId, + ); +} diff --git a/e2e/pw/scripts/upload-to-workspace.ts b/e2e/pw/scripts/upload-to-workspace.ts index 03dd59ba2..7135007da 100644 --- a/e2e/pw/scripts/upload-to-workspace.ts +++ b/e2e/pw/scripts/upload-to-workspace.ts @@ -67,7 +67,7 @@ async function main() { .replace(/[:.]/g, "-") .slice(0, 19); const fileName = `generated-${fileSizeMb}MB-${timestamp}-file${i}.txt`; - const file = await uploadFile( + const uploadId = await uploadFile( config.egressBaseUrl, token, workspaceId, @@ -75,7 +75,7 @@ async function main() { fileName, uploadPath ); - uploaded.push({ id: file.id, fileName: file.fileName }); + uploaded.push({ id: uploadId, fileName }); } console.log("\n=== Done ==="); diff --git a/e2e/pw/tests/egress-to-netapp-move-default.spec.ts b/e2e/pw/tests/egress-to-netapp-move-default.spec.ts index fc00c2ea1..06e5dd2d3 100644 --- a/e2e/pw/tests/egress-to-netapp-move-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-move-default.spec.ts @@ -4,7 +4,7 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; -import { verifyNetAppFileSizeByName, isFileInEgress } from "../helpers/transfer-verify"; +import { verifyNetAppFileSizeByName, isFileInEgressById } from "../helpers/transfer-verify"; import { expect } from "@playwright/test"; @@ -92,10 +92,10 @@ test.describe("Egress to NetApp Move (Default Mode)", () => { await test.step( `Verify file '${file.fileName}' is no longer present in Egress`, async () => { - const exists = await isFileInEgress( + const exists = await isFileInEgressById( testData.workspace.id, - testData.sourceSubfolderId!, - file.fileName, + file.fileId, + testData.egressToken, ); expect( diff --git a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts index c46eaee0d..c0719bbd7 100644 --- a/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-move-large-default.spec.ts @@ -4,7 +4,7 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { TransferMaterialsTab } from "../pages/TransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; -import { verifyNetAppFileSizeByName, isFileInEgress } from "../helpers/transfer-verify"; +import { verifyNetAppFileSizeByName, isFileInEgressById } from "../helpers/transfer-verify"; import { expect } from "@playwright/test"; test.describe("Egress to NetApp Move (Default Mode)", () => { @@ -59,7 +59,7 @@ test.describe("Egress to NetApp Move (Default Mode)", () => { await transferTab.selectAction("Move"); // Step 5: Confirm transfer - await transferTab.confirmTransfer(); + await transferTab.confirmTransfer("Move"); // Step 6: Wait for transfer to complete (10 min timeout) await transferTab.waitForTransferComplete(600_000); @@ -91,10 +91,10 @@ test.describe("Egress to NetApp Move (Default Mode)", () => { await test.step( `Verify file '${file.fileName}' is no longer present in Egress`, async () => { - const exists = await isFileInEgress( + const exists = await isFileInEgressById( testData.workspace.id, - testData.sourceSubfolderId!, - file.fileName, + file.fileId, + testData.egressToken, ); expect( diff --git a/e2e/pw/tests/egress-to-netapp-move.spec.ts b/e2e/pw/tests/egress-to-netapp-move.spec.ts index 16b9a1a77..707433fc6 100644 --- a/e2e/pw/tests/egress-to-netapp-move.spec.ts +++ b/e2e/pw/tests/egress-to-netapp-move.spec.ts @@ -4,7 +4,7 @@ import { SearchResultsPage } from "../pages/SearchResultsPage"; import { CaseManagementPage } from "../pages/CaseManagementPage"; import { getTransferMaterialsTab } from "../pages/getTransferMaterialsTab"; import { ActivityLogTab } from "../pages/ActivityLogTab"; -import { verifyNetAppFileSizeByName, isFileInEgress } from "../helpers/transfer-verify"; +import { verifyNetAppFileSizeByName, isFileInEgressById } from "../helpers/transfer-verify"; import { expect } from "@playwright/test"; test.describe("Egress to NetApp Move", () => { @@ -77,10 +77,10 @@ test.describe("Egress to NetApp Move", () => { await test.step( `Verify file '${file.fileName}' is no longer present in Egress`, async () => { - const exists = await isFileInEgress( + const exists = await isFileInEgressById( testData.workspace.id, - testData.sourceSubfolderId!, - file.fileName, + file.fileId, + testData.egressToken, ); expect( diff --git a/e2e/pw/tests/seed-netapp-fixture.setup.ts b/e2e/pw/tests/seed-netapp-fixture.setup.ts index a8719dc7c..e7597b84e 100644 --- a/e2e/pw/tests/seed-netapp-fixture.setup.ts +++ b/e2e/pw/tests/seed-netapp-fixture.setup.ts @@ -58,7 +58,7 @@ setup("seed lcc-e2e-fixture-source.txt to NetApp", async ({ page }) => { SEED_PARENT, SEED_SUBFOLDER, ); - const uploaded = await uploadFile( + const uploadId = await uploadFile( config.egressBaseUrl, egressToken, config.defaultWorkspaceId, @@ -137,12 +137,12 @@ setup("seed lcc-e2e-fixture-source.txt to NetApp", async ({ page }) => { await transferTab.selectNetAppFileByExactName(NETAPP_FIXTURE_FILENAME); console.log("Deleting Egress-side seed source..."); - if (uploaded.id) { + if (uploadId) { await deleteFiles( config.egressBaseUrl, egressToken, config.defaultWorkspaceId, - [uploaded.id], + [uploadId], ); } From c57600e31303f0d20caf4e9377aafe350d27643a Mon Sep 17 00:00:00 2001 From: "fcteas\\KameshRaj.Rajendran" Date: Mon, 27 Jul 2026 12:22:56 +0100 Subject: [PATCH 18/18] FCT2-20588 Remove unused imports and dead assignment flagged by SonarQube --- e2e/pw/fixtures/setup-helper-default.ts | 2 +- e2e/pw/fixtures/setup-helper.ts | 2 +- e2e/pw/fixtures/test-fixtures-register-case.ts | 2 +- e2e/pw/helpers/egress-api.ts | 5 ----- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/e2e/pw/fixtures/setup-helper-default.ts b/e2e/pw/fixtures/setup-helper-default.ts index 1f1ba0bd2..f5db3581d 100644 --- a/e2e/pw/fixtures/setup-helper-default.ts +++ b/e2e/pw/fixtures/setup-helper-default.ts @@ -9,7 +9,7 @@ import { import { TacticalLoginPage } from "../pages/TacticalLoginPage"; import { AzureADLoginPage } from "../pages/AzureADLoginPage"; import { CaseSearchPage } from "../pages/CaseSearchPage"; -import type { TestSetupResult, UploadedFile } from "../helpers/types"; +import type { TestSetupResult } from "../helpers/types"; export interface DefaultSetupOptions { fileSizeMb?: number; diff --git a/e2e/pw/fixtures/setup-helper.ts b/e2e/pw/fixtures/setup-helper.ts index 596cbf8a3..504889f22 100644 --- a/e2e/pw/fixtures/setup-helper.ts +++ b/e2e/pw/fixtures/setup-helper.ts @@ -13,7 +13,7 @@ import { registerCase } from "../helpers/case-api"; import { TacticalLoginPage } from "../pages/TacticalLoginPage"; import { AzureADLoginPage } from "../pages/AzureADLoginPage"; import { CaseSearchPage } from "../pages/CaseSearchPage"; -import type { TestSetupResult, UploadedFile } from "../helpers/types"; +import type { TestSetupResult } from "../helpers/types"; export interface SetupOptions { fileSizeMb?: number; diff --git a/e2e/pw/fixtures/test-fixtures-register-case.ts b/e2e/pw/fixtures/test-fixtures-register-case.ts index fada1af80..d28416ad4 100644 --- a/e2e/pw/fixtures/test-fixtures-register-case.ts +++ b/e2e/pw/fixtures/test-fixtures-register-case.ts @@ -9,7 +9,7 @@ import { getUploadedFile, } from "../helpers/egress-api"; import { REGISTER_CASE_NETAPP_FOLDER } from "../helpers/constants"; -import type { TestSetupResult, UploadedFile } from "../helpers/types"; +import type { TestSetupResult } from "../helpers/types"; import { STATE_FILE, type RegisterCaseSharedState, diff --git a/e2e/pw/helpers/egress-api.ts b/e2e/pw/helpers/egress-api.ts index 05f8cc94f..6a7691772 100644 --- a/e2e/pw/helpers/egress-api.ts +++ b/e2e/pw/helpers/egress-api.ts @@ -335,11 +335,6 @@ export async function uploadFile( ); } - // Egress returns the file record on completion. Fall back to uploadId if - // the response shape changes so callers that need an id for teardown - // always get something to work with. - const completeData = await completeResponse.json().catch(() => ({})); - console.log(` Upload complete: ${uploadId}`); return uploadId; }