Skip to content

Commit 67c340d

Browse files
committed
ci(workflows,scripts): shard the Android E2E run across emulators
1 parent 5b8af35 commit 67c340d

2 files changed

Lines changed: 73 additions & 31 deletions

File tree

.github/workflows/e2e.yml

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,29 @@ jobs:
2525
e2e-android:
2626
needs: coverage
2727
runs-on: ubuntu-latest
28-
timeout-minutes: 60
28+
timeout-minutes: 30
29+
# GitHub-hosted Android emulators grow unstable after ~15 minutes of
30+
# sustained Maestro driving and start reporting "device offline" /
31+
# "device not found" mid-run (the emulator VM, not the app, dies).
32+
# The full 60-flow suite reliably crosses that threshold around the
33+
# ~35th flow. Sharding the run into a few balanced groups — each on
34+
# its own freshly booted emulator — keeps every session well under
35+
# the limit, and parallelizes the Android pass as a bonus. Groups are
36+
# sized so none exceeds ~25 flows; ``components`` is the largest
37+
# single category and runs alone. ``fail-fast: false`` so one shard's
38+
# failure still lets the others report.
39+
strategy:
40+
fail-fast: false
41+
matrix:
42+
include:
43+
- name: components
44+
suites: components
45+
- name: hooks-nav
46+
suites: hooks navigation
47+
- name: layout-styling-anim-misc
48+
suites: layout styling animations misc
49+
50+
name: e2e-android (${{ matrix.name }})
2951

3052
steps:
3153
- name: Checkout
@@ -61,7 +83,7 @@ jobs:
6183
with:
6284
api-level: 31
6385
arch: x86_64
64-
script: bash -lc "./scripts/run-e2e.sh android"
86+
script: bash -lc "./scripts/run-e2e.sh android ${{ matrix.suites }}"
6587

6688
e2e-ios:
6789
needs: coverage

scripts/run-e2e.sh

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,26 @@
55
# full E2E pass locally. It mirrors what CI does in .github/workflows/e2e.yml.
66
#
77
# Usage:
8-
# ./scripts/run-e2e.sh android [suite]
9-
# ./scripts/run-e2e.sh ios [suite]
8+
# ./scripts/run-e2e.sh android [suite ...]
9+
# ./scripts/run-e2e.sh ios [suite ...]
1010
#
1111
# Examples:
12-
# ./scripts/run-e2e.sh android # full suite
13-
# ./scripts/run-e2e.sh android components # only the components suite
14-
# ./scripts/run-e2e.sh ios hooks # only the hooks suite on iOS
12+
# ./scripts/run-e2e.sh android # full suite
13+
# ./scripts/run-e2e.sh android components # only the components suite
14+
# ./scripts/run-e2e.sh ios hooks # only the hooks suite on iOS
15+
# ./scripts/run-e2e.sh android hooks navigation # two categories, one session
1516
#
1617
# Available suites: full, components, hooks, navigation, layout, styling,
1718
# animations, misc.
1819
#
20+
# Multiple category suites can be passed at once; they run sequentially in
21+
# a single Maestro session (and against a single emulator/simulator boot).
22+
# CI uses this to shard the Android run into a few balanced groups so no
23+
# single emulator session has to survive the entire 60-flow marathon —
24+
# GitHub-hosted Android emulators grow unstable under ~15 minutes of
25+
# sustained Maestro driving and start reporting "device offline". See
26+
# .github/workflows/e2e.yml and tests/e2e/AGENTS.md.
27+
#
1928
# Prerequisites:
2029
# - `pn` CLI available (e.g. via `pip install -e .`).
2130
# - `maestro` CLI on PATH (https://maestro.dev/).
@@ -24,9 +33,9 @@
2433
#
2534
# The script:
2635
# 1. Builds + installs the e2e-suite app via `pn run <platform> --no-logs`.
27-
# 2. Picks the right Maestro YAML based on platform + suite.
28-
# 3. Runs `maestro test` up to ``MAESTRO_MAX_ATTEMPTS`` times (default
29-
# 2) and exits with the last attempt's exit code.
36+
# 2. Resolves each requested suite to its Maestro YAML target.
37+
# 3. Runs `maestro test` (over all targets) up to ``MAESTRO_MAX_ATTEMPTS``
38+
# times (default 2) and exits with the last attempt's exit code.
3039
#
3140
# A successful run prints "All E2E suites passed." at the end and exits 0.
3241
# Any failed flow is reported by Maestro in its standard format; see
@@ -39,7 +48,12 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
3948
cd "$ROOT_DIR"
4049

