Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/actions/authorize-pr/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ runs:
HAS_LABEL=$(echo "$LABELS_JSON" | jq -r --arg l "$LABEL_NAME" \
'if . == null then "false" elif any(. == $l) then "true" else "false" end')

# On synchronize from a non-writer, reject even if the label is still
# present. The label was granted for the previous commit; new unreviewed
# code must not run with secrets. The strip step below removes the label
# so that a re-review + re-label is required for the next run.
if [ "$ACTION" = "synchronize" ]; then
echo "::warning::External fork synchronize from non-writer — blocking until re-review"
echo "allowed=false" >> "$GITHUB_OUTPUT"
exit 0
fi

if [ "$HAS_LABEL" = "true" ]; then
echo "::notice::Fork PR with '$LABEL_NAME' label — authorized"
echo "allowed=true" >> "$GITHUB_OUTPUT"
Expand Down
100 changes: 100 additions & 0 deletions .github/workflows/on-pr-test-sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# QVAC SDK E2E Tests - PR Trigger
#
# Triggers SDK e2e tests via test-sdk.yml when:
# - "test-e2e-smoke" label applied: runs smoke suite on all platforms
# - "test-e2e-full" label applied: runs full suite on all platforms
# - PR opened/updated targeting release-* branch with packages/sdk/ changes: runs full suite
#
# On success, applies "e2e-tested" label to the PR.
#
# Uses pull_request_target for secrets access (Device Farm, MQTT).
# Authorization gate prevents untrusted fork PRs from running.

name: QVAC Tests (sdk) - PR

on:
pull_request_target:
types: [labeled, opened, synchronize]
paths:
- "packages/sdk/**"

permissions:
contents: read
pull-requests: write
packages: read

jobs:
resolve-config:
runs-on: ubuntu-latest
outputs:
should-run: ${{ steps.check.outputs.should-run }}
suite: ${{ steps.check.outputs.suite }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
with:
sparse-checkout: .github/actions/authorize-pr
sparse-checkout-cone-mode: false

- name: Authorize PR
id: auth
uses: ./.github/actions/authorize-pr
with:
label: safe-to-test
github-token: ${{ github.token }}

- name: Determine test config
if: steps.auth.outputs.allowed == 'true'
id: check
shell: bash
run: |
EVENT="${{ github.event.action }}"
LABEL="${{ github.event.label.name }}"
TARGET="${{ github.base_ref }}"

if [ "$EVENT" = "labeled" ]; then
if [ "$LABEL" = "test-e2e-smoke" ]; then
echo "should-run=true" >> "$GITHUB_OUTPUT"
echo "suite=smoke" >> "$GITHUB_OUTPUT"
elif [ "$LABEL" = "test-e2e-full" ]; then
echo "should-run=true" >> "$GITHUB_OUTPUT"
echo "suite=" >> "$GITHUB_OUTPUT"
else
echo "::notice::Ignoring unrelated label: $LABEL"
echo "should-run=false" >> "$GITHUB_OUTPUT"
fi
elif echo "$TARGET" | grep -q '^release-'; then
# Path filter on the trigger already ensures packages/sdk/ changes exist
echo "::notice::Release branch PR with SDK changes — running full suite"
echo "should-run=true" >> "$GITHUB_OUTPUT"
echo "suite=" >> "$GITHUB_OUTPUT"
else
echo "should-run=false" >> "$GITHUB_OUTPUT"
fi

run-tests:
needs: resolve-config
if: needs.resolve-config.outputs.should-run == 'true'
uses: ./.github/workflows/test-sdk.yml
with:
targets: all
suite: ${{ needs.resolve-config.outputs.suite }}
secrets: inherit

apply-label:
needs: run-tests
if: success()
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Apply e2e-tested label
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # 7.0.1
with:
script: |
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ['e2e-tested']
});
76 changes: 71 additions & 5 deletions .github/workflows/test-android-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ on:
description: "Filter tests by category or testId prefix (comma-separated)"
required: false
type: string
suite:
description: "Suite filter (e.g. 'smoke'). Empty = all tests."
required: false
type: string
exclude-suite:
description: "Exclude suites (comma-separated)"
required: false
type: string
device-farm-timeout:
description: "Maximum time in minutes to keep Device Farm session alive."
required: false
Expand Down Expand Up @@ -517,17 +525,31 @@ jobs:
echo " Run ID: ${{ needs.build.outputs.runId }}"
echo " Consumer timeout: ${{ inputs.consumer-timeout }}s"
echo " Filter: ${{ inputs.filter || '(none)' }}"
echo " Suite: ${{ inputs.suite || '(all)' }}"
echo " Exclude suite: ${{ inputs.exclude-suite || '(none)' }}"

FILTER_ARG=""
if [ -n "${{ inputs.filter }}" ]; then
FILTER_ARG="--filter=${{ inputs.filter }}"
fi

