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: 50 #TODO: Change back before merge 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: | # 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 \"@smoke | @Advanced\"") # 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: | # 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) \ $(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 # npx playwright merge-reports --config=playwright-tests/playwright.config.ts ./playwright-reports # num_failures=$(sed -n 's/.*failures="\([^"]*\)".*/\1/p' /app/test-results/junit_results.xml | head -n 1) # echo \"Number of test failures: '$num_failures'\" # (($num_failures -eq 0)) && jira_message='Playwright tests passed!' || jira_message=\"$num_failures Playwright tests failed.\" # TODO: Update to include test status message later echo \"Contents of ortoni-report:\" && ls ./ortoni-report && branch_name=$(git rev-parse --abbrev-ref HEAD) # Had to do it this way because Build.SourceBranchName is always 'merge' on PR jira_card_number=\"${branch_name##*/}\" # Grab everything after last '/' echo \"Writing report to Jira card '$jira_card_number'...\" && ./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