From fca5bec6fb1109a4daa30e424518f2eba162b2a1 Mon Sep 17 00:00:00 2001 From: Amark19 Date: Mon, 11 Aug 2025 12:17:09 +0530 Subject: [PATCH 1/8] Added test report functionality for gitlab --- browserstack_test_report.yml | 223 +++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 browserstack_test_report.yml diff --git a/browserstack_test_report.yml b/browserstack_test_report.yml new file mode 100644 index 0000000..225cbdb --- /dev/null +++ b/browserstack_test_report.yml @@ -0,0 +1,223 @@ +.set_browserstack_config: + image: alpine:latest + script: | + echo "Setting config vars..." + + # Validate required variables + if [ -z "$BROWSERSTACK_USERNAME" ]; then + echo "Error: BROWSERSTACK_USERNAME is not set!" + exit 1 + fi + + if [ -z "$BROWSERSTACK_ACCESS_KEY" ]; then + echo "Error: BROWSERSTACK_ACCESS_KEY is not set!" + exit 1 + fi + + # Construct dynamic build name + export BROWSERSTACK_BUILD_NAME="gitlab-ci-${CI_PROJECT_NAME}-${CI_PIPELINE_IID}" + + # Write to browserstack_envvars.env + echo "BROWSERSTACK_USERNAME=${BROWSERSTACK_USERNAME}" > browserstack_envvars.env + echo "BROWSERSTACK_ACCESS_KEY=${BROWSERSTACK_ACCESS_KEY}" >> browserstack_envvars.env + echo "BROWSERSTACK_BUILD_NAME=${BROWSERSTACK_BUILD_NAME}" >> browserstack_envvars.env + cat browserstack_envvars.env + artifacts: + paths: + - browserstack_envvars.env + +.set_browserstack_test_report: + image: alpine:latest + script: | + echo "Installing bash, curl & jq" + apk add --no-cache bash curl jq + bash <<'EOF' + #!/bin/bash + source browserstack_envvars.env + 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="gitlab-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 [[ -z "$REPORT_TIMEOUT" ]]; then + REPORT_TIMEOUT=130 + fi + + if [[ "$REPORT_TIMEOUT" -lt 20 || "$REPORT_TIMEOUT" -gt 600 ]]; then + echo "Error: REPORT_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\": \"${REPORT_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 "Build Name: $BROWSERSTACK_BUILD_NAME will be used to fetch the report." + 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 "$REPORT_TIMEOUT" && "$ELAPSED_TIME" -ge "$REPORT_TIMEOUT" ]]; then + echo "User report 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 " + + + + + $rich_html_response + " > 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 + EOF + artifacts: + paths: + - browserstack/testreport.html From 7c8f02de6a59eab5265d66882714c6a5a65c2cc8 Mon Sep 17 00:00:00 2001 From: Amark19 Date: Mon, 11 Aug 2025 15:18:24 +0530 Subject: [PATCH 2/8] renamed file to ci_template --- browserstack_test_report.yml => browserstack_ci_template.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename browserstack_test_report.yml => browserstack_ci_template.yml (100%) diff --git a/browserstack_test_report.yml b/browserstack_ci_template.yml similarity index 100% rename from browserstack_test_report.yml rename to browserstack_ci_template.yml From ad8e637820a72479c705580759846470a84875b5 Mon Sep 17 00:00:00 2001 From: Amark19 Date: Mon, 11 Aug 2025 16:56:05 +0530 Subject: [PATCH 3/8] writing config to browserstack_vars.env --- browserstack_ci_template.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/browserstack_ci_template.yml b/browserstack_ci_template.yml index 225cbdb..2b939f4 100644 --- a/browserstack_ci_template.yml +++ b/browserstack_ci_template.yml @@ -1,7 +1,7 @@ .set_browserstack_config: image: alpine:latest script: | - echo "Setting config vars..." + echo "Setting Browserstack Config vars..." # Validate required variables if [ -z "$BROWSERSTACK_USERNAME" ]; then @@ -17,14 +17,14 @@ # Construct dynamic build name export BROWSERSTACK_BUILD_NAME="gitlab-ci-${CI_PROJECT_NAME}-${CI_PIPELINE_IID}" - # Write to browserstack_envvars.env - echo "BROWSERSTACK_USERNAME=${BROWSERSTACK_USERNAME}" > browserstack_envvars.env - echo "BROWSERSTACK_ACCESS_KEY=${BROWSERSTACK_ACCESS_KEY}" >> browserstack_envvars.env - echo "BROWSERSTACK_BUILD_NAME=${BROWSERSTACK_BUILD_NAME}" >> browserstack_envvars.env - cat browserstack_envvars.env + # Write to browserstack_vars.env + echo "BROWSERSTACK_USERNAME=${BROWSERSTACK_USERNAME}" > browserstack_vars.env + echo "BROWSERSTACK_ACCESS_KEY=${BROWSERSTACK_ACCESS_KEY}" >> browserstack_vars.env + echo "BROWSERSTACK_BUILD_NAME=${BROWSERSTACK_BUILD_NAME}" >> browserstack_vars.env + cat browserstack_vars.env artifacts: paths: - - browserstack_envvars.env + - browserstack_vars.env .set_browserstack_test_report: image: alpine:latest @@ -33,7 +33,7 @@ apk add --no-cache bash curl jq bash <<'EOF' #!/bin/bash - source browserstack_envvars.env + source browserstack_vars.env API_PATH="https://api-observability.browserstack.com/ext/v1/builds/buildReport" REPORT_STATUS_COMPLETED="COMPLETED" REPORT_STATUS_NOT_AVAILABLE="NOT_AVAILABLE" From 6a78264f8b218238279acbd2ec2d780e2e599fa7 Mon Sep 17 00:00:00 2001 From: Amark19 Date: Mon, 11 Aug 2025 17:55:44 +0530 Subject: [PATCH 4/8] some debug logs added & exit if check_report_status is returning nonZero --- browserstack_ci_template.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/browserstack_ci_template.yml b/browserstack_ci_template.yml index 2b939f4..127cde7 100644 --- a/browserstack_ci_template.yml +++ b/browserstack_ci_template.yml @@ -120,7 +120,10 @@ local error_message status_code=$(echo "$response" | jq -r '.status_code') + # todo-remove + echo "Response Status Code in check_report_status: $status_code" body=$(echo "$response" | jq -r '.body') + echo "Response body in check_report_status: body" if [[ $status_code -ne 200 ]]; then echo "Error: API returned status code $status_code" @@ -143,7 +146,7 @@ # Initial API Request RESPONSE=$(make_api_request "FIRST") - check_report_status "$RESPONSE" || true + check_report_status "$RESPONSE" || exit 0 RETRY_COUNT=$(echo "$RESPONSE" | jq -r '.body.retryCount // 3') POLLING_DURATION=$(echo "$RESPONSE" | jq -r '.body.pollingInterval // 3') From db435691810d131e784ba5c15ca9357fbc66bd3b Mon Sep 17 00:00:00 2001 From: Amark19 Date: Mon, 11 Aug 2025 18:25:48 +0530 Subject: [PATCH 5/8] handling non200 correctly --- browserstack_ci_template.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/browserstack_ci_template.yml b/browserstack_ci_template.yml index 127cde7..924fb95 100644 --- a/browserstack_ci_template.yml +++ b/browserstack_ci_template.yml @@ -120,10 +120,7 @@ local error_message status_code=$(echo "$response" | jq -r '.status_code') - # todo-remove - echo "Response Status Code in check_report_status: $status_code" body=$(echo "$response" | jq -r '.body') - echo "Response body in check_report_status: body" if [[ $status_code -ne 200 ]]; then echo "Error: API returned status code $status_code" @@ -146,7 +143,11 @@ # Initial API Request RESPONSE=$(make_api_request "FIRST") - check_report_status "$RESPONSE" || exit 0 + check_report_status "$RESPONSE" + report_status_return=$? + if [[ report_status_return -eq 1 ]]; then + exit 0 + fi RETRY_COUNT=$(echo "$RESPONSE" | jq -r '.body.retryCount // 3') POLLING_DURATION=$(echo "$RESPONSE" | jq -r '.body.pollingInterval // 3') From 00cf8ae99e39bc81f2105d5687d1de9b575673e6 Mon Sep 17 00:00:00 2001 From: Amark19 Date: Mon, 11 Aug 2025 18:28:46 +0530 Subject: [PATCH 6/8] removed if condition to check non2xx --- browserstack_ci_template.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/browserstack_ci_template.yml b/browserstack_ci_template.yml index 924fb95..8267d15 100644 --- a/browserstack_ci_template.yml +++ b/browserstack_ci_template.yml @@ -120,7 +120,10 @@ local error_message status_code=$(echo "$response" | jq -r '.status_code') + # todo-remove + echo "Response Status Code in check_report_status: $status_code" body=$(echo "$response" | jq -r '.body') + echo "Response body in check_report_status: $body" if [[ $status_code -ne 200 ]]; then echo "Error: API returned status code $status_code" @@ -143,11 +146,7 @@ # Initial API Request RESPONSE=$(make_api_request "FIRST") - check_report_status "$RESPONSE" - report_status_return=$? - if [[ report_status_return -eq 1 ]]; then - exit 0 - fi + check_report_status "$RESPONSE" || true RETRY_COUNT=$(echo "$RESPONSE" | jq -r '.body.retryCount // 3') POLLING_DURATION=$(echo "$RESPONSE" | jq -r '.body.pollingInterval // 3') From 88ef071d505345c991561fb29d9da6e00ecf09eb Mon Sep 17 00:00:00 2001 From: Amark19 Date: Mon, 11 Aug 2025 18:33:06 +0530 Subject: [PATCH 7/8] removed logging --- browserstack_ci_template.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/browserstack_ci_template.yml b/browserstack_ci_template.yml index 8267d15..2b939f4 100644 --- a/browserstack_ci_template.yml +++ b/browserstack_ci_template.yml @@ -120,10 +120,7 @@ local error_message status_code=$(echo "$response" | jq -r '.status_code') - # todo-remove - echo "Response Status Code in check_report_status: $status_code" body=$(echo "$response" | jq -r '.body') - echo "Response body in check_report_status: $body" if [[ $status_code -ne 200 ]]; then echo "Error: API returned status code $status_code" From 77c24a94da26bfd8dd1736770d184aef34fc6b32 Mon Sep 17 00:00:00 2001 From: Amar Khamkar <71604396+Amark19@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:55:50 +0530 Subject: [PATCH 8/8] corrected view artifacts log line --- browserstack_ci_template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browserstack_ci_template.yml b/browserstack_ci_template.yml index 2b939f4..3114943 100644 --- a/browserstack_ci_template.yml +++ b/browserstack_ci_template.yml @@ -194,7 +194,7 @@ $rich_html_response " > browserstack/testreport.html - echo "Rich html report saved as browserstack/testreport.html. To view the report, open artifacts tab & click on testreport.html" + echo "Rich html report saved as browserstack/testreport.html. To download the report, open artifacts tab & go to a job which extends set_browserstack_test_report job." # Generate plain text report if [[ -n "$plain_text_response" ]]; then