diff --git a/browserstack_ci_template.yml b/browserstack_ci_template.yml new file mode 100644 index 0000000..3114943 --- /dev/null +++ b/browserstack_ci_template.yml @@ -0,0 +1,223 @@ +.set_browserstack_config: + image: alpine:latest + script: | + echo "Setting Browserstack 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_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_vars.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_vars.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 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 + 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