493 lines
No EOL
18 KiB
YAML
493 lines
No EOL
18 KiB
YAML
parameters:
|
|
- name: totalShards
|
|
type: number
|
|
default: 2
|
|
values: [1, 2, 4, 8]
|
|
- name: targetUrl
|
|
type: string
|
|
- name: dockerFileName
|
|
type: string
|
|
default: 'Dockerfile.playwright'
|
|
- name: isRegression
|
|
type: boolean
|
|
default: false
|
|
- name: filterTags
|
|
type: string
|
|
default: '@smoke'
|
|
- name: playwrightTestsPath
|
|
type: string
|
|
default: 'playwright-tests'
|
|
- name: npmServePath
|
|
type: string
|
|
default: '.'
|
|
- name: npmrcPath
|
|
type: string
|
|
default: 'playwright-tests/.npmrc'
|
|
- name: secrets
|
|
type: object
|
|
default: {}
|
|
- name: dockerImageName
|
|
type: string
|
|
default: 'playwright-tests'
|
|
- name: imageTag
|
|
type: string
|
|
default: '$(Build.BuildId)'
|
|
- name: dockerBuildArgs
|
|
type: string
|
|
default: ''
|
|
- name: envFileName
|
|
type: string
|
|
default: '.env.ci'
|
|
- name: applicationType
|
|
type: string
|
|
default: 'vue'
|
|
values: ['vue', 'dotnet']
|
|
- name: readinessEndpoint
|
|
type: string
|
|
default: '/healthcheck'
|
|
- name: serverMaxAttempts
|
|
type: number
|
|
default: 60
|
|
|
|
jobs:
|
|
- job: playwright_tests
|
|
continueOnError: true
|
|
strategy:
|
|
matrix:
|
|
${{ if eq(parameters.totalShards, 1) }}:
|
|
shard1:
|
|
shardNumber: 1
|
|
${{ if eq(parameters.totalShards, 2) }}:
|
|
shard1:
|
|
shardNumber: 1
|
|
shard2:
|
|
shardNumber: 2
|
|
${{ if eq(parameters.totalShards, 4) }}:
|
|
shard1:
|
|
shardNumber: 1
|
|
shard2:
|
|
shardNumber: 2
|
|
shard3:
|
|
shardNumber: 3
|
|
shard4:
|
|
shardNumber: 4
|
|
${{ if eq(parameters.totalShards, 8) }}:
|
|
shard1:
|
|
shardNumber: 1
|
|
shard2:
|
|
shardNumber: 2
|
|
shard3:
|
|
shardNumber: 3
|
|
shard4:
|
|
shardNumber: 4
|
|
shard5:
|
|
shardNumber: 5
|
|
shard6:
|
|
shardNumber: 6
|
|
shard7:
|
|
shardNumber: 7
|
|
shard8:
|
|
shardNumber: 8
|
|
steps:
|
|
- task: npmAuthenticate@0
|
|
inputs:
|
|
workingFile: ${{ parameters.npmrcPath }}
|
|
|
|
- script: |
|
|
echo "Starting Docker cleanup to free space..."
|
|
|
|
# Remove all stopped containers
|
|
echo "Removing stopped containers..."
|
|
docker container prune -f
|
|
|
|
# Remove dangling images (untagged)
|
|
echo "Removing dangling images..."
|
|
docker image prune -f
|
|
|
|
# Remove unused volumes
|
|
echo "Removing unused volumes..."
|
|
docker volume prune -f
|
|
|
|
# Show disk usage after cleanup
|
|
echo "Docker system usage after cleanup:"
|
|
docker system df
|
|
|
|
echo "Docker cleanup completed!"
|
|
displayName: "Docker Cleanup"
|
|
|
|
- bash: |
|
|
printenv > "${{ parameters.envFileName }}"
|
|
if [ -z "$(GITHUB_TOKEN)" ]; then
|
|
echo "Printed env, but GITHUB_TOKEN was undefined"
|
|
fi
|
|
env: ${{ parameters.secrets }}
|
|
displayName: "Make Azure Pipeline Variables Available to Docker"
|
|
|
|
- task: Docker@2
|
|
displayName: 'Build Docker Image'
|
|
inputs:
|
|
command: build
|
|
dockerfile: ${{ parameters.dockerFileName }}
|
|
repository: ${{ parameters.dockerImageName }}
|
|
tags: ${{ parameters.imageTag }}
|
|
arguments: |
|
|
--no-cache --pull ${{ parameters.dockerBuildArgs }}
|
|
|
|
- script: |
|
|
# Determine JIRA card number and filters
|
|
JIRA_CARD_NUMBER=""
|
|
IS_REGRESSION="${{ parameters.isRegression }}"
|
|
echo "isRegression: $IS_REGRESSION"
|
|
|
|
if [[ "${{ parameters.isRegression }}" == "False" ]]; then
|
|
branch_name="$(System.PullRequest.SourceBranch)"
|
|
echo "Retrieved branch name: '$branch_name'"
|
|
JIRA_CARD_NUMBER="${branch_name##*/}"
|
|
echo "Extracted JIRA Card number: '$JIRA_CARD_NUMBER'"
|
|
fi
|
|
|
|
# Build test filter
|
|
FILTER=""
|
|
if [[ -n "${{ parameters.filterTags }}" && -n "$JIRA_CARD_NUMBER" ]]; then
|
|
echo "Filtering by filterTags and Jira Card Number..."
|
|
FILTER="--grep '@$JIRA_CARD_NUMBER|${{ parameters.filterTags }}'"
|
|
elif [[ -n "${{ parameters.filterTags }}" ]]; then
|
|
echo "Filtering by filterTags only..."
|
|
FILTER="--grep '${{ parameters.filterTags }}'"
|
|
elif [[ -n "$JIRA_CARD_NUMBER" ]]; then
|
|
echo "Filtering by Jira card number only..."
|
|
FILTER="--grep '@$JIRA_CARD_NUMBER'"
|
|
else
|
|
echo "No filter. Running all test cases..."
|
|
FILTER=""
|
|
fi
|
|
|
|
echo "Filter value: $FILTER"
|
|
TARGET_URL="${{ parameters.targetUrl }}"
|
|
echo "Target URL: $TARGET_URL"
|
|
echo "Application Type: ${{ parameters.applicationType }}"
|
|
|
|
# Set common variables
|
|
PLAYWRIGHT_PATH="${{ parameters.playwrightTestsPath }}"
|
|
SERVE_PATH="${{ parameters.npmServePath }}"
|
|
SHARD_NUMBER=$(shardNumber)
|
|
TOTAL_SHARDS=${{ parameters.totalShards }}
|
|
APPLICATION_TYPE="${{ parameters.applicationType }}"
|
|
ENV_FILE="${{ parameters.envFileName }}"
|
|
|
|
# Handle localhost URLs
|
|
if [[ "$TARGET_URL" == *localhost* ]]; then
|
|
echo "Localhost URL detected, extracting port..."
|
|
port=$(echo "$TARGET_URL" | sed -E 's/.*:([0-9]+).*/\1/')
|
|
echo "Port extracted: $port"
|
|
|
|
# Create readiness check URL based on application type
|
|
if [[ "$APPLICATION_TYPE" == "dotnet" ]]; then
|
|
# For .NET APIs, append the readiness endpoint to the target URL
|
|
READINESS_URL="${TARGET_URL}${{ parameters.readinessEndpoint }}"
|
|
echo "Using .NET readiness check URL: $READINESS_URL"
|
|
else
|
|
# For Vue apps, use the target URL as-is
|
|
READINESS_URL="$TARGET_URL"
|
|
echo "Using Vue app URL for readiness check: $READINESS_URL"
|
|
fi
|
|
|
|
echo "Starting tests for shard $SHARD_NUMBER of $TOTAL_SHARDS..."
|
|
|
|
container_id=$(docker create \
|
|
--ipc=host \
|
|
--env CI=true \
|
|
--env TARGET_URL="$TARGET_URL" \
|
|
--env READINESS_URL="$READINESS_URL" \
|
|
--env PLAYWRIGHT_PATH="$PLAYWRIGHT_PATH" \
|
|
--env SERVE_PATH="$SERVE_PATH" \
|
|
--env SHARD_NUMBER="$SHARD_NUMBER" \
|
|
--env TOTAL_SHARDS="$TOTAL_SHARDS" \
|
|
--env FILTER="$FILTER" \
|
|
--env APPLICATION_TYPE="$APPLICATION_TYPE" \
|
|
--env SERVER_MAX_ATTEMPTS="${{ parameters.serverMaxAttempts }}" \
|
|
--env ASPNETCORE_URLS="https://localhost:$port" \
|
|
--env ENV_FILE="$ENV_FILE" \
|
|
--env-file "${{ parameters.envFileName }}" \
|
|
${{ parameters.dockerImageName }}:${{ parameters.imageTag }} \
|
|
bash -c '
|
|
echo "=== Starting Application Server ==="
|
|
echo "Application Type: $APPLICATION_TYPE"
|
|
echo "Target URL: $TARGET_URL"
|
|
echo "Readiness Check URL: $READINESS_URL"
|
|
echo "Server Max Attempts: $SERVER_MAX_ATTEMPTS seconds"
|
|
|
|
# Function to wait for server readiness
|
|
wait_for_server() {
|
|
local url=$1
|
|
local max_attempts=$SERVER_MAX_ATTEMPTS
|
|
local attempt=1
|
|
|
|
echo "Waiting for server at: $url"
|
|
|
|
while [ $attempt -le $max_attempts ]; do
|
|
echo "Attempt $attempt/$max_attempts: Checking server health..."
|
|
|
|
if [[ "$APPLICATION_TYPE" == "vue" ]]; then
|
|
# For Vue apps, use a simple HTTP check
|
|
if curl -s -f "$url" --max-time 10 > /dev/null 2>&1; then
|
|
echo "Vue server is ready!"
|
|
return 0
|
|
fi
|
|
else
|
|
# For .NET APIs, use healthcheck endpoint
|
|
if curl -k -s -f "$url" --max-time 10 > /dev/null 2>&1; then
|
|
echo ".NET server is ready!"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [ $attempt -eq $max_attempts ]; then
|
|
echo "Server failed to become ready after $max_attempts attempts"
|
|
return 1
|
|
fi
|
|
|
|
echo "Server not ready yet, waiting 2 seconds..."
|
|
sleep 2
|
|
attempt=$((attempt + 1))
|
|
done
|
|
}
|
|
|
|
# Install curl if not available
|
|
if ! command -v curl &> /dev/null; then
|
|
echo "Installing curl..."
|
|
apk add --no-cache curl 2>/dev/null || apt-get update && apt-get install -y curl 2>/dev/null || true
|
|
fi
|
|
|
|
# Start server and run tests based on application type
|
|
if [[ "$APPLICATION_TYPE" == "vue" ]]; then
|
|
echo "=== Using Vue Mode with wait-on and concurrently ==="
|
|
|
|
# Extract port for Vue server
|
|
port=$(echo "$TARGET_URL" | sed -E "s/.*:([0-9]+).*/\1/")
|
|
echo "Starting Vue server on port: $port"
|
|
|
|
# Create blob report directory
|
|
mkdir -p /app/blob-report
|
|
|
|
# Use concurrently for Vue apps (original approach)
|
|
if [ -n "$FILTER" ]; then
|
|
echo "Running Vue tests with filter: $FILTER"
|
|
npx concurrently -k -n "server,playwright" \
|
|
"npm --prefix $SERVE_PATH run serve -- --port=$port" \
|
|
"npx --prefix $PLAYWRIGHT_PATH wait-on $TARGET_URL && cd $PLAYWRIGHT_PATH && PLAYWRIGHT_BLOB_OUTPUT_DIR=\"/app/blob-report\" npx dotenv-cli -e \"../$ENV_FILE\" -- playwright test --config=./playwright.config.ts --shard=$SHARD_NUMBER/$TOTAL_SHARDS --reporter=list,blob $FILTER"
|
|
else
|
|
echo "Running all Vue tests"
|
|
npx concurrently -k -n "server,playwright" \
|
|
"npm --prefix $SERVE_PATH run serve -- --port=$port" \
|
|
"npx --prefix $PLAYWRIGHT_PATH wait-on $TARGET_URL && cd $PLAYWRIGHT_PATH && PLAYWRIGHT_BLOB_OUTPUT_DIR=\"/app/blob-report\" npx dotenv-cli -e \"../$ENV_FILE\" -- playwright test --config=./playwright.config.ts --shard=$SHARD_NUMBER/$TOTAL_SHARDS --reporter=list,blob"
|
|
fi
|
|
|
|
else
|
|
echo "=== Using .NET Mode with healthcheck ==="
|
|
echo "Starting .NET server..."
|
|
echo "ASPNETCORE_URLS: $ASPNETCORE_URLS"
|
|
|
|
npm --prefix "$SERVE_PATH" run serve &
|
|
SERVER_PID=$!
|
|
echo ".NET server started with PID: $SERVER_PID"
|
|
|
|
# Wait for .NET server readiness check
|
|
if ! wait_for_server "$READINESS_URL"; then
|
|
kill $SERVER_PID 2>/dev/null || true
|
|
exit 1
|
|
fi
|
|
|
|
echo "=== Starting Playwright Tests ==="
|
|
|
|
# Create blob report directory
|
|
mkdir -p /app/blob-report
|
|
cd "$PLAYWRIGHT_PATH"
|
|
|
|
# Run Playwright tests
|
|
if [ -n "$FILTER" ]; then
|
|
echo "Running .NET tests with filter: $FILTER"
|
|
PLAYWRIGHT_BLOB_OUTPUT_DIR="/app/blob-report" npx dotenv-cli -e "../$ENV_FILE" -- playwright test \
|
|
--config="./playwright.config.ts" \
|
|
--shard="$SHARD_NUMBER/$TOTAL_SHARDS" \
|
|
--reporter=list,blob \
|
|
$FILTER || true
|
|
else
|
|
echo "Running all .NET tests"
|
|
PLAYWRIGHT_BLOB_OUTPUT_DIR="/app/blob-report" npx dotenv-cli -e "../$ENV_FILE" -- playwright test \
|
|
--config="./playwright.config.ts" \
|
|
--shard="$SHARD_NUMBER/$TOTAL_SHARDS" \
|
|
--reporter=list,blob || true
|
|
fi
|
|
|
|
TEST_EXIT_CODE=$?
|
|
echo "Tests completed with exit code: $TEST_EXIT_CODE"
|
|
|
|
# Clean up server
|
|
echo "Stopping server..."
|
|
kill $SERVER_PID 2>/dev/null || true
|
|
|
|
exit $TEST_EXIT_CODE
|
|
fi
|
|
'
|
|
)
|
|
else
|
|
# Remote URL testing
|
|
echo "Remote URL testing mode"
|
|
container_id=$(docker create \
|
|
--ipc=host \
|
|
--env CI=true \
|
|
--env TARGET_URL="$TARGET_URL" \
|
|
--env PLAYWRIGHT_PATH="$PLAYWRIGHT_PATH" \
|
|
--env SHARD_NUMBER="$SHARD_NUMBER" \
|
|
--env TOTAL_SHARDS="$TOTAL_SHARDS" \
|
|
--env FILTER="$FILTER" \
|
|
--env ENV_FILE="$ENV_FILE" \
|
|
--env-file "${{ parameters.envFileName }}" \
|
|
${{ parameters.dockerImageName }}:${{ parameters.imageTag }} \
|
|
bash -c "
|
|
echo '=== Running Tests Against Remote URL ==='
|
|
echo 'Target URL: $TARGET_URL'
|
|
|
|
mkdir -p /app/blob-report
|
|
cd \$PLAYWRIGHT_PATH
|
|
|
|
if [ -n \"\$FILTER\" ]; then
|
|
echo 'Running tests with filter: \$FILTER'
|
|
PLAYWRIGHT_BLOB_OUTPUT_DIR='/app/blob-report' npx dotenv-cli -e \"../\$ENV_FILE\" -- playwright test \
|
|
--config=./playwright.config.ts \
|
|
--shard=\$SHARD_NUMBER/\$TOTAL_SHARDS \
|
|
--reporter=list,blob \
|
|
\$FILTER || true
|
|
else
|
|
echo 'Running all tests'
|
|
PLAYWRIGHT_BLOB_OUTPUT_DIR='/app/blob-report' npx dotenv-cli -e \"../\$ENV_FILE\" -- playwright test \
|
|
--config=./playwright.config.ts \
|
|
--shard=\$SHARD_NUMBER/\$TOTAL_SHARDS \
|
|
--reporter=list,blob || true
|
|
fi
|
|
"
|
|
)
|
|
fi
|
|
|
|
# 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
|
|
displayName: 'Run Playwright Tests - Shard $(shardNumber)'
|
|
env: ${{ parameters.secrets }}
|
|
|
|
- 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 ${{ parameters.dockerImageName }}:${{ parameters.imageTag }} -f
|
|
displayName: 'Cleanup Docker Image'
|
|
condition: always()
|
|
|
|
- job: download_and_merge_reports
|
|
dependsOn: playwright_tests
|
|
timeoutInMinutes: 8
|
|
cancelTimeoutInMinutes: 10
|
|
steps:
|
|
- task: npmAuthenticate@0
|
|
inputs:
|
|
workingFile: ${{ parameters.npmrcPath }}
|
|
- task: DownloadPipelineArtifact@2
|
|
inputs:
|
|
targetPath: '$(System.DefaultWorkingDirectory)/playwright-reports'
|
|
- bash: |
|
|
printenv > "${{ parameters.envFileName }}"
|
|
env: ${{ parameters.secrets }}
|
|
displayName: "Make Azure Pipeline Variables Available to Docker"
|
|
- task: Docker@2
|
|
displayName: 'Build Docker Image'
|
|
inputs:
|
|
command: build
|
|
dockerfile: ${{ parameters.dockerFileName }}
|
|
repository: ${{ parameters.dockerImageName }}
|
|
tags: ${{ parameters.imageTag }}
|
|
arguments: |
|
|
--no-cache --pull ${{ parameters.dockerBuildArgs }}
|
|
- 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'"
|
|
export PLAYWRIGHT_PATH="${{ parameters.playwrightTestsPath }}"
|
|
|
|
export JIRA_CARD_NUMBER="$JIRA_CARD_NUMBER"
|
|
export ENV_FILE="${{ parameters.envFileName }}"
|
|
container_id=$(docker create \
|
|
--ipc=host \
|
|
--env CI=true \
|
|
--env-file "${{ parameters.envFileName }}" \
|
|
${{ parameters.dockerImageName }}:${{ parameters.imageTag }} \
|
|
bash -c "
|
|
echo 'Moving Playwright reports out of subfolders...'
|
|
find ./playwright-reports/ -mindepth 2 -type f -exec mv {} ./playwright-reports/ \;
|
|
echo 'Value of JIRA_CARD_NUMBER in bash -c command: $JIRA_CARD_NUMBER'
|
|
echo 'Merging reports...'
|
|
cd $PLAYWRIGHT_PATH
|
|
JIRA_CARD_NUMBER='$JIRA_CARD_NUMBER' PLAYWRIGHT_JUNIT_OUTPUT_DIR='/app/test-results' PLAYWRIGHT_JUNIT_OUTPUT_NAME='junit_results.xml' npx dotenv-cli -e ../$ENV_FILE -- playwright merge-reports --config=./playwright.config.ts ../playwright-reports
|
|
"
|
|
)
|
|
|
|
# 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
|
|
displayName: merge_and_publish_results_to_jira
|
|
env: ${{ parameters.secrets }}
|
|
|
|
- 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 ${{ parameters.dockerImageName }}:${{ parameters.imageTag }} -f
|
|
displayName: 'Cleanup Docker Image'
|
|
condition: always() |