Set up daily regression run and extended Jira Writeback Reporter to cover all Jira API functionality (#938)
* Initial regression pipeline * Progress toward Jira API Util changes * Updated Regression run and Jira Writeback Reporter to work together * Temporarily disabled cron * Allow Jira Writeback Reporter to set UAT tester * Updates to azure-pipelines to avoid using jira command line * Deleted unused script * Changed pool to default * Added back closing quote * Added missing paren * Set up cron schedule
This commit is contained in:
parent
6bef3840cc
commit
2791150daf
10 changed files with 555 additions and 228 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -28,6 +28,7 @@ pnpm-debug.log*
|
|||
/blob-report/
|
||||
/playwright/.cache/
|
||||
artifacts/
|
||||
ortoni-report/
|
||||
|
||||
# Misc
|
||||
coverage/*
|
||||
|
|
|
|||
187
azure-pipelines-automated-testing.yml
Normal file
187
azure-pipelines-automated-testing.yml
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
trigger: none
|
||||
schedules:
|
||||
- cron: 0 9 * * MON-FRI
|
||||
always: true
|
||||
displayName: Daily Test Automation Run for ISS-NextGen
|
||||
branches:
|
||||
include:
|
||||
- develop
|
||||
pool: 'Default'
|
||||
|
||||
variables:
|
||||
# - group: Digital-Infrastructure
|
||||
# - group: ISS-BuildBranches
|
||||
- name: dockerImageName
|
||||
value: 'playwright-tests'
|
||||
- name: imageTag
|
||||
value: '$(Build.BuildId)'
|
||||
- name: totalShards
|
||||
value: 4
|
||||
- name: IS_REGRESSION
|
||||
value: 'true'
|
||||
- name: JIRA_BOARD_ID
|
||||
value: '853'
|
||||
- name: JIRA_EPIC_KEY
|
||||
value: 'INSR-414'
|
||||
- name: JIRA_PROJECT_KEY
|
||||
value: 'INSR'
|
||||
|
||||
stages:
|
||||
- stage: TestPr
|
||||
displayName: Run Playwright Test
|
||||
jobs:
|
||||
- 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) or tests don't exist for this shard number!"
|
||||
exit 0 # Suppress error. It will be visible in report.
|
||||
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) \
|
||||
-e IS_REGRESSION=$(IS_REGRESSION) \
|
||||
-e JIRA_BOARD_ID=$(JIRA_BOARD_ID) \
|
||||
-e JIRA_EPIC_KEY=$(JIRA_EPIC_KEY) \
|
||||
-e JIRA_PROJECT_KEY=$(JIRA_PROJECT_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...\" &&
|
||||
PLAYWRIGHT_JUNIT_OUTPUT_DIR='/app/test-results' PLAYWRIGHT_JUNIT_OUTPUT_NAME='junit_results.xml' npx playwright merge-reports --reporter='playwright-tests/impl/reporter/JiraWritebackReporter.ts',junit ./playwright-reports
|
||||
echo \"Contents of ortoni-report:\" && ls ./ortoni-report &&
|
||||
echo 'Current dir: ' && pwd
|
||||
echo 'Contents of current dir: ' && ls
|
||||
echo 'Contents of /app/test-results' && ls /app/test-results ")
|
||||
|
||||
# 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
|
||||
mkdir -p $(System.DefaultWorkingDirectory)/test-results
|
||||
|
||||
# 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)/test-results
|
||||
|
||||
# Remove container
|
||||
echo "Cleaning up container..."
|
||||
docker rm $container_id
|
||||
env:
|
||||
JIRA_API_KEY: $(JIRA_API_KEY)
|
||||
displayName: merge_and_publish_results_to_jira
|
||||
|
||||
- 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()
|
||||
|
|
@ -39,6 +39,8 @@ variables:
|
|||
value: '$(Build.BuildId)'
|
||||
- name: totalShards
|
||||
value: 2
|
||||
- name: IS_REGRESSION
|
||||
value: 'false'
|
||||
|
||||
stages:
|
||||
# PR's
|
||||
|
|
@ -160,19 +162,18 @@ stages:
|
|||
-e JIRA_SERVER=$(JIRA_SERVER) \
|
||||
-e JIRA_USERNAME=$(JIRA_USERNAME) \
|
||||
-e JIRA_API_KEY=$(JIRA_API_KEY) \
|
||||
-e IS_REGRESSION=$(IS_REGRESSION) \
|
||||
-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...\" &&
|
||||
export NODE_OPTIONS=--max_old_space_size=4096
|
||||
PLAYWRIGHT_JUNIT_OUTPUT_DIR='/app/test-results' PLAYWRIGHT_JUNIT_OUTPUT_NAME='junit_results.xml' npx playwright merge-reports --reporter=ortoni-report,junit,\"/app/playwright-tests/impl/reporter/JiraWritebackReporter.ts\" ./playwright-reports
|
||||
PLAYWRIGHT_JUNIT_OUTPUT_DIR='/app/test-results' PLAYWRIGHT_JUNIT_OUTPUT_NAME='junit_results.xml' npx playwright merge-reports --reporter=junit,\"/app/playwright-tests/impl/reporter/JiraWritebackReporter.ts\" ./playwright-reports
|
||||
echo \"Contents of ortoni-report:\" && ls ./ortoni-report &&
|
||||
echo 'Current dir: ' && pwd
|
||||
echo 'Contents of current dir: ' && ls
|
||||
echo 'Contents of /app/test-results' && ls /app/test-results
|
||||
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)\" ")
|
||||
echo 'Contents of /app/test-results' && ls /app/test-results ")
|
||||
|
||||
# Start container and stream logs
|
||||
echo "Starting merge"
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
#!/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
|
||||
169
package-lock.json
generated
169
package-lock.json
generated
|
|
@ -31,6 +31,7 @@
|
|||
"@testing-library/user-event": "14.4.3",
|
||||
"@testing-library/vue": "6.6.1",
|
||||
"@types/dotenv-safe": "^8.1.6",
|
||||
"@types/form-data": "^2.2.1",
|
||||
"@types/node": "^22.7.5",
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"@vitest/coverage-v8": "^0.34.1",
|
||||
|
|
@ -52,6 +53,7 @@
|
|||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-jsdoc": "^46.4.3",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"form-data": "^4.0.2",
|
||||
"jest": "^27.0.5",
|
||||
"jest-junit": "^13.0.0",
|
||||
"jest-serializer-vue": "^3.1.0",
|
||||
|
|
@ -3852,6 +3854,16 @@
|
|||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/form-data": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz",
|
||||
"integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||
|
|
@ -6737,6 +6749,20 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
|
|
@ -8531,6 +8557,21 @@
|
|||
"dotenv": ">= 8.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexer": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||
|
|
@ -8797,13 +8838,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
|
|
@ -8848,6 +8887,35 @@
|
|||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-shim-unscopables": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
|
||||
|
|
@ -10077,13 +10145,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -10271,16 +10341,22 @@
|
|||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -10298,6 +10374,20 @@
|
|||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
|
||||
|
|
@ -10402,12 +10492,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
|
|
@ -10488,23 +10579,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
|
|
@ -10513,12 +10593,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
||||
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.2"
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -15216,6 +15297,16 @@
|
|||
"resolved": "https://registry.npmjs.org/maska/-/maska-2.1.11.tgz",
|
||||
"integrity": "sha512-IGqWjBnKxMYcVa06pb4mPfag9sJjnR2T15CdGfQ2llR3gajiSd4AxXCvNqHMEq9W3UBhjjTazgWumsP3sWrUSg=="
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"@testing-library/user-event": "14.4.3",
|
||||
"@testing-library/vue": "6.6.1",
|
||||
"@types/dotenv-safe": "^8.1.6",
|
||||
"@types/form-data": "^2.2.1",
|
||||
"@types/node": "^22.7.5",
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"@vitest/coverage-v8": "^0.34.1",
|
||||
|
|
@ -48,6 +49,7 @@
|
|||
"@vue/cli-service": "~5.0.0",
|
||||
"@vue/test-utils": "^2.4.1",
|
||||
"@vue/vue3-jest": "^27.0.0-alpha.1",
|
||||
"axe-core": "^4.10.2",
|
||||
"axios": "^1.7.8",
|
||||
"axios-mock-adapter": "^1.21.5",
|
||||
"babel-jest": "^27.0.6",
|
||||
|
|
@ -59,6 +61,7 @@
|
|||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-jsdoc": "^46.4.3",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"form-data": "^4.0.2",
|
||||
"jest": "^27.0.5",
|
||||
"jest-junit": "^13.0.0",
|
||||
"jest-serializer-vue": "^3.1.0",
|
||||
|
|
@ -73,7 +76,6 @@
|
|||
"vite": "^4.5.9",
|
||||
"vitest": "^0.33.0",
|
||||
"volar-service-vetur": "latest",
|
||||
"wait-on": "^8.0.2",
|
||||
"axe-core": "^4.10.2"
|
||||
"wait-on": "^8.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,10 @@ export interface JiraIssueFields {
|
|||
parent?: JiraParent,
|
||||
fixVersions?: JiraVersion[],
|
||||
subtasks?: JiraSubTask[],
|
||||
customfield_14857?: JiraContent // Test Steps field
|
||||
customfield_14857?: JiraContent, // Test Steps field
|
||||
customfield_10007?: number // Sprint field,
|
||||
customfield_13100?: { id: string } // UAT Tester ID field
|
||||
|
||||
}
|
||||
|
||||
export interface JiraSubTask {
|
||||
|
|
@ -108,4 +111,25 @@ export interface JiraIssue {
|
|||
fields: JiraIssueFields
|
||||
}
|
||||
|
||||
export interface GetIssueResponse extends JiraIssue {}
|
||||
export interface GetIssueResponse extends JiraIssue {}
|
||||
|
||||
export interface GetBoardResponse {
|
||||
id: number,
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface GetSprintResponse {
|
||||
values: JiraSprint[]
|
||||
}
|
||||
|
||||
export interface GetJqlSearchIssueParams {
|
||||
jql: string
|
||||
}
|
||||
|
||||
export interface GetJqlSearchIssueResponse extends PostBulkFetchIssuesResponse {}
|
||||
|
||||
export interface JiraSprint {
|
||||
id: number,
|
||||
name: string,
|
||||
originBoardId: number
|
||||
}
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
import { PostBulkFetchIssuesRequestBody, PostBulkFetchIssuesResponse, GetIssueResponse, GetIssueTransitionsResponse, GetIssueTypesResponse, PostAddCommentResponse, PostBulkCreateIssueRequestBody, PostBulkCreateIssueResponse, PostCreateIssueRequestBody, PostCreateIssueResponse, PostTransitionIssueRequestBody, PutEditIssueRequestBody, PostBulkTransitionIssuesRequestBody } from "@business-logic/types/JiraApi";
|
||||
import axios, { request } from "axios";
|
||||
import { PostBulkFetchIssuesRequestBody, PostBulkFetchIssuesResponse, GetIssueResponse, GetIssueTransitionsResponse, GetIssueTypesResponse, PostAddCommentResponse, PostBulkCreateIssueRequestBody, PostBulkCreateIssueResponse, PostCreateIssueRequestBody, PostCreateIssueResponse, PostTransitionIssueRequestBody, PutEditIssueRequestBody, PostBulkTransitionIssuesRequestBody, GetSprintResponse, GetJqlSearchIssueParams, GetJqlSearchIssueResponse } from "@business-logic/types/JiraApi";
|
||||
import axios from "axios";
|
||||
import axiosRetry from "axios-retry";
|
||||
import FormData from 'form-data';
|
||||
import path from "path";
|
||||
import fs from 'fs';
|
||||
|
||||
const jiraUrl = process.env.JIRA_SERVER!;
|
||||
const jiraUsername = process.env.JIRA_USERNAME!;
|
||||
const jiraApiKey = process.env.JIRA_API_KEY!;
|
||||
const boardId = process.env.JIRA_BOARD_ID!;
|
||||
const encodedAuthKey = Buffer.from(`${jiraUsername}:${jiraApiKey}`).toString('base64');
|
||||
|
||||
export default class JiraApiUtil {
|
||||
|
|
@ -13,6 +17,9 @@ export default class JiraApiUtil {
|
|||
readonly bulkIssueCreateUrl: string;
|
||||
readonly bulkIssueFetchUrl: string;
|
||||
readonly bulkTransitionIssuesUrl: string;
|
||||
readonly getCurrentSprintUrl: string;
|
||||
readonly getJqlSearchIssueUrl: string;
|
||||
|
||||
readonly axiosClient: axios.AxiosInstance;
|
||||
|
||||
constructor() {
|
||||
|
|
@ -20,7 +27,9 @@ export default class JiraApiUtil {
|
|||
this.issueUrl = `${this.baseUrl}/rest/api/3/issue`;
|
||||
this.bulkIssueCreateUrl = `${this.issueUrl}/bulk`;
|
||||
this.bulkIssueFetchUrl = `${this.issueUrl}/bulkfetch`;
|
||||
this.bulkTransitionIssuesUrl = `${this.baseUrl}/rest/api/3/bulk/issues/transition`
|
||||
this.bulkTransitionIssuesUrl = `${this.baseUrl}/rest/api/3/bulk/issues/transition`;
|
||||
this.getCurrentSprintUrl = `${this.baseUrl}/rest/agile/1.0/board/${boardId}/sprint?state=active`;
|
||||
this.getJqlSearchIssueUrl = `${this.baseUrl}/rest/api/3/search/jql`;
|
||||
this.axiosClient = axios.create();
|
||||
// interceptor to log error message from api
|
||||
this.axiosClient.interceptors.response.use(
|
||||
|
|
@ -48,6 +57,33 @@ export default class JiraApiUtil {
|
|||
});
|
||||
}
|
||||
|
||||
async getCurrentSprint() {
|
||||
const res = await this.axiosClient.get<GetSprintResponse>(this.getCurrentSprintUrl, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
const currentSprint = res.data.values.filter(sprint => {
|
||||
return `${sprint.originBoardId}` === boardId;
|
||||
});
|
||||
if (currentSprint.length === 1) {
|
||||
return currentSprint[0];
|
||||
} else {
|
||||
console.error(`JiraApiUtil >> Multiple active sprints found for board ${boardId}`);
|
||||
}
|
||||
}
|
||||
|
||||
getJqlSearchIssue(params: GetJqlSearchIssueParams) {
|
||||
return this.axiosClient.get<GetJqlSearchIssueResponse>(this.getJqlSearchIssueUrl, {
|
||||
params: params,
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
postBulkFetchIssues(requestBody: PostBulkFetchIssuesRequestBody) {
|
||||
return this.axiosClient.post<PostBulkFetchIssuesResponse>(this.bulkIssueFetchUrl, requestBody, {
|
||||
headers: {
|
||||
|
|
@ -140,4 +176,19 @@ export default class JiraApiUtil {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
postUploadAttachment(issueKey: string, filePath: string) {
|
||||
const url = `${this.baseUrl}/rest/api/3/issue/${issueKey}/attachments`
|
||||
const form = new FormData();
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
form.append('file', fs.createReadStream(filePath), fileName);
|
||||
return this.axiosClient.post(url, form, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
'X-Atlassian-Token': 'no-check',
|
||||
...form.getHeaders()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,37 @@
|
|||
import JiraApiUtil from "@impl/api/JiraApiUtil";
|
||||
import type { FullResult, Reporter, TestCase, TestResult } from "@playwright/test/reporter";
|
||||
import { formatDate } from "@impl/utils/DateUtils";
|
||||
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestError, TestResult } from "@playwright/test/reporter";
|
||||
import { formatDate, formatDateForFilename } from "@impl/utils/DateUtils";
|
||||
import { GetIssueResponse, JiraIssue, JiraIssueFields } from "@business-logic/types/JiraApi";
|
||||
import OrtoniReport, { OrtoniReportConfig } from "ortoni-report";
|
||||
|
||||
const jiraCardNumber = process.env.JIRA_CARD_NUMBER!;
|
||||
const jiraProjectKey = 'INSR';
|
||||
let jiraCardNumber = process.env.JIRA_CARD_NUMBER || '';
|
||||
const isRegressionRun = process.env.IS_REGRESSION === 'true'? true: false;
|
||||
const jiraProjectKey = process.env.JIRA_PROJECT_KEY!;
|
||||
const jiraEpicKey = process.env.JIRA_EPIC_KEY!;
|
||||
const passTransitionId = '111';
|
||||
const failTransitionId = '101';
|
||||
const inTestTransitionId = '271';
|
||||
const currentDate = formatDateForFilename(new Date());
|
||||
|
||||
// Ortoni config
|
||||
const reportName = `ortoni_report_${currentDate}.html`;
|
||||
const reportConfig: OrtoniReportConfig = {
|
||||
port: 1994,
|
||||
open: "never",
|
||||
folderPath: "ortoni-report",
|
||||
filename: reportName,
|
||||
logo: 'playwright-tests/business-logic/data/logo.png',
|
||||
title: "Test Report",
|
||||
showProject: false,
|
||||
projectName: "ISS-Nextgen-Playwright-Report",
|
||||
testType: `E2E- Environment: ${process.env.NODE_ENV} `,
|
||||
preferredTheme: "light",
|
||||
base64Image: true,
|
||||
};
|
||||
|
||||
export default class JiraWritebackReporter implements Reporter {
|
||||
readonly jiraApiUtil: JiraApiUtil = new JiraApiUtil();
|
||||
readonly ortoniReport: OrtoniReport = new OrtoniReport(reportConfig);
|
||||
readonly loadIssueCalls: (() => void)[] = []; // Calls to loadIssue must be deferred because they depend on information from the API.
|
||||
readonly issuesToPass: string[] = []; // Issues to transition to "Pass".
|
||||
readonly issuesToFail: string[] = []; // Issues to transition to "Fail".
|
||||
|
|
@ -17,6 +39,7 @@ export default class JiraWritebackReporter implements Reporter {
|
|||
existingSubtasks: JiraIssue[] = []; // Array to hold existing subtasks of the dev card.
|
||||
testCaseTypeId: string|undefined = undefined; // ID of the test case subtask type in Jira. Will be filled by API call.
|
||||
bugTypeId:string|undefined = undefined; // ID of the bug subtask type.
|
||||
userStoryTypeId:string|undefined = undefined;
|
||||
parentCard: GetIssueResponse | undefined = undefined; // Variable to hold the parent card. Will be filled by API call.
|
||||
|
||||
// Will need this if we want to move ortoni report upload into this reporter.
|
||||
|
|
@ -36,6 +59,9 @@ export default class JiraWritebackReporter implements Reporter {
|
|||
this.bugTypeId = issueTypes.issueTypes.find(issueType => {
|
||||
return issueType.subtask === true && issueType.name === 'Bug Sub-task'
|
||||
})?.id;
|
||||
this.userStoryTypeId = issueTypes.issueTypes.find(issueType => {
|
||||
return issueType.name === 'Story';
|
||||
})?.id;
|
||||
console.log('JiraWritebackReporter >> Loaded issue types.');
|
||||
}
|
||||
|
||||
|
|
@ -167,16 +193,97 @@ export default class JiraWritebackReporter implements Reporter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param cardName
|
||||
* @param sprintId
|
||||
* @returns
|
||||
*/
|
||||
getCreateRegressionCardRequestBody(cardName: string, sprintId: number) {
|
||||
const body: JiraIssueFields = {
|
||||
summary: cardName,
|
||||
description: {
|
||||
content: [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": 'Card to hold regression test results for this sprint.',
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"type": "paragraph"
|
||||
}
|
||||
],
|
||||
"type": "doc",
|
||||
"version": 1
|
||||
},
|
||||
project: {
|
||||
key: jiraProjectKey
|
||||
},
|
||||
issuetype: { id: this.userStoryTypeId!},
|
||||
parent: { key: jiraEpicKey },
|
||||
customfield_10007: sprintId,
|
||||
customfield_13100: { id: '557058:a314fc5b-aed9-4472-90f8-00f106e06207' } // Mark UAT Tester as N/A
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs parent card and existing subtasks.
|
||||
*/
|
||||
async loadReporterData() {
|
||||
let parentCard: GetIssueResponse;
|
||||
await this.loadJiraIssueTypes();
|
||||
|
||||
console.log('JiraWritebackReporter >> Searching for parent card...');
|
||||
const parentRes = await this.jiraApiUtil.getIssue(jiraCardNumber);
|
||||
const parentCard = parentRes.data;
|
||||
console.log(`JiraWritebackReporter >> Retrieved parent card: ${parentCard.key}: ${parentCard.fields.summary}`);
|
||||
if (isRegressionRun) {
|
||||
console.log('JiraWritebackReporter >> Searching for current regression card...');
|
||||
const sprint = await this.jiraApiUtil.getCurrentSprint();
|
||||
if (!sprint) {
|
||||
console.error('JiraWritebackReporter >> Could not find current sprint');
|
||||
} else {
|
||||
console.log(`JiraWritebackReporter >> Found sprint with id '${sprint.id}' called '${sprint.name}'`);
|
||||
}
|
||||
const regressionCardName = `Playwright Automated Regression Tests ${sprint?.name}`;
|
||||
const parentRes = await this.jiraApiUtil.getJqlSearchIssue({ jql: `sprint = ${sprint?.id} and summary ~ "${regressionCardName}"` });
|
||||
if (parentRes.data.issues.length > 1) {
|
||||
console.log('JiraWritebackReporter >> WARNING: Multiple regression cards found. Using first one.');
|
||||
parentCard = (await this.jiraApiUtil.getIssue(parentRes.data.issues[0].id!)).data;
|
||||
await this.jiraApiUtil.postTransitionIssue(parentCard.id!, {
|
||||
transition: {
|
||||
id: inTestTransitionId
|
||||
}
|
||||
});
|
||||
} else if (parentRes.data.issues.length < 1) {
|
||||
// NO regression card found. Make one.
|
||||
console.log('JiraWritebackReporter >> No regression card was found for this sprint. Creating one.');
|
||||
const fields = this.getCreateRegressionCardRequestBody(regressionCardName, sprint!.id);
|
||||
const regressionCard: JiraIssue = {
|
||||
fields: fields,
|
||||
transition: {
|
||||
id: inTestTransitionId
|
||||
}
|
||||
};
|
||||
const createRes = await this.jiraApiUtil.postCreateIssue(regressionCard);
|
||||
regressionCard.id = createRes.data.id;
|
||||
regressionCard.key = createRes.data.key;
|
||||
parentCard = regressionCard;
|
||||
} else {
|
||||
// Found one
|
||||
console.log('JiraWritebackReporter >> Found one regression card. Using it.');
|
||||
parentCard = (await this.jiraApiUtil.getIssue(parentRes.data.issues[0].id!)).data;
|
||||
await this.jiraApiUtil.postTransitionIssue(parentCard.id!, {
|
||||
transition: {
|
||||
id: inTestTransitionId
|
||||
}
|
||||
});
|
||||
}
|
||||
jiraCardNumber = parentCard.key!;
|
||||
console.log(`JiraWritebackReporter >> Retrieved current regression card: ${parentCard.key}: ${parentCard.fields.summary}`);
|
||||
} else {
|
||||
console.log('JiraWritebackReporter >> Searching for parent card...');
|
||||
const parentRes = await this.jiraApiUtil.getIssue(jiraCardNumber);
|
||||
parentCard = parentRes.data;
|
||||
console.log(`JiraWritebackReporter >> Retrieved parent card: ${parentCard.key}: ${parentCard.fields.summary}`);
|
||||
}
|
||||
|
||||
console.log(`JiraWritebackReporter >> Searching for all subtasks of parent card '${jiraCardNumber}'...`);
|
||||
const subtaskIds = parentCard.fields.subtasks?.map(subtask => {
|
||||
|
|
@ -258,7 +365,6 @@ export default class JiraWritebackReporter implements Reporter {
|
|||
if (new RegExp(`^TEST FAILED [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]: ${test.title}$`).test(card.fields.summary)) {
|
||||
existingBug = card;
|
||||
console.log('JiraWritebackReporter >> Found existing bug.');
|
||||
// TODO: Handle multiple matches for bug/test case. Currently picks one in that case.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -302,7 +408,7 @@ export default class JiraWritebackReporter implements Reporter {
|
|||
* @param result
|
||||
*/
|
||||
onTestEnd(test: TestCase, result: TestResult) {
|
||||
// this.ortoniReport.onTestEnd(test, result);
|
||||
this.ortoniReport.onTestEnd(test, result);
|
||||
this.loadIssueCalls.push(() => { this.loadIssue(test, result); });
|
||||
}
|
||||
|
||||
|
|
@ -312,28 +418,32 @@ export default class JiraWritebackReporter implements Reporter {
|
|||
* @returns Promise to write results to Jira.
|
||||
*/
|
||||
onEnd(result: FullResult): Promise<{ status?: FullResult["status"]; } | undefined | void>|void {
|
||||
// this.ortoniReport.onEnd(result);
|
||||
return this.loadReporterData().then(async () => {
|
||||
this.loadIssueCalls.map(fn => fn());
|
||||
await this.writeResults();
|
||||
console.log(`JiraWritebackReporter >> Posted necessary changes for '${jiraCardNumber}'`);
|
||||
return this.ortoniReport.onEnd(result).then(async () => {
|
||||
await this.loadReporterData().then(async () => {
|
||||
this.loadIssueCalls.map(fn => fn());
|
||||
await this.writeResults();
|
||||
}).then(async () => {
|
||||
console.log('JiraWritebackReporter >> Uploading Ortoni HTML Report to Jira...');
|
||||
await this.jiraApiUtil.postUploadAttachment(jiraCardNumber, `${reportConfig.folderPath}/${reportConfig.filename}`);
|
||||
console.log('JiraWritebackReporter >> Uploaded Ortoni HTML Report to Jira.');
|
||||
console.log(`JiraWritebackReporter >> Posted necessary changes for '${jiraCardNumber}'`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// These overloads will become necessary if we move the ortoni report upload into this reporter
|
||||
// onBegin(config: FullConfig, suite: Suite): void {
|
||||
// this.ortoniReport.onBegin(config, suite);
|
||||
// }
|
||||
onBegin(config: FullConfig, suite: Suite): Promise<void> {
|
||||
return this.ortoniReport.onBegin(config, suite);
|
||||
}
|
||||
|
||||
// onError(error: TestError): void {
|
||||
// this.ortoniReport.onError(error);
|
||||
// }
|
||||
onError(error: TestError): void {
|
||||
return this.ortoniReport.onError(error);
|
||||
}
|
||||
|
||||
// onExit(): Promise<void> {
|
||||
// return this.ortoniReport.onExit();
|
||||
// }
|
||||
onExit(): Promise<void> {
|
||||
return this.ortoniReport.onExit();
|
||||
}
|
||||
|
||||
// onStdOut(chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void {
|
||||
// this.ortoniReport.onStdOut(chunk, test, result);
|
||||
// }
|
||||
onStdOut(chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void {
|
||||
return this.ortoniReport.onStdOut(chunk, test, result);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,15 @@ export function formatDate(date: Date) {
|
|||
return isoString.slice(0, 10);
|
||||
}
|
||||
|
||||
export function formatDateForFilename(date: Date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hour = String(date.getHours()).padStart(2, '0');
|
||||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}${month}${day}_${hour}${minute}`;
|
||||
}
|
||||
|
||||
export function formatTime(date: Date) {
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour: 'numeric',
|
||||
|
|
|
|||
Loading…
Reference in a new issue