diff --git a/azure-pipelines-automated-testing.yml b/azure-pipelines-automated-testing.yml new file mode 100644 index 00000000..ba2a2502 --- /dev/null +++ b/azure-pipelines-automated-testing.yml @@ -0,0 +1,474 @@ +trigger: + branches: + include: [ develop, release/* ] + paths: + exclude: + - deployment/* + include: + - "*" +pr: + branches: + include: [ '*' ] + paths: + exclude: + - deployment/* + include: + - "*" + +resources: + containers: + - container: node + image: packagerepository.sagaws.net:8082/safelite/node-build:18 + - container: awscli + image: packagerepository.sagaws.net:8082/safelite/awscli2-build:3.9 + repositories: + - repository: AzureDevOps + type: github + name: Safelite/AzureDevOps + endpoint: Safelite + ref: refs/tags/t5.5.40 + +variables: + - group: Digital-Infrastructure + - group: ISS-BuildBranches + - name: dockerImageName + value: 'playwright-tests' + - name: imageTag + value: '$(Build.BuildId)' + - name: totalShards + value: 4 + +stages: + # PR's + - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + - stage: TestPr + displayName: Run Tests For PullRequest + jobs: + - template: templates/digital/vue-jest-run-unit-tests.yml@AzureDevOps + parameters: + nodeContainer: node + npmLocation: $(Build.SourcesDirectory) + testResultsFile: junit.xml + summaryFileLocation: $(Build.SourcesDirectory)/coverage/cobertura-coverage.xml + - job: playwright_tests + continueOnError: true + strategy: + matrix: + shard1: + shardNumber: 1 + shard2: + shardNumber: 2 + shard3: + shardNumber: 3 + shard4: + shardNumber: 4 + + steps: + - task: Docker@2 + displayName: 'Build Docker Image' + inputs: + command: build + dockerfile: Dockerfile.playwright + repository: $(dockerImageName) + tags: $(imageTag) + arguments: '--no-cache --pull' + + - script: | + branch_name=$(System.PullRequest.SourceBranch) + echo "Retrieved branch name: '$branch_name'" + jira_card_number="${branch_name##*/}" + echo "Extracted JIRA Card number: '$jira_card_number'" + # Create container and run tests + container_id=$(docker create \ + --ipc=host \ + -e CCIS_API_AUTH=$(CCIS_API_AUTH) \ + -e BASE_URL=$(BASE_URL) \ + -e CCIS_API_URL=$(CCIS_API_URL) \ + -e ADMIN_SERVICE_API_URL=$(ADMIN_SERVICE_API_URL) \ + -e SHARD=$(shardNumber) \ + -e CI=true \ + -e NODE_ENV=$(NODE_ENV) \ + $(dockerImageName):$(imageTag) \ + npx concurrently -k -n "server,playwright"\ + "sed -i \"s|^process\.env\.VUE_APP_CONSUMER_CF_DISTRO = .*|process\.env\.VUE_APP_CONSUMER_CF_DISTRO='https://digitalapi.test.safelite.io'|\" \"./vue.config.js\" && echo \"Updated config file to use TEST APIs\" && npm run serve -- --port=8080"\ + "npx wait-on http://localhost:8080 && npm run test:playwright -- --shard=$(shardNumber)/$(totalShards) --reporter=list,blob --grep \"@$jira_card_number\"") + + # Start container and stream logs + echo "Starting tests for shard $(shardNumber)..." + docker start -a $container_id + + # Create directory for test results + echo "Creating test results directory..." + mkdir -p $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber) + + # Copy test results from container + echo "Copying test results..." + docker cp $container_id:/app/blob-report/. $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)/ + + # Remove container + echo "Cleaning up container..." + docker rm $container_id + + # Check if tests failed + if [ $? -ne 0 ]; then + echo "Tests failed in shard $(shardNumber)!" + exit 1 + fi + displayName: 'Run Playwright Tests - Shard $(shardNumber)' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Reports - Shard $(shardNumber)' + condition: always() + inputs: + targetPath: '$(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)' + artifact: 'playwright-report-shard-$(shardNumber)' + publishLocation: 'pipeline' + + - script: | + docker rmi $(dockerImageName):$(imageTag) -f + displayName: 'Cleanup Docker Image' + condition: always() + + - job: download_and_merge_reports + dependsOn: playwright_tests + timeoutInMinutes: 8 + cancelTimeoutInMinutes: 10 + steps: + - task: DownloadPipelineArtifact@2 + inputs: + targetPath: '$(System.DefaultWorkingDirectory)/playwright-reports' + - task: Docker@2 + displayName: 'Build Docker Image' + inputs: + command: build + dockerfile: Dockerfile.playwright + repository: $(dockerImageName) + tags: $(imageTag) + arguments: '--no-cache --pull' + - bash: | + branch_name=$(System.PullRequest.SourceBranch) + echo "Retrieved branch name: '$branch_name'" + jira_card_number="${branch_name##*/}" + echo "Extracted JIRA Card number: '$jira_card_number'" + # Create container for jira writeback + container_id=$(docker create \ + --ipc=host \ + -e JIRA_SERVER=$(JIRA_SERVER) \ + -e JIRA_USERNAME=$(JIRA_USERNAME) \ + -e JIRA_API_KEY=$(JIRA_API_KEY) \ + -e jira_card_number="$jira_card_number" \ + $(dockerImageName):$(imageTag) \ + bash -c "echo \"Moving Playwright reports out of subfolders...\" && + find ./playwright-reports/ -mindepth 2 -type f -exec mv {} ./playwright-reports/ \; && + echo \"Merging reports...\" && + npx playwright merge-reports --reporter=ortoni-report ./playwright-reports + echo \"Contents of ortoni-report:\" && ls ./ortoni-report && + echo 'Current dir: ' && pwd + echo 'Contents of current dir: ' && ls + echo 'Contents of /app/devops/scripts' && ls /app/devops/scripts + echo \"Writing report to Jira card '$jira_card_number'...\" && + /app/devops/scripts/jira_writeback.sh add_comment \"$jira_card_number\" /app/ortoni-report/ortoni-report.html \"AUTOMATED TEST RUN: $(date)\" ") + + # Start container and stream logs + echo "Starting merge" + docker start -a $container_id + + # Create directory for test results + echo "Creating test results directory..." + mkdir -p $(System.DefaultWorkingDirectory)/ortoni-report + + # Copy test results from container + echo "Copying test results..." + docker cp $container_id:/app/ortoni-report/. $(System.DefaultWorkingDirectory)/ortoni-report + # docker cp $container_id:/app/test-results/junit_results.xml $(System.DefaultWorkingDirectory) + # TODO: Update to include junit results if necessary + + # Remove container + echo "Cleaning up container..." + docker rm $container_id + env: + JIRA_API_KEY: $(JIRA_API_KEY) + displayName: merge_and_publish_results_to_jira + + # TODO: Enable later when working on PR gate + # - task: PublishTestResults@2 + # displayName: 'Publish test results' + # inputs: + # searchFolder: 'test-results' + # testResultsFormat: 'JUnit' + # testResultsFiles: 'junit_results.xml' + # mergeTestResults: true + # failTaskOnFailedTests: false + # testRunTitle: 'Playwright Tests' + # condition: succeededOrFailed() + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Merged Report' + condition: always() + inputs: + targetPath: '$(System.DefaultWorkingDirectory)/ortoni-report' + artifact: 'playwright-merged-report' + publishLocation: 'pipeline' + - script: | + docker rmi $(dockerImageName):$(imageTag) -f + displayName: 'Cleanup Docker Image' + condition: always() + + - ${{ else }}: + # Dev Build/Deploy + - stage: Dev + condition: eq(variables['Build.SourceBranch'], variables['dev-branch'] ) + variables: + - group: ISS-Dev + jobs: + - deployment: devBuildDeployment + displayName: Build and Deploy ISS - Dev + environment: digitalCloud-dev + container: node + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - checkout: self + clean: true + - task: CopyFiles@2 + inputs: + SourceFolder: 'public/apple-pay/dev' + Contents: '**' + TargetFolder: 'public/.well-known' + CleanTargetFolder: true + - task: DeleteFiles@1 + inputs: + SourceFolder: 'public/apple-pay' + Contents: '**' + RemoveSourceFolder: true + - template: templates/digital/step-build-vue.yml@AzureDevOps + parameters: + buildOutputDir: dist + environment: Dev + - template: templates/digital/step-deploy-vue.yml@AzureDevOps + parameters: + artifactName: vueDistDev + awsProfile: $(devDeploymentProfile) + deployBuckets: + safelite-dev-iss-us-east-2: + clearFolder: true + deployFolder: '' + region: us-east-2 + safelite-dev-iss-us-east-1: + clearFolder: true + deployFolder: '' + region: us-east-1 + appDeployVariables: + __VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__) + __VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__) + __VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__) + __VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__) + __VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__) + indexDeployVariables: + __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) + __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) + __VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__) + - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps + parameters: + awsCliContainer: awscli + distributionId: $(cfDistributionId) + paths: /* + awsProfile: $(devDeploymentProfile) + + # Test Build/Deploy + - stage: Test + condition: eq(variables['Build.SourceBranch'], variables['test-branch'] ) + variables: + - group: ISS-Test + jobs: + - deployment: testBuildDeployment + displayName: Build and Deploy ISS - Test + environment: NoApproval-All + container: node + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - checkout: self + clean: true + - task: CopyFiles@2 + inputs: + SourceFolder: 'public/apple-pay/test' + Contents: '**' + TargetFolder: 'public/.well-known' + CleanTargetFolder: true + - task: DeleteFiles@1 + inputs: + SourceFolder: 'public/apple-pay' + Contents: '**' + RemoveSourceFolder: true + - template: templates/digital/step-build-vue.yml@AzureDevOps + parameters: + buildOutputDir: dist + environment: Test + - template: templates/digital/step-deploy-vue.yml@AzureDevOps + parameters: + artifactName: vueDistTest + awsProfile: $(sysDeploymentProfile) + deployBuckets: + safelite-sys-iss-us-east-1: + clearFolder: true + deployFolder: "" + region: us-east-1 + safelite-sys-iss-us-east-2: + clearFolder: true + deployFolder: "" + region: us-east-2 + appDeployVariables: + __VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__) + __VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__) + __VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__) + __VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__) + __VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__) + indexDeployVariables: + __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) + __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) + __VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__) + - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps + parameters: + awsCliContainer: awscli + distributionId: $(cfDistributionId) + paths: /* + awsProfile: $(sysDeploymentProfile) + + # QA Build/Deploy + - stage: QA + condition: eq(variables['Build.SourceBranch'], variables['qa-branch'] ) + variables: + - group: ISS-QA + jobs: + - deployment: qaBuildDeployment + displayName: Build and Deploy ISS - QA + environment: NoApproval-All + container: node + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - checkout: self + clean: true + - task: CopyFiles@2 + inputs: + SourceFolder: 'public/apple-pay/qa' + Contents: '**' + TargetFolder: 'public/.well-known' + CleanTargetFolder: true + - task: DeleteFiles@1 + inputs: + SourceFolder: 'public/apple-pay' + Contents: '**' + RemoveSourceFolder: true + - template: templates/digital/step-build-vue.yml@AzureDevOps + parameters: + buildOutputDir: dist + environment: Qa + - template: templates/digital/step-deploy-vue.yml@AzureDevOps + parameters: + artifactName: vueDistQa + awsProfile: $(qaDeploymentProfile) + deployBuckets: + safelite-qa-iss-us-east-1: + clearFolder: true + deployFolder: "" + region: us-east-1 + safelite-qa-iss-us-east-2: + clearFolder: true + deployFolder: "" + region: us-east-2 + appDeployVariables: + __VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__) + __VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__) + __VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__) + __VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__) + __VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__) + indexDeployVariables: + __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) + __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) + __VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__) + - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps + parameters: + awsCliContainer: awscli + distributionId: $(cfDistributionId) + paths: /* + awsProfile: $(qaDeploymentProfile) + + # Prod Build/Deploy + - stage: Prod + condition: succeeded('QA') + variables: + - group: ISS-Prod + jobs: + - deployment: prodBuildDeployment + displayName: Build and Deploy ISS - Prod + environment: digitalCloud-prod + container: node + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - checkout: self + clean: true + - task: CopyFiles@2 + inputs: + SourceFolder: 'public/apple-pay/prod' + Contents: '**' + TargetFolder: 'public/.well-known' + CleanTargetFolder: true + - task: DeleteFiles@1 + inputs: + SourceFolder: 'public/apple-pay' + Contents: '**' + RemoveSourceFolder: true + - template: templates/digital/step-build-vue.yml@AzureDevOps + parameters: + buildOutputDir: dist + environment: Prod + - template: templates/digital/step-deploy-vue.yml@AzureDevOps + parameters: + artifactName: vueDistProd + awsProfile: $(prodDeploymentProfile) + deployBuckets: + safelite-prod-iss-us-east-1: + clearFolder: true + deployFolder: "" + region: us-east-1 + safelite-prod-iss-us-east-2: + clearFolder: true + deployFolder: "" + region: us-east-2 + appDeployVariables: + __VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__) + __VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__) + __VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__) + __VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__) + __VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__) + indexDeployVariables: + __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) + __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) + __VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__) + - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps + parameters: + awsCliContainer: awscli + distributionId: $(cfDistributionId) + paths: /* + awsProfile: $(prodDeploymentProfile) + - template: templates/digital/auto-tag.yml@AzureDevOps + parameters: + dependsOn: prodBuildDeployment + userName: SafeliteAzureDevops + userEmail: githubazuredevops@safelite.com +