diff --git a/Dockerfile.playwright b/Dockerfile.playwright index 6271a937..46a2114a 100644 --- a/Dockerfile.playwright +++ b/Dockerfile.playwright @@ -1,4 +1,4 @@ -FROM node:16 +FROM node:20 FROM mcr.microsoft.com/playwright:v1.48.0-noble @@ -12,10 +12,10 @@ COPY package*.json ./ RUN npm install # Install Playwright browsers -RUN npx playwright install --with-deps +RUN npx playwright install chromium --with-deps + +# Install jq +RUN apt-get install -y jq # Copy the rest of the application code COPY . . - -# Run Playwright tests -CMD ["npx", "playwright", "test"] diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cedebb5b..c40e2c36 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -90,7 +90,6 @@ stages: "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 @@ -127,26 +126,65 @@ stages: condition: always() - job: download_and_merge_reports - container: - image: mcr.microsoft.com/playwright:v1.48.0-noble dependsOn: playwright_tests + timeoutInMinutes: 8 + cancelTimeoutInMinutes: 10 steps: - - task: DownloadPipelineArtifact@2 - inputs: - targetPath: '$(System.DefaultWorkingDirectory)/playwright-reports' - - script: | - npm i ortoni-report && - for dir in $(System.DefaultWorkingDirectory)/playwright-reports/*/; do - if [ -d "$dir" ]; then - mv "$dir"* $(System.DefaultWorkingDirectory)/playwright-reports/ - rmdir "$dir" - fi - done - npx playwright merge-reports --reporter=ortoni-report,junit $(System.DefaultWorkingDirectory)/playwright-reports - ls - displayName: merge_reports - env: - PLAYWRIGHT_JUNIT_OUTPUT_FILE: "test-results/results.xml" + - 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 @@ -154,19 +192,23 @@ stages: # inputs: # searchFolder: 'test-results' # testResultsFormat: 'JUnit' - # testResultsFiles: 'results.xml' + # 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' + - 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 diff --git a/devops/scripts/jira_writeback.sh b/devops/scripts/jira_writeback.sh new file mode 100755 index 00000000..1d605072 --- /dev/null +++ b/devops/scripts/jira_writeback.sh @@ -0,0 +1,149 @@ +#!/bin/bash +create_issue() { + local title="$1" + local project_key="$2" + local issue_type="$3" + local parent_issue_key="$4" + + AUTH=$(echo -ne "$JIRA_USERNAME:$JIRA_API_KEY" | base64 --wrap 0) + + local parent_issue=$(curl -s -H "Authorization: Basic $AUTH" \ + "$JIRA_SERVER/rest/api/3/issue/$parent_issue_key") + + local parent_fix_versions=$(echo $parent_issue | jq -r '.fields.fixVersions') + + local created_issue=$(curl -X POST -H "Content-Type: application/json" \ + -H "Authorization: Basic $AUTH" \ + -d '{ + "fields": { + "summary": "'"$title"'", + "project": { + "key": "'"$project_key"'" + }, + "issuetype": { + "name": "'"$issue_type"'" + }, + "parent": { + "key": "'"$parent_issue_key"'" + }, + "fixVersions": '"$parent_fix_versions"' + } + }' \ + "$JIRA_SERVER/rest/api/3/issue") + + local created_issue_id=$(echo $created_issue | jq -r '.id') + + curl -X PUT -H "Content-Type: application/json" \ + -H "Authorization: Basic $AUTH" \ + -d '{ + "fields": { + "fixVersions": '"$parent_fix_versions"' + } + }' \ + "$JIRA_SERVER/rest/api/3/issue/$created_issue_id" +} + +extract_uuid() { + local url="$1" + local uuid_regex='[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}' + if [[ "$url" =~ $uuid_regex ]]; then + echo "${BASH_REMATCH}" + else + echo "No UUID found in the URL." + fi +} + +update_issue_status() { + local issue_key="$1" + local status_name="$2" + + AUTH=$(echo -ne "$JIRA_USERNAME:$JIRA_API_KEY" | base64 --wrap 0) + + local transitions=$(curl -s -H "Authorization: Basic $AUTH" \ + "$JIRA_SERVER/rest/api/3/issue/$issue_key/transitions") + + local transition_id=$(echo "$transitions" | jq -r --arg status_name "$status_name" ' + .transitions[] | select(.isAvailable == true and .to.name == $status_name) | .id + ') + + if [ -z "$transition_id" ]; then + echo "BadRequestError" + exit 1 + else + curl -X POST -H "Content-Type: application/json" \ + -H "Authorization: Basic $AUTH" \ + -d '{ + "transition": { + "id": "'"$transition_id"'" + } + }' \ + "$JIRA_SERVER/rest/api/3/issue/$issue_key/transitions" + fi +} + +add_attachments() { + AUTH=$(echo -ne "$JIRA_USERNAME:$JIRA_API_KEY" | base64 --wrap 0) + + local issue_key="$1" + shift + local attachments=("$@") + + echo $issue_key + echo $attachments + + local form_data="" + for attachment in "${attachments[@]}"; do + form_data+="--form file=@$attachment " + done + + echo $form_data + + echo $(curl -X POST $form_data \ + -H "X-Atlassian-Token: no-check" \ + -H "Authorization: Basic $AUTH" \ + "$JIRA_SERVER/rest/api/3/issue/$issue_key/attachments") +} + +add_comment() { + AUTH=$(echo -ne "$JIRA_USERNAME:$JIRA_API_KEY" | base64 --wrap 0) + local issue_key="$1" + shift + local comment_items_input=("$@") + local comment_json="[]" + + for item in "${comment_items_input[@]}"; do + if [ -e "$item" ]; then + # If it's a file path + local attachment=$(add_attachments "$issue_key" "$item") + local id=$(echo $attachment | grep -oP '"id":\s*"\K[^"]+') + + local attachment_content=$(curl -s -I -L -H "Authorization: Basic $AUTH" "$JIRA_SERVER/rest/api/3/attachment/content/$id" \ + | grep -i "Location:" | tail -1 | awk '{print $2}' | tr -d '\r') + echo "$JIRA_SERVER/rest/api/3/attachment/content/$id" + echo "$attachment_content" + + local uuid=$(extract_uuid "$attachment_content") + echo "$uuid" + + json_object=$(jq -n --arg uuid "$uuid" '{ type: "mediaSingle", attrs: { layout: "align-start" }, content: [{ type: "media", attrs: { type: "file", id: $uuid, width: 200, height: 200, collection: "", alt: "" } }]}') + + comment_json=$(echo "$comment_json" | jq --argjson obj "$json_object" '. += [$obj]') + else + # If it's a string + json_object=$(jq -n --arg text "$item" '{ type: "paragraph", content: [{ type: "text", text: $text }]}') + + comment_json=$(echo "$comment_json" | jq --argjson obj "$json_object" '. += [$obj]') + fi + done + + request=$(jq -n --argjson content "$comment_json" '{body: { type: "doc", version: 1, content: $content }}') + + curl -X POST -H "Content-Type: application/json" \ + -H "Authorization: Basic $AUTH" \ + -d "$request" \ + "$JIRA_SERVER/rest/api/3/issue/$issue_key/comment" +} + +if [[ $# -gt 0 ]]; then # IF function call passed in + "$@" # Call function +fi \ No newline at end of file