4150
PLATFORM="${1:-android}"
42-
SUITE="${2:-full}"
51+
shift || true
52+
# Remaining args are suite names; default to the full aggregate suite.
53+
SUITES=("$@")
54+
if [[ ${#SUITES[@]} -eq 0 ]]; then
55+
SUITES=("full")
56+
fi
4357

4458
case "$PLATFORM" in
4559
android|ios) ;;
@@ -65,23 +79,29 @@ case "$PLATFORM" in
6579
ios) APP_ID="com.pythonnative.ios-template" ;;
6680
esac
6781

68-
case "$SUITE" in
69-
full)
70-
if [[ "$PLATFORM" == "android" ]]; then
71-
MAESTRO_TARGET="tests/e2e/android.yaml"
72-
else
73-
MAESTRO_TARGET="tests/e2e/ios.yaml"
74-
fi
75-
;;
76-
components|hooks|navigation|layout|styling|animations|misc)
77-
MAESTRO_TARGET="tests/e2e/suites/${SUITE}.yaml"
78-
;;
79-
*)
80-
echo "Error: unknown suite '$SUITE'" >&2
81-
echo "Available suites: full, components, hooks, navigation, layout, styling, animations, misc" >&2
82-
exit 2
83-
;;
84-
esac
82+
# Resolve each requested suite to a Maestro YAML target. ``full`` expands
83+
# to the platform's master aggregate; the category names map to the
84+
# per-category suite files under tests/e2e/suites/.
85+
MAESTRO_TARGETS=()
86+
for suite in "${SUITES[@]}"; do
87+
case "$suite" in
88+
full)
89+
if [[ "$PLATFORM" == "android" ]]; then
90+
MAESTRO_TARGETS+=("tests/e2e/android.yaml")
91+
else
92+
MAESTRO_TARGETS+=("tests/e2e/ios.yaml")
93+
fi
94+
;;
95+
components|hooks|navigation|layout|styling|animations|misc)
96+
MAESTRO_TARGETS+=("tests/e2e/suites/${suite}.yaml")
97+
;;
98+
*)
99+
echo "Error: unknown suite '$suite'" >&2
100+
echo "Available suites: full, components, hooks, navigation, layout, styling, animations, misc" >&2
101+
exit 2
102+
;;
103+
esac
104+
done
85105

86106
printf "\n==> Building e2e-suite app for %s\n" "$PLATFORM"
87107
pushd examples/e2e-suite > /dev/null
@@ -90,13 +110,13 @@ popd > /dev/null
90110

91111
run_maestro() {
92112
if [[ "$PLATFORM" == "ios" ]]; then
93-
maestro --platform ios test -e "APP_ID=$APP_ID" "$MAESTRO_TARGET"
113+
maestro --platform ios test -e "APP_ID=$APP_ID" "${MAESTRO_TARGETS[@]}"
94114
else
95-
maestro test -e "APP_ID=$APP_ID" "$MAESTRO_TARGET"
115+
maestro test -e "APP_ID=$APP_ID" "${MAESTRO_TARGETS[@]}"
96116
fi
97117
}
98118

99-
printf "\n==> Running Maestro suite: %s\n" "$MAESTRO_TARGET"
119+
printf "\n==> Running Maestro suite(s): %s\n" "${MAESTRO_TARGETS[*]}"
100120

101121
# Maestro's iOS XCUITest driver occasionally loses its connection to the
102122
# app during long suites and surfaces transient "Application is not

0 commit comments

Comments
 (0)