Skip to content

Commit c8b7b6c

Browse files
authored
ci: implement GitHub Actions runs for android ui tests (WPB-23413) (#4605)
1 parent 1cae972 commit c8b7b6c

19 files changed

Lines changed: 1473 additions & 165 deletions

File tree

Lines changed: 206 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
name: QA Android UI Tests
22

3+
# Concurrency lock:
4+
# - If androidDeviceId is set, we lock per-device so only one run can use that specific phone at a time (other devices can run in parallel).
5+
# - If androidDeviceId is empty ("auto"), we lock the shared device pool so only one auto-run uses the farm at a time (other auto-runs queue).
6+
concurrency:
7+
group: qa-android-ui-tests-office-${{ inputs.androidDeviceId || 'auto' }}
8+
cancel-in-progress: false
9+
310
on:
411
workflow_dispatch:
512
inputs:
@@ -44,34 +51,12 @@ on:
4451
- production
4552
default: internal release candidate
4653

47-
buildType:
48-
description: "Build type"
49-
required: true
50-
type: choice
51-
options:
52-
- release
53-
- debug
54-
- compat
55-
default: release
56-
5754
TAGS:
5855
description: "Tags: '@regression' OR '@TC-8143'."
5956
required: false
6057
default: ""
6158
type: string
6259

63-
branch:
64-
description: "Branch"
65-
required: true
66-
default: "develop"
67-
type: string
68-
69-
backendType:
70-
description: "Backend."
71-
required: true
72-
default: "staging"
73-
type: string
74-
7560
testinyRunName:
7661
description: "TESTINY_RUN_NAME."
7762
required: false
@@ -84,145 +69,216 @@ on:
8469
default: ""
8570
type: string
8671

87-
callingServiceEnv:
88-
description: "Calling service environment."
89-
required: true
90-
type: choice
91-
options:
92-
- dev
93-
- custom
94-
- master
95-
- avs
96-
- qa
97-
- edge
98-
- staging
99-
- prod
100-
default: dev
101-
102-
callingServiceUrl:
103-
description: "Calling service URL."
104-
required: true
105-
default: "loadbalanced"
106-
type: string
107-
108-
deflakeCount:
109-
description: "Rerun only failed tests."
110-
required: true
111-
default: 1
112-
type: number
72+
permissions:
73+
contents: read
11374

11475
jobs:
76+
# Validate user inputs and derive selectors.
11577
validate-and-resolve-inputs:
116-
name: Validate + resolve selectors (no execution)
78+
name: Validate + resolve selectors
11779
runs-on: ubuntu-latest
11880

11981
outputs:
120-
resolvedTestCaseId: ${{ steps.resolve.outputs.testCaseId }}
121-
resolvedCategory: ${{ steps.resolve.outputs.category }}
122-
resolvedTagKey: ${{ steps.resolve.outputs.tagKey }}
123-
resolvedTagValue: ${{ steps.resolve.outputs.tagValue }}
82+
resolvedTestCaseId: ${{ steps.resolve_selector.outputs.testCaseId }}
83+
resolvedCategory: ${{ steps.resolve_selector.outputs.category }}
12484

12585
steps:
86+
- name: Checkout repository
87+
uses: actions/checkout@v4
88+
89+
# Validate upgrade inputs before runner work starts.
12690
- name: Validate upgrade inputs
12791
shell: bash
128-
run: |
129-
set -euo pipefail
130-
if [[ "${{ inputs.isUpgrade }}" == "true" && -z "${{ inputs.oldBuildNumber }}" ]]; then
131-
echo "ERROR: oldBuildNumber is REQUIRED when isUpgrade=true"
132-
exit 1
133-
fi
92+
env:
93+
IS_UPGRADE: ${{ inputs.isUpgrade }}
94+
OLD_BUILD_NUMBER: ${{ inputs.oldBuildNumber }}
95+
run: bash scripts/qa_android_ui_tests/validation.sh validate-upgrade-inputs
13496

97+
# Resolve TAGS into CI selectors and expose them as job outputs.
13598
- name: Resolve selector from TAGS
136-
id: resolve
99+
id: resolve_selector
137100
shell: bash
138-
run: |
139-
set -euo pipefail
140-
141-
# We only use TAGS in this UI (no extra selector inputs)
142-
TESTCASE_ID=""
143-
CATEGORY=""
144-
TAG_KEY=""
145-
TAG_VALUE=""
146-
147-
TAGS_RAW="${{ inputs.TAGS }}"
148-
149-
trim() { echo "$1" | xargs; }
150-
151-
# If TAGS is provided, derive selector
152-
if [[ -n "$(trim "${TAGS_RAW}")" ]]; then
153-
# TAGS can be comma-separated; use first non-empty token
154-
sel=""
155-
IFS=',' read -ra parts <<< "${TAGS_RAW}"
156-
for p in "${parts[@]}"; do
157-
t="$(trim "$p")"
158-
if [[ -n "$t" ]]; then
159-
sel="$t"
160-
break
161-
fi
162-
done
163-
164-
# Strip leading @ if present
165-
sel="${sel#@}"
166-
sel="$(trim "$sel")"
167-
168-
# @TC-8143 -> testCaseId
169-
if [[ "$sel" =~ ^TC-[0-9]+$ ]]; then
170-
TESTCASE_ID="$sel"
171-
172-
# @key:value -> tagKey/tagValue (kept for future parity)
173-
elif [[ "$sel" == *:* ]]; then
174-
k="$(trim "${sel%%:*}")"
175-
v="$(trim "${sel#*:}")"
176-
if [[ -z "$k" || -z "$v" ]]; then
177-
echo "ERROR: Invalid TAGS format '${TAGS_RAW}'. Expected '@key:value' e.g. @criticalFlow:groupCallChat"
178-
exit 1
179-
fi
180-
TAG_KEY="$k"
181-
TAG_VALUE="$v"
182-
183-
# @regression -> category
184-
else
185-
CATEGORY="$sel"
186-
fi
187-
fi
188-
189-
# Safety: prevent partial key/value
190-
if [[ -n "$TAG_KEY" && -z "$TAG_VALUE" ]]; then
191-
echo "ERROR: tagKey provided but tagValue is empty."
192-
exit 1
193-
fi
194-
if [[ -z "$TAG_KEY" && -n "$TAG_VALUE" ]]; then
195-
echo "ERROR: tagValue provided but tagKey is empty."
196-
exit 1
197-
fi
198-
199-
echo "testCaseId=$TESTCASE_ID" >> "$GITHUB_OUTPUT"
200-
echo "category=$CATEGORY" >> "$GITHUB_OUTPUT"
201-
echo "tagKey=$TAG_KEY" >> "$GITHUB_OUTPUT"
202-
echo "tagValue=$TAG_VALUE" >> "$GITHUB_OUTPUT"
203-
204-
- name: Print final resolved inputs (for reviewers)
101+
env:
102+
TAGS_RAW: ${{ inputs.TAGS }}
103+
run: bash scripts/qa_android_ui_tests/validation.sh resolve-selector-from-tags
104+
105+
# Print resolved values for traceability in workflow logs.
106+
- name: Print resolved values
205107
shell: bash
206-
run: |
207-
set -euo pipefail
208-
echo "=== RAW INPUTS ==="
209-
echo "appBuildNumber=${{ inputs.appBuildNumber }}"
210-
echo "isUpgrade=${{ inputs.isUpgrade }}"
211-
echo "oldBuildNumber=${{ inputs.oldBuildNumber }}"
212-
echo "enforceAppInstall=${{ inputs.enforceAppInstall }}"
213-
echo "flavor=${{ inputs.flavor }}"
214-
echo "buildType=${{ inputs.buildType }}"
215-
echo "TAGS=${{ inputs.TAGS }}"
216-
echo "branch=${{ inputs.branch }}"
217-
echo "backendType=${{ inputs.backendType }}"
218-
echo "testinyRunName=${{ inputs.testinyRunName }}"
219-
echo "androidDeviceId=${{ inputs.androidDeviceId }}"
220-
echo "callingServiceEnv=${{ inputs.callingServiceEnv }}"
221-
echo "callingServiceUrl=${{ inputs.callingServiceUrl }}"
222-
echo "deflakeCount=${{ inputs.deflakeCount }}"
223-
echo ""
224-
echo "=== RESOLVED SELECTORS ==="
225-
echo "testCaseId=${{ steps.resolve.outputs.testCaseId }}"
226-
echo "category=${{ steps.resolve.outputs.category }}"
227-
echo "tagKey=${{ steps.resolve.outputs.tagKey }}"
228-
echo "tagValue=${{ steps.resolve.outputs.tagValue }}"
108+
env:
109+
FLAVOR_INPUT: ${{ inputs.flavor }}
110+
RESOLVED_TESTCASE_ID: ${{ steps.resolve_selector.outputs.testCaseId }}
111+
RESOLVED_CATEGORY: ${{ steps.resolve_selector.outputs.category }}
112+
run: bash scripts/qa_android_ui_tests/validation.sh print-resolved-values
113+
114+
run-android-ui-tests:
115+
name: Run Android UI tests
116+
runs-on:
117+
- self-hosted
118+
- Linux
119+
- X64
120+
- office
121+
- android-qa
122+
123+
needs: validate-and-resolve-inputs
124+
permissions:
125+
contents: write
126+
127+
env:
128+
AWS_REGION: eu-west-1
129+
S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
130+
OP_VAULT: "Test Automation"
131+
FLAVORS_CONFIG_PATH: "/etc/android-qa/flavors.json"
132+
133+
defaults:
134+
run:
135+
shell: bash
136+
137+
steps:
138+
- name: Checkout (with submodules)
139+
uses: actions/checkout@v4
140+
with:
141+
clean: true
142+
submodules: recursive
143+
144+
- name: Set up Java 17
145+
uses: actions/setup-java@v4
146+
with:
147+
distribution: temurin
148+
java-version: "17"
149+
cache: gradle
150+
151+
- name: Set up Android SDK (ANDROID_HOME + adb)
152+
uses: android-actions/setup-android@v3
153+
154+
# Verify required CLIs on the runner before setup continues.
155+
- name: Ensure required tools exist
156+
run: bash scripts/qa_android_ui_tests/execution_setup.sh ensure-required-tools
157+
158+
# Flavor resolution is runner-driven (from /etc/android-qa/flavors.json), not hardcoded in repo.
159+
# This bash subcommand exports S3_FOLDER, APP_ID, and PACKAGES_TO_UNINSTALL for downstream steps.
160+
- name: Resolve flavor (runner config)
161+
id: resolve_flavor
162+
env:
163+
FLAVOR_INPUT: ${{ inputs.flavor }}
164+
run: bash scripts/qa_android_ui_tests/execution_setup.sh resolve-flavor
165+
166+
- name: Configure AWS credentials (for S3)
167+
uses: aws-actions/configure-aws-credentials@v4
168+
with:
169+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
170+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
171+
aws-region: eu-west-1
172+
173+
# Download app APK(s) from S3 and export resolved build metadata.
174+
- name: Download APK(s) from S3
175+
id: download_apks
176+
env:
177+
APP_BUILD_NUMBER: ${{ inputs.appBuildNumber }}
178+
IS_UPGRADE: ${{ inputs.isUpgrade }}
179+
OLD_BUILD_NUMBER: ${{ inputs.oldBuildNumber }}
180+
run: bash scripts/qa_android_ui_tests/execution_setup.sh download-apks
181+
182+
# Select device(s): use input device when provided, otherwise auto-pick.
183+
- name: Detect target device(s)
184+
env:
185+
TARGET_DEVICE_ID: ${{ inputs.androidDeviceId }}
186+
RESOLVED_TESTCASE_ID: ${{ needs.validate-and-resolve-inputs.outputs.resolvedTestCaseId }}
187+
run: bash scripts/qa_android_ui_tests/execution_setup.sh detect-target-devices
188+
189+
# Install app/test prerequisites on each selected device.
190+
- name: Install APK(s) on device(s)
191+
env:
192+
ENFORCE_APP_INSTALL: ${{ inputs.enforceAppInstall }}
193+
IS_UPGRADE: ${{ inputs.isUpgrade }}
194+
run: bash scripts/qa_android_ui_tests/execution_setup.sh install-apks-on-devices
195+
196+
- name: Install 1Password CLI
197+
uses: 1password/install-cli-action@v2
198+
199+
# Fetch runtime secrets only for this run.
200+
- name: Fetch secrets.json (runtime only)
201+
env:
202+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
203+
run: bash scripts/qa_android_ui_tests/execution_setup.sh fetch-runtime-secrets
204+
205+
# Build androidTest APK once and reuse it across selected devices.
206+
- name: Build test APK (assemble once)
207+
run: bash scripts/qa_android_ui_tests/execution_setup.sh build-test-apk
208+
209+
# Resolve the built androidTest APK path for execution steps.
210+
- name: Resolve test APK path
211+
run: bash scripts/qa_android_ui_tests/execution_setup.sh resolve-test-apk-path
212+
213+
# Resolve AndroidX Test Services APKs required for TestStorage/Allure.
214+
- name: Resolve AndroidX Test Services APKs (for Allure TestStorage)
215+
run: bash scripts/qa_android_ui_tests/execution_setup.sh resolve-test-services-apks
216+
217+
# Run instrumentation on selected devices and stream per-device logs.
218+
- name: Run UI tests (one shard per device, adb instrumentation)
219+
env:
220+
RESOLVED_TESTCASE_ID: ${{ needs.validate-and-resolve-inputs.outputs.resolvedTestCaseId }}
221+
RESOLVED_CATEGORY: ${{ needs.validate-and-resolve-inputs.outputs.resolvedCategory }}
222+
IS_UPGRADE: ${{ inputs.isUpgrade }}
223+
run: bash scripts/qa_android_ui_tests/run_ui_tests.sh
224+
225+
# Remove runtime secrets before report generation and publish steps.
226+
- name: Remove runtime secrets (before Allure/Pages)
227+
if: always()
228+
run: bash scripts/qa_android_ui_tests/reporting.sh remove-runtime-secrets
229+
230+
# Pull raw allure-results from each device even when tests fail.
231+
- name: Pull Allure results from device(s)
232+
if: always()
233+
env:
234+
OUT_DIR: ${{ runner.temp }}/allure-results
235+
run: bash scripts/qa_android_ui_tests/reporting.sh pull-allure-results
236+
237+
# Merge per-device results and attach run metadata labels.
238+
- name: Merge Allure results (add device label)
239+
if: always()
240+
env:
241+
OUT_DIR: ${{ runner.temp }}/allure-results
242+
MERGED_DIR: ${{ runner.temp }}/allure-results-merged
243+
REAL_BUILD_NUMBER: ${{ env.REAL_BUILD_NUMBER }}
244+
NEW_APK_NAME: ${{ env.NEW_APK_NAME }}
245+
INPUT_TAGS: ${{ inputs.TAGS }}
246+
run: bash scripts/qa_android_ui_tests/reporting.sh merge-allure-results
247+
248+
# Generate static Allure HTML from merged results.
249+
- name: Generate Allure HTML report
250+
if: always()
251+
env:
252+
MERGED_DIR: ${{ runner.temp }}/allure-results-merged
253+
REPORT_DIR: ${{ runner.temp }}/allure-report
254+
run: bash scripts/qa_android_ui_tests/reporting.sh generate-allure-report
255+
256+
- name: Checkout GitHub Pages branch
257+
if: always()
258+
uses: actions/checkout@v4
259+
with:
260+
ref: gh-pages
261+
path: gh-pages
262+
fetch-depth: 1
263+
persist-credentials: true
264+
265+
# Publish report snapshots and apply retention cleanup.
266+
- name: Publish Allure report to Pages branch
267+
if: always()
268+
env:
269+
REPORT_DIR: ${{ runner.temp }}/allure-report
270+
PAGES_DIR: gh-pages/docs/qa-ui-tests
271+
KEEP_DAYS: "90"
272+
INPUT_TAGS: ${{ inputs.TAGS }}
273+
APK_VERSION: ${{ env.REAL_BUILD_NUMBER }}
274+
APK_NAME: ${{ env.NEW_APK_NAME }}
275+
run: bash scripts/qa_android_ui_tests/reporting.sh publish-allure-report
276+
277+
# Clean temporary artifacts on the runner, even after failures.
278+
- name: Cleanup (remove secrets + build outputs)
279+
if: always()
280+
env:
281+
ALLURE_RESULTS_DIR: ${{ runner.temp }}/allure-results
282+
ALLURE_RESULTS_MERGED_DIR: ${{ runner.temp }}/allure-results-merged
283+
ALLURE_REPORT_DIR: ${{ runner.temp }}/allure-report
284+
run: bash scripts/qa_android_ui_tests/reporting.sh cleanup-workspace

0 commit comments

Comments
 (0)