Skip to content

Added support for showing cad report #3

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 12 commits into from
Jun 9, 2025
Merged
6 changes: 6 additions & 0 deletions src/commands/set_browserstack_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: "Set BrowserStack configuration in the environment variables. ( Also, here expectation is that browserstack username & access key will be set by user either in yml or in project env variables )"

steps:
- run:
name: Set BrowserStack Config
command: <<include(scripts/set_browserstack_config.sh)>>
17 changes: 17 additions & 0 deletions src/commands/test_reports.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
description: "Upload report and handle polling mechanism"

parameters:
user_timeout:
type: integer
description: "User timeout in seconds to manage poll time of report (optional)"
default: 130

steps:
- run:
name: Show & Upload Report
command: <<include(scripts/test_reports.sh)>>
environment:
USER_TIMEOUT: <<parameters.user_timeout>>
- store_artifacts:
path: browserstack/testreport.html
destination: Browserstack Test Report
29 changes: 29 additions & 0 deletions src/examples/set_browserstack_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
description: |
This example demonstrates how to set BrowserStack config in Job Scope. (CIRCLECI_TOKEN needs to be set in CircleCI project environment variables)

usage:
version: 2.1
orbs:
browserstack-circleci-orb: browserstack/browserstack-circleci-orb@x.y.z

jobs:
my_test_job:
executor: default
environment:
BROWSERSTACK_USERNAME: "xxx"
BROWSERSTACK_ACCESS_KEY: "yyy"
BROWSERSTACK_LOCAL: false
BROWSERSTACK_LOCAL_IDENTIFIER: "identifier"

steps:
- checkout
- browserstack-circleci-orb/set_browserstack_config
- run:
name: Run Test
command: |
echo "Browserstack build name: $BROWSERSTACK_BUILD_NAME"
npm run test
workflows:
set-rerun-tests-example:
jobs:
- my_test_job
24 changes: 24 additions & 0 deletions src/examples/test_reports.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
description: |
This example demonstrates how to show browserstack test insights & report within circleCI.

usage:
version: 2.1
orbs:
browserstack-circleci-orb: browserstack/browserstack-circleci-orb@x.y.z

jobs:
my_test_job:
executor: default
steps:
- checkout
- browserstack-circleci-orb/set_browserstack_config
- run:
name: Run Test
command: |
npm run test
- browserstack-circleci-orb/test_reports:
user_timeout: 60 # Optional, default is 100 seconds
workflows:
set-rerun-tests-example:
jobs:
- my_test_job
41 changes: 41 additions & 0 deletions src/scripts/set_browserstack_config.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# Check if CircleCI token is set
if [[ -z "$CIRCLECI_TOKEN" ]]; then
echo "CircleCI token (CIRCLECI_TOKEN) is not set. Exiting."
exit 1
fi

# Fetch workflow name using CircleCI API
echo "Fetching workflow details..."
WORKFLOW_RESPONSE=$(curl -s -H "Circle-Token: ${CIRCLECI_TOKEN}" \
"https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}")

if [[ -z "$WORKFLOW_RESPONSE" ]]; then
echo "Failed to fetch workflow details. Exiting."
exit 0
fi

WORKFLOW_NAME=$(echo "$WORKFLOW_RESPONSE" | jq -r '.name // empty')

if [[ -z "$WORKFLOW_NAME" ]]; then
echo "Workflow name not found in response. Exiting."
exit 0
fi

echo "Workflow name: $WORKFLOW_NAME"

# Set BrowserStack build name
export BROWSERSTACK_BUILD_NAME="circleci-${WORKFLOW_NAME}-${CIRCLE_BUILD_NUM}"
echo "BrowserStack build name set to: $BROWSERSTACK_BUILD_NAME"

# Set BrowserStack credentials
if [[ -z "$BROWSERSTACK_USERNAME" || -z "$BROWSERSTACK_ACCESS_KEY" ]]; then
echo "BrowserStack credentials are not set. Please add it into your environment variable. Exiting."
exit 1
fi

# Export build name to job scope environment
echo "export BROWSERSTACK_BUILD_NAME=\"${BROWSERSTACK_BUILD_NAME}\"" >> "$BASH_ENV"

echo "BrowserStack credentials and build name exported to job scope environment."
183 changes: 183 additions & 0 deletions src/scripts/test_reports.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/bin/bash

# Constants
API_PATH="https://api-observability.browserstack.com/ext/v1/builds/buildReport"
REPORT_STATUS_COMPLETED="COMPLETED"
REPORT_STATUS_NOT_AVAILABLE="NOT_AVAILABLE"
REPORT_STATUS_TEST_AVAILABLE="TEST_AVAILABLE"
REPORT_STATUS_IN_PROGRESS="IN_PROGRESS"
REQUESTING_CI="circle-ci"
REPORT_FORMAT='["plainText", "richHtml"]'

# Error scenario mappings
declare -A ERROR_SCENARIOS=(
["BUILD_NOT_FOUND"]="Build not found in BrowserStack"
["MULTIPLE_BUILD_FOUND"]="Multiple builds found with the same name"
["DATA_NOT_AVAILABLE"]="Report data not available from BrowserStack"
)

# Check if BROWSERSTACK_BUILD_NAME is set
if [[ -z "$BROWSERSTACK_BUILD_NAME" ]]; then
echo "Error: BROWSERSTACK_BUILD_NAME is not set."
exit 0
fi

