diff --git a/devops-pipelines/move-playwright-test.yml b/devops-pipelines/move-playwright-test.yml index fdd380bcb..ca077ca08 100644 --- a/devops-pipelines/move-playwright-test.yml +++ b/devops-pipelines/move-playwright-test.yml @@ -26,10 +26,16 @@ jobs: variables: - group: lacc-automated-testing-${{ parameters.environment }} - group: lacc-backend-config-${{ parameters.environment }} + # Pin Node.js to v22 because Node.js v24 has a memory retention issue when + # using Undici multipart FormData uploads. Chunked uploads cause RSS to grow + # by approximately the total uploaded file size. This does not reproduce on + # Node.js v22. Revisit when the Node.js/Undici issue is resolved. - name: nodeVersion - value: "24.x" + value: "22.x" - name: workingDir value: "$(System.DefaultWorkingDirectory)/e2e/pw" + - name: transferMaterialsV1 # Staging renders the v1 ("Shared Drive") transfer screen for the E2E user. + value: ${{ ne(parameters.environment, 'dev') }} steps: - task: UseNode@1 @@ -54,7 +60,7 @@ jobs: displayName: "Run E2E Tests" env: CI: true - TRANSFER_MATERIALS_V1: true # Staging renders the v1 ("Shared Drive") transfer screen for the E2E user + TRANSFER_MATERIALS_V1: $(transferMaterialsV1) TEST_FILE_SIZE_MB: ${{ parameters.testFileSizeMb }} BASE_URL: $(RedirectUrlLccUi) CMS_LOGIN_PAGE: "$(LccApiBaseUrl)/api/tactical/login" diff --git a/devops-pipelines/move-soak-test.yml b/devops-pipelines/move-soak-test.yml new file mode 100644 index 000000000..58cc54baa --- /dev/null +++ b/devops-pipelines/move-soak-test.yml @@ -0,0 +1,133 @@ +schedules: + - cron: "0 1 * * Sun" + displayName: "Sun 1am UTC test" + branches: + include: + - main + always: true + +trigger: none + +parameters: + - name: environment + type: string + default: dev + - name: agentPool + type: string + default: "LACC PreProd Pool" + +variables: + - group: lacc-automated-testing-${{ parameters.environment }} + - group: lacc-backend-config-${{ parameters.environment }} + - name: transferMaterialsV1 # Staging renders the v1 ("Shared Drive") transfer screen for the E2E user. + value: ${{ ne(parameters.environment, 'dev') }} + +jobs: + - job: runMoveSoakTest + displayName: "Run Move Soak Test with Playwright" + pool: ${{ parameters.agentPool }} + timeoutInMinutes: 150 + variables: + # Pin Node.js to v22 because Node.js v24 has a memory retention issue when + # using Undici multipart FormData uploads. Chunked uploads cause RSS to grow + # by approximately the total uploaded file size. This does not reproduce on + # Node.js v22. Revisit when the Node.js/Undici issue is resolved. + - name: nodeVersion + value: "22.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: | + npm run move-soak + workingDirectory: $(workingDir) + displayName: "Run Move Soak Test Scenarios" + env: + TRANSFER_MATERIALS_V1: $(transferMaterialsV1) + 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)/move-soak/playwright-report/*-e2e-test-report.xml" + publishRunAttachments: false + condition: succeededOrFailed() + + - publish: "$(workingDir)/move-soak/playwright-report" + artifact: "$(System.JobId)-e2e-playwright-report" + displayName: "Publish Report Artifact" + condition: succeededOrFailed() + + - job: cleanupTestFiles + displayName: "Cleanup Test Files from Egress and Shared Drive" + dependsOn: runMoveSoakTest + condition: succeededOrFailed() + pool: ${{ parameters.agentPool }} + steps: + - script: | + if command -v pwsh &> /dev/null; then + echo "PowerShell already installed. Skipping installation." + else + echo "Installing PowerShell..." + bash '$(System.DefaultWorkingDirectory)/devops-pipelines/scripts/installPowershell.sh' + echo "Powershel version: $(pwsh -v)" + fi + displayName: "Install PowerShell" + + - task: PowerShell@2 + displayName: "Cleanup existingCaseAutomation NetApp folder" + inputs: + filePath: "$(System.DefaultWorkingDirectory)/devops-pipelines/scripts/test-automation-cleanup/Remove-SharedDriveFolderContent.ps1" + arguments: > + -ClientId $(CallingAppValidAudience) + -TenantId $(TenantId) + -AadUsername $(AadUserName) + -AadPassword $(AadUserPassword) + -CaseId $(E2eTestsCaseId) + -FolderPath "existingCaseAutomation" + -BaseUrl $(LccApiBaseUrl) + -ExcludePatterns "lcc-e2e-fixture-source" + -Force + + - task: PowerShell@2 + displayName: "Clear ExistingCaseAutomation Egress folder" + condition: succeededOrFailed() + inputs: + filePath: "$(System.DefaultWorkingDirectory)/devops-pipelines/scripts/test-automation-cleanup/Clear-EgressFolder.ps1" + arguments: > + -BaseUrl "$(EgressOptionsUrl)" + -ServiceAccountAuth "$(EgressServiceAccountAuth)" + -WorkspaceId "$(E2eEgressWorkspaceId)" + -FolderName "1. ABEs for Transcript" + -Force diff --git a/devops-pipelines/scripts/test-automation-cleanup/Clear-EgressFolder.ps1 b/devops-pipelines/scripts/test-automation-cleanup/Clear-EgressFolder.ps1 index 0121c39ad..6b5a6f864 100644 --- a/devops-pipelines/scripts/test-automation-cleanup/Clear-EgressFolder.ps1 +++ b/devops-pipelines/scripts/test-automation-cleanup/Clear-EgressFolder.ps1 @@ -23,17 +23,63 @@ Import-Module (Join-Path $PSScriptRoot 'EgressCleanupHelperModule.psm1') # ============================================================ Write-Host "[1/3] Authenticating to Egress..." -ForegroundColor Yellow -try { - $AuthHeader = Connect-EgressServiceAccount ` - -BaseUrl $BaseUrl ` - -AuthToken $ServiceAccountAuth -} -catch { - Write-Error $_.Exception.Message - exit 1 -} +$maxAttempts = 3 +$retryDelaySeconds = 15 + +for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + try { + + Write-Host "Authentication attempt $attempt/$maxAttempts" -Write-Host " [OK] Authenticated" -ForegroundColor Green + $AuthHeader = Connect-EgressServiceAccount ` + -BaseUrl $BaseUrl ` + -AuthToken $ServiceAccountAuth + + Write-Host " [OK] Authenticated" -ForegroundColor Green + break + } + catch { + Write-Warning "Authentication attempt $attempt failed." + + Write-Warning "Exception type: $($_.Exception.GetType().FullName)" + Write-Warning "Exception message: $($_.Exception.Message)" + + if ($_.Exception.Response) { + try { + $statusCode = [int]$_.Exception.Response.StatusCode + $statusDescription = $_.Exception.Response.StatusDescription + + Write-Warning "HTTP Status: $statusCode $statusDescription" + } + catch {} + + try { + $responseStream = $_.Exception.Response.GetResponseStream() + + if ($responseStream) { + $reader = New-Object System.IO.StreamReader($responseStream) + $responseBody = $reader.ReadToEnd() + + if (-not [string]::IsNullOrWhiteSpace($responseBody)) { + Write-Warning "Response body:" + Write-Warning $responseBody + } + } + } + catch { + Write-Warning "Unable to read error response body." + } + } + + if ($attempt -eq $maxAttempts) { + Write-Error "Authentication failed after $maxAttempts attempts." + exit 1 + } + + Write-Host "Retrying in $retryDelaySeconds seconds..." + Start-Sleep -Seconds $retryDelaySeconds + } +} # ============================================================ # LIST FILES diff --git a/devops-pipelines/templates/e2e-playwright-test-job.yml b/devops-pipelines/templates/e2e-playwright-test-job.yml index 80ac1d199..b61cc5d01 100644 --- a/devops-pipelines/templates/e2e-playwright-test-job.yml +++ b/devops-pipelines/templates/e2e-playwright-test-job.yml @@ -19,8 +19,12 @@ jobs: variables: - group: lacc-automated-testing-${{ parameters.environment }} - group: lacc-backend-config-${{ parameters.environment }} + # Pin Node.js to v22 because Node.js v24 has a memory retention issue when + # using Undici multipart FormData uploads. Chunked uploads cause RSS to grow + # by approximately the total uploaded file size. This does not reproduce on + # Node.js v22. Revisit when the Node.js/Undici issue is resolved. - name: nodeVersion - value: "24.x" + value: "22.x" - name: workingDir value: "$(System.DefaultWorkingDirectory)/e2e/pw" - name: transferMaterialsV1 # Staging renders the v1 ("Shared Drive") transfer screen for the E2E user. diff --git a/e2e/pw/move-soak/playwright.config.ts b/e2e/pw/move-soak/playwright.config.ts index 6c4c9eb64..d7036cf44 100644 --- a/e2e/pw/move-soak/playwright.config.ts +++ b/e2e/pw/move-soak/playwright.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ reporter: [ ["list"], ["html", { outputFolder: `./playwright-report/${runId}` }], - ["junit", { outputFile: `./playwright-report/e2e-test-report-${runId}.xml` }], + ["junit", { outputFile: `./playwright-report/${runId}-e2e-test-report.xml` }], ], timeout: 120_000, expect: { timeout: 500_000 },