SUITE_ARG=""
if [ -n "${{ inputs.suite }}" ]; then
SUITE_ARG="--suite=${{ inputs.suite }}"
fi

EXCLUDE_SUITE_ARG=""
if [ -n "${{ inputs.exclude-suite }}" ]; then
EXCLUDE_SUITE_ARG="--exclude-suite=${{ inputs.exclude-suite }}"
fi

npx qvac-test run:producer \
--runId "${{ needs.build.outputs.runId }}" \
--consumer-timeout "${{ inputs.consumer-timeout }}" \
--config . \
$FILTER_ARG
$FILTER_ARG \
$SUITE_ARG \
$EXCLUDE_SUITE_ARG

- name: Upload results
if: always()
Expand All @@ -551,7 +573,7 @@ jobs:
if: always()
runs-on: ubuntu-latest
environment: release
timeout-minutes: 5
timeout-minutes: 35
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # 6.0.0
Expand All @@ -566,9 +588,10 @@ jobs:
path: ./run-arns
merge-multiple: true

- name: Stop Device Farm runs
- name: Stop Device Farm runs and download artifacts
run: |
echo "Cleaning up Device Farm runs for runId: ${{ needs.build.outputs.runId }}"
mkdir -p ./device-farm-logs

for arn_file in run-arns/*.txt; do
if [ -f "$arn_file" ]; then
Expand All @@ -581,12 +604,55 @@ jobs:
if [ "$STATUS" = "RUNNING" ] || [ "$STATUS" = "PENDING" ]; then
echo "Stopping run: $RUN_ARN"
aws devicefarm stop-run --arn "$RUN_ARN" || true
else
echo "Run already finished ($STATUS), skipping"
fi

echo "Waiting for run to finish: $DEVICE_NAME (up to 30 min)..."
for attempt in $(seq 1 120); do
STATUS=$(aws devicefarm get-run --arn "$RUN_ARN" --query 'run.status' --output text 2>/dev/null || echo "UNKNOWN")
if [ "$STATUS" = "COMPLETED" ] || [ "$STATUS" = "STOPPED" ] || [ "$STATUS" = "ERRORED" ]; then
echo "Run $DEVICE_NAME finished with status: $STATUS"
break
fi
if [ $((attempt % 6)) -eq 0 ]; then
echo "Run $DEVICE_NAME still $STATUS (${attempt}/120, $((attempt * 15))s elapsed)..."
fi
sleep 15
done

DEVICE_DIR="./device-farm-logs/$DEVICE_NAME"
mkdir -p "$DEVICE_DIR"

echo "Downloading Customer Artifacts for $DEVICE_NAME..."
JOBS=$(aws devicefarm list-jobs --arn "$RUN_ARN" --query 'jobs[*].arn' --output text 2>/dev/null || echo "")
for JOB_ARN in $JOBS; do
aws devicefarm list-artifacts \
--arn "$JOB_ARN" \
--type FILE \
--query "artifacts[?contains(name,'Customer')].[name,extension,url]" \
--output text 2>/dev/null \
| while IFS=$'\t' read -r NAME EXT URL; do
if [ -z "$URL" ] || [ "$URL" = "None" ]; then
continue
fi
SAFE_NAME=$(echo "$NAME" | tr ' /' '__')
echo " Downloading ${SAFE_NAME}.${EXT}"
curl -sS -o "$DEVICE_DIR/${SAFE_NAME}.${EXT}" "$URL" || echo " Failed: ${SAFE_NAME}.${EXT}"
done
done

echo "Artifacts for $DEVICE_NAME:"
ls -lh "$DEVICE_DIR" 2>/dev/null || echo " (empty)"
fi
done

- name: Upload Device Farm logs
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
with:
name: device-farm-logs-android-${{ needs.build.outputs.runId }}
path: ./device-farm-logs/
retention-days: 30

compare-results:
needs: [build, run-producer]
if: github.event_name == 'pull_request'
Expand Down
16 changes: 15 additions & 1 deletion .github/workflows/test-desktop-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ on:
description: 'Filter tests by category or testId prefix (comma-separated, e.g., "model,completion")'
required: false
type: string
suite:
description: "Suite filter (e.g. 'smoke'). Empty = all tests."
required: false
type: string
exclude-suite:
description: "Exclude suites (comma-separated)"
required: false
type: string
test-version:
description: "Git ref to checkout (branch, tag, or SHA). Defaults to trigger ref."
required: false
Expand Down Expand Up @@ -261,6 +269,10 @@ jobs:

const filter = '${{ inputs.filter }}';
const filterArgs = filter ? ['--filter=' + filter] : [];
const suite = '${{ inputs.suite }}';
const suiteArgs = suite ? ['--suite=' + suite] : [];
const excludeSuite = '${{ inputs.exclude-suite }}';
const excludeSuiteArgs = excludeSuite ? ['--exclude-suite=' + excludeSuite] : [];

const consumer = spawn('npx', [
'qvac-test', 'run:consumer:desktop',
Expand Down Expand Up @@ -309,7 +321,9 @@ jobs:
'--runId=${{ steps.runid.outputs.runId }}',
'--consumer-timeout=${{ inputs.consumer-timeout }}',
'--config=.',
...filterArgs
...filterArgs,
...suiteArgs,
...excludeSuiteArgs
];
console.log('Producer command: npx ' + producerArgs.join(' '));

Expand Down
52 changes: 32 additions & 20 deletions .github/workflows/test-ios-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ on:
description: "Filter tests by category or testId prefix (comma-separated)"
required: false
type: string
suite:
description: "Suite filter (e.g. 'smoke'). Empty = all tests."
required: false
type: string
exclude-suite:
description: "Exclude suites (comma-separated)"
required: false
type: string
device-farm-timeout:
description: "Maximum time in minutes to keep Device Farm session alive."
required: false
Expand Down Expand Up @@ -633,17 +641,31 @@ jobs:
echo " Run ID: ${{ needs.build.outputs.runId }}"
echo " Consumer timeout: ${{ inputs.consumer-timeout }}s"
echo " Filter: ${{ inputs.filter || '(none)' }}"
echo " Suite: ${{ inputs.suite || '(all)' }}"
echo " Exclude suite: ${{ inputs.exclude-suite || '(none)' }}"

FILTER_ARG=""
if [ -n "${{ inputs.filter }}" ]; then
FILTER_ARG="--filter=${{ inputs.filter }}"
fi

SUITE_ARG=""
if [ -n "${{ inputs.suite }}" ]; then
SUITE_ARG="--suite=${{ inputs.suite }}"
fi

EXCLUDE_SUITE_ARG=""
if [ -n "${{ inputs.exclude-suite }}" ]; then
EXCLUDE_SUITE_ARG="--exclude-suite=${{ inputs.exclude-suite }}"
fi

npx qvac-test run:producer \
--runId "${{ needs.build.outputs.runId }}" \
--consumer-timeout "${{ inputs.consumer-timeout }}" \
--config . \
$FILTER_ARG
$FILTER_ARG \
$SUITE_ARG \
$EXCLUDE_SUITE_ARG

- name: Upload results
if: always()
Expand All @@ -667,7 +689,7 @@ jobs:
if: always()
runs-on: ubuntu-latest
environment: release
timeout-minutes: 10
timeout-minutes: 35
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # 6.0.0
Expand All @@ -682,9 +704,10 @@ jobs:
path: ./run-arns
merge-multiple: true

- name: Stop Device Farm runs
- name: Stop Device Farm runs and download artifacts
run: |
echo "Cleaning up Device Farm runs for runId: ${{ needs.build.outputs.runId }}"
mkdir -p ./device-farm-logs

for arn_file in run-arns/*.txt; do
if [ -f "$arn_file" ]; then
Expand All @@ -697,30 +720,19 @@ jobs:
if [ "$STATUS" = "RUNNING" ] || [ "$STATUS" = "PENDING" ]; then
echo "Stopping run: $RUN_ARN"
aws devicefarm stop-run --arn "$RUN_ARN" || true
else
echo "Run already finished ($STATUS), skipping"
fi
fi
done

- name: Download Device Farm artifacts
run: |
mkdir -p ./device-farm-logs

for arn_file in run-arns/*.txt; do
if [ -f "$arn_file" ]; then
RUN_ARN=$(cat "$arn_file")
DEVICE_NAME=$(basename "$arn_file" .txt)

echo "Waiting for run to finish: $DEVICE_NAME"
for attempt in $(seq 1 30); do
echo "Waiting for run to finish: $DEVICE_NAME (up to 30 min)..."
for attempt in $(seq 1 120); do
STATUS=$(aws devicefarm get-run --arn "$RUN_ARN" --query 'run.status' --output text 2>/dev/null || echo "UNKNOWN")
if [ "$STATUS" = "COMPLETED" ] || [ "$STATUS" = "STOPPED" ] || [ "$STATUS" = "ERRORED" ]; then
echo "Run $DEVICE_NAME finished with status: $STATUS"
break
fi
echo "Run $DEVICE_NAME still $STATUS (attempt $attempt/30)..."
sleep 10
if [ $((attempt % 6)) -eq 0 ]; then
echo "Run $DEVICE_NAME still $STATUS (${attempt}/120, $((attempt * 15))s elapsed)..."
fi
sleep 15
done

DEVICE_DIR="./device-farm-logs/$DEVICE_NAME"
Expand Down
Loading
Loading