if [[ "$USER_TIMEOUT" -lt 20 || "$USER_TIMEOUT" -gt 600 ]]; then
echo "Error: USER_TIMEOUT must be between 20 and 600 seconds."
exit 1
fi

# Function to make API requests
make_api_request() {
local request_type=$1
local auth_header
local header_file
local response

# Encode username:accesskey to base64
auth_header=$(echo -n "${BROWSERSTACK_USERNAME}:${BROWSERSTACK_ACCESS_KEY}" | base64)
# Create a temporary file for headers
header_file=$(mktemp)
response=$(curl -s -w "%{http_code}" -X POST "$API_PATH" \
-H "Content-Type: application/json" \
-H "Authorization: Basic $auth_header" \
-D "$header_file" \
-d "{
\"originalBuildName\": \"${BROWSERSTACK_BUILD_NAME}\",
\"buildStartedAt\": \"$(date +%s)\",
\"requestingCi\": \"$REQUESTING_CI\",
\"reportFormat\": $REPORT_FORMAT,
\"requestType\": \"$request_type\",
\"userTimeout\": \"${USER_TIMEOUT}\"
}")

# Extract the HTTP status code from the response
local http_status=${response: -3}
# Extract the response body (everything except the last 3 characters)
local body=${response:0:${#response}-3}

# Clean up the temporary file
rm -f "$header_file"

if [[ -z "$body" ]]; then
body='""'
fi

# Return both status code and body as a JSON object
echo "{\"status_code\": $http_status, \"body\": $body}"
}

# Function to extract report data
extract_report_data() {
local response=$1
rich_html_response=$(echo "$response" | jq -r '.report.richHtml // empty')
rich_css_response=$(echo "$response" | jq -r '.report.richCss // empty')
plain_text_response=$(echo "$response" | jq -r '.report.plainText // empty')
}

# Function to check report status
check_report_status() {
local response=$1
local status_code
local body
local error_message

status_code=$(echo "$response" | jq -r '.status_code')
body=$(echo "$response" | jq -r '.body')

if [[ $status_code -ne 200 ]]; then
echo "Error: API returned status code $status_code"
error_message=$(echo "$body" | jq -r '.message // "Unknown error"')
echo "Error message: $error_message"
return 1
fi

REPORT_STATUS=$(echo "$body" | jq -r '.reportStatus // empty')
if [[ "$REPORT_STATUS" == "$REPORT_STATUS_COMPLETED" ||
"$REPORT_STATUS" == "$REPORT_STATUS_TEST_AVAILABLE" ||
"$REPORT_STATUS" == "$REPORT_STATUS_NOT_AVAILABLE" ]]; then
extract_report_data "$body"
return 0
fi
return 2
}
echo "Making initial API request to Browserstack Test Report API..."

# Initial API Request
RESPONSE=$(make_api_request "FIRST")
check_report_status "$RESPONSE" || true
RETRY_COUNT=$(echo "$RESPONSE" | jq -r '.body.retryCount // 3')
POLLING_DURATION=$(echo "$RESPONSE" | jq -r '.body.pollingInterval // 3')

# Polling Mechanism
[ "$REPORT_STATUS" == "$REPORT_STATUS_IN_PROGRESS" ] && {
echo "Starting polling mechanism to fetch Test report..."
}
local_retry=0
ELAPSED_TIME=0

while [[ $local_retry -lt $RETRY_COUNT && $REPORT_STATUS == "$REPORT_STATUS_IN_PROGRESS" ]]; do
if [[ -n "$USER_TIMEOUT" && "$ELAPSED_TIME" -ge "$USER_TIMEOUT" ]]; then
echo "User timeout reached. Making final API request..."
RESPONSE=$(make_api_request "LAST")
check_report_status "$RESPONSE" && break
break
fi

ELAPSED_TIME=$((ELAPSED_TIME + POLLING_DURATION))
local_retry=$((local_retry + 1))

RESPONSE=$(make_api_request "POLL")
echo "Polling attempt $local_retry/$RETRY_COUNT"

# Stop polling if API response is non-200
status_code=$(echo "$RESPONSE" | jq -r '.status_code')
if [[ $status_code -ne 200 ]]; then
echo "Polling stopped due to non-200 response from API. Status code: $status_code"
break
fi

check_report_status "$RESPONSE" && {
echo "Valid report status received. Exiting polling loop...."
break
}

sleep "$POLLING_DURATION"
done

# Handle Report
if [[ -n "$rich_html_response" ]]; then
# Embed CSS into the rich HTML report
mkdir -p browserstack
echo "<!DOCTYPE html>
<html>
<head>
<style>
$rich_css_response
</style>
</head>
$rich_html_response
</html>" > browserstack/testreport.html
echo "Rich html report saved as browserstack/testreport.html. To view the report, open artifacts tab & click on testreport.html"

# Generate plain text report
if [[ -n "$plain_text_response" ]]; then
echo ""
echo "Browserstack textual report"
echo "$plain_text_response"
else
echo "Plain text response is empty."
fi
elif [[ "$REPORT_STATUS" == "$REPORT_STATUS_NOT_AVAILABLE" ]]; then
error_reason=$(echo "$RESPONSE" | jq -r '.body.errorReason // empty')
default_error_message="Failed to retrieve report. Reason:"
if [[ -n "$error_reason" ]]; then
echo "$default_error_message ${ERROR_SCENARIOS[$error_reason]:-$error_reason}"
else
echo "$default_error_message Unexpected error"
fi
else
echo "Failed to retrieve report."
fi
# Ensure pipeline doesn't exit with non-zero status
exit 0