E2e tests - #229
Conversation
The iOS-style redesign folded the old add-profile / switch / change-server flow into a single profile editor dialog, so LoginFlow now drives that: name + setup key in one submit, with the key field revealed via its row-owned switch. Enrolment success is verified positively — the profile must appear in the list — since the dialog also dismisses on paths that never registered the peer. Creating a profile does not activate it, so the flow switches to it explicitly before connecting. All navigation now goes through taps (bottom nav and settings rows) rather than NavController, keeping the app in the state a user would produce. The force-relay setup step was flaky because scrollForward() flings and a tap injected while the list is still settling is consumed as a scroll-stop. Scroll, settle, click and verify now retry as one unit. Also: EditText writes are read back (soft-keyboard layout shifts silently swallowed setText), autofilled setup-key fields are cleared first, screenshots moved to the app's external files dir (scoped storage made /sdcard/Pictures fail with EACCES), and the first-run screen is dismissed before the suite starts since it shares view ids with the profile editor. VpnTestHarness listens through StateListenerAdapter so the interface can grow without breaking the suite.
Brings in #7022, which stops NewAuth from rebuilding the config from scratch on setup-key enrolment — previously that dropped every stored field, blanking the profile's display name and regenerating the WireGuard identity. The e2e suite's positive login check depends on it.
connectAndAwait started the engine through MainActivity.switchConnection() and waited on a StateListener — a code path no user takes. Tap the Home screen's connect toggle instead and wait for the on-screen status text to read Connected, so both the trigger and the feedback are the exact UI a user sees. The suite runs on an English locale, so the literal status text is matched.
A reachability failure only reported that the peer never answered; the evidence — did the name resolve, to what address, or did the reply just not come back — was discarded. Log the complete ping output (and the resolve probe's) so the CI logcat artifact shows exactly which of those it was.
The CI artifact showed every failing ping with empty stdout — resolution errors go to stderr, which executeShellCommand does not capture — and the tunnel's DNS trail shows only search-domain expansions being queried, never the bare peer name. What is still missing is the resolver's actual error and whether the app's own resolver (guaranteed to route through the VPN, unlike the shell-uid ping) agrees. Log InetAddress.getByName() for the target on every failed attempt so the next run's artifact answers both.
Three CI runs put the evidence together: the tunnel comes up fast, but its data plane does not. The relay-backed peer resolves and answers 8-12s after Connected (attempt 1 fails, attempt 3 pings), and the exit-node profile SERVFAILs upstream DNS until the exit-node path is ready — every failure was one of these still settling when the 20s window closed, with a different test tripping first each run. The Robot suite allows ~3 minutes for the same sequence; 90s on the ping/resolve/port/egress windows keeps failures reasonably fast while clearing the observed settling time with margin. Connect stays at 20s — the UI reports Connected within seconds.
The android/gui-integration work is squash-merged into main — the only code difference left was main's relay reconnect backoff randomization (#7067), which the branch did not have.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe pull request adds Android end-to-end test infrastructure and coverage for authentication, peer connectivity, DNS, exit-node routing, and ACL behavior. It also updates Android build caching, excludes e2e tests from the debug workflow, and advances the ChangesAndroid end-to-end testing
Build and test pipeline
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant E2ETest
participant LoginFlow
participant VpnTestHarness
participant AndroidApp
participant NetworkTarget
E2ETest->>LoginFlow: create profile with setup key
LoginFlow->>AndroidApp: enroll and select profile
E2ETest->>VpnTestHarness: request VPN connection
VpnTestHarness->>AndroidApp: grant consent and connect
AndroidApp-->>VpnTestHarness: report Connected state
VpnTestHarness->>NetworkTarget: run DNS, ping, TCP, or HTTPS check
NetworkTarget-->>E2ETest: return validation result
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The CI script passes both keys as -P instrumentation arguments, so reading them from the environment never took effect.
setup-java fails to resolve the 'adopt' distribution — AdoptOpenJDK was renamed to Eclipse Temurin and the old endpoints are going away — so the build died before installing a JDK. Same JDK, current name.
The e2e tests need a setup key for a live account, held only by the private mobile-e2e repo, so every one of them failed here with "setupKey instrumentation argument is required". They are meant to run from that repo's workflow, not on every push.
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (3)
app/src/androidTest/java/io/netbird/client/e2e/VpnTestHarness.java (1)
282-298: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCapture stderr in
shellsopingfailures log diagnostics.
UiAutomation.executeShellCommand(command)returns only stdout.pingOnceandresolvelogshell()output for failures, but host-resolution errors can be emitted to stderr, leaving the artifact empty. Redirect stderr into stdout in the command, or use the stderr-returning command variant if available.♻️ Proposed change to capture stderr
String shell(String command) { try { - ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command); + ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command + " 2>&1");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/androidTest/java/io/netbird/client/e2e/VpnTestHarness.java` around lines 282 - 298, Update VpnTestHarness.shell to capture stderr along with stdout when executing commands, such as by redirecting stderr into stdout before calling uiAutomation.executeShellCommand. Preserve the existing output-reading, failure logging, and empty-string fallback behavior.app/src/androidTest/java/io/netbird/client/e2e/FailFast.java (1)
3-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the public
RunListenerbase class.
FailFastdoes not use any AndroidX Test instrumentation-specific feature, but it lives inandroidx.test.internal.runner.listener.InstrumentationRunListener, which is non-public API. Useorg.junit.runner.notification.RunListenerinstead so the listener remains compatible with standard AndroidJUnitRunnerlistenerregistration and is not tied to an internal API.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/androidTest/java/io/netbird/client/e2e/FailFast.java` around lines 3 - 17, Update FailFast to extend the public org.junit.runner.notification.RunListener instead of the internal InstrumentationRunListener, and replace the internal import accordingly. Preserve the existing failure tracking and skipIfAborted behavior..github/actions/build-android/action.yml (1)
47-47: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winMove the toolchain actions to Node 24-based majors.
This composite action still pins
actions/setup-java@v4,actions/setup-go@v5, and bothactions/cache@v4steps to Node 20. Migrate toactions/setup-java@v5,actions/setup-go@v6, andactions/cache@v5, and check any self-hosted runners are at runner version2.327.1or later.Suggested version changes
- uses: actions/setup-java@v4 + uses: actions/setup-java@v5 - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 - uses: actions/cache@v4 + uses: actions/cache@v5Also applies to:
.github/workflows/build-debug.yml, lines 14, 55, 63, and 90.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/actions/build-android/action.yml at line 47, Update the toolchain action references in the build-android composite action and the corresponding build-debug workflow to actions/setup-java@v5, actions/setup-go@v6, and every actions/cache@v5 usage. Verify any self-hosted runner configuration uses runner version 2.327.1 or newer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/actions/build-android/action.yml:
- Around line 45-46: Update the comment near the Java distribution configuration
to remove the incorrect claim that the legacy “adopt” alias fails outright. Keep
“temurin” unchanged and explain that AdoptOpenJDK moved to Eclipse Temurin and
no longer receives updates.
In `@app/src/androidTest/java/io/netbird/client/e2e/DnsResolutionTest.java`:
- Around line 25-35: Update the class Javadoc in DnsResolutionTest to describe
resolution through VpnTestHarness.resolve using the device’s ping command, not
nslookup. Replace the nslookup-specific wording and examples while preserving
the documented FQDN and search-domain resolution assertions.
In `@app/src/androidTest/java/io/netbird/client/e2e/E2eSuite.java`:
- Around line 23-41: Remove the suite-level force-relay setup from E2eSuite,
including its disableForceRelay() method and any now-unused imports. Ensure each
e2e test class retains its own shared, idempotent `@BeforeClass` setup so direct
execution disables force relay before tests run.
In `@app/src/androidTest/java/io/netbird/client/e2e/ExitNodeRouteTest.java`:
- Around line 38-39: Update ExitNodeRouteTest setup and the related lines around
the exitNodeSetupKey validation to use a test assumption that skips when the
optional key is missing, rather than assertNotNull or FailFast.testFailure.
Preserve the documented behavior so only this test is skipped and other E2eSuite
scenarios continue running.
In `@app/src/androidTest/java/io/netbird/client/e2e/FailFast.java`:
- Around line 19-31: Update the instrumentation test runner configuration to
register io.netbird.client.e2e.FailFast as the listener runner argument,
ensuring its testFailure method is invoked and skipIfAborted() can stop
subsequent tests after the first failure.
In `@app/src/androidTest/java/io/netbird/client/e2e/LoginFlow.java`:
- Around line 231-248: Update the retry path in openProfileEditor to use a
tolerant boolean-returning navigation helper instead of openProfiles, whose
tapTab/tapSettingsRow calls fail immediately. Add tryOpenProfiles (and any
needed tolerant tab/row navigation) to return false when navigation elements are
unavailable, allowing subsequent attempts; retain the final dumpScreenshot and
fail only after all three attempts fail.
In `@app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java`:
- Around line 84-90: Add a shared VpnTestHarness.disconnectAndAwait method that
turns off the connect toggle when needed and waits until text_connection_status
is no longer "Connected". Call it in tearDown before profile removal in
PeerConnectivityTest.java (84-90), DnsResolutionTest.java (80-86),
ExitNodeRouteTest.java (72-78), and PortAclTest.java (86-92), preserving the
existing visualization cleanup and profile removal.
In `@app/src/androidTest/java/io/netbird/client/e2e/SetupKeyAuthTest.java`:
- Around line 45-68: Update LoginFlow.createProfileAndLogin and its callers so
the generated profile name is published to the owning test immediately after
creation, before enrolment or profile-switching steps can fail. In
app/src/androidTest/java/io/netbird/client/e2e/SetupKeyAuthTest.java:45-68, pass
a result holder and retain the early name for tearDown; apply the same mechanism
in connectAndPing at
app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java:108-125,
app/src/androidTest/java/io/netbird/client/e2e/DnsResolutionTest.java:103-104,
app/src/androidTest/java/io/netbird/client/e2e/ExitNodeRouteTest.java:96-97, and
app/src/androidTest/java/io/netbird/client/e2e/PortAclTest.java:110-111 so each
cleanup path can remove partially created profiles.
In `@netbird`:
- Line 1: Update the netbird submodule reference to a commit that exists in the
submodule repository, or add the intended missing revision to its remote
history, so update-server.sh can fetch it and the Android AAR build succeeds.
---
Nitpick comments:
In @.github/actions/build-android/action.yml:
- Line 47: Update the toolchain action references in the build-android composite
action and the corresponding build-debug workflow to actions/setup-java@v5,
actions/setup-go@v6, and every actions/cache@v5 usage. Verify any self-hosted
runner configuration uses runner version 2.327.1 or newer.
In `@app/src/androidTest/java/io/netbird/client/e2e/FailFast.java`:
- Around line 3-17: Update FailFast to extend the public
org.junit.runner.notification.RunListener instead of the internal
InstrumentationRunListener, and replace the internal import accordingly.
Preserve the existing failure tracking and skipIfAborted behavior.
In `@app/src/androidTest/java/io/netbird/client/e2e/VpnTestHarness.java`:
- Around line 282-298: Update VpnTestHarness.shell to capture stderr along with
stdout when executing commands, such as by redirecting stderr into stdout before
calling uiAutomation.executeShellCommand. Preserve the existing output-reading,
failure logging, and empty-string fallback behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6447164e-d382-4987-b328-511d5767e45f
📒 Files selected for processing (13)
.github/actions/build-android/action.yml.github/workflows/build-debug.ymlapp/src/androidTest/java/io/netbird/client/e2e/DnsResolutionTest.javaapp/src/androidTest/java/io/netbird/client/e2e/E2eAppRule.javaapp/src/androidTest/java/io/netbird/client/e2e/E2eSuite.javaapp/src/androidTest/java/io/netbird/client/e2e/ExitNodeRouteTest.javaapp/src/androidTest/java/io/netbird/client/e2e/FailFast.javaapp/src/androidTest/java/io/netbird/client/e2e/LoginFlow.javaapp/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.javaapp/src/androidTest/java/io/netbird/client/e2e/PortAclTest.javaapp/src/androidTest/java/io/netbird/client/e2e/SetupKeyAuthTest.javaapp/src/androidTest/java/io/netbird/client/e2e/VpnTestHarness.javanetbird
| # AdoptOpenJDK became Eclipse Temurin; resolving "adopt" now fails | ||
| # outright, so the build never gets as far as installing a JDK. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate action file =="
fd -a 'action\.yml' .github/actions/build-android 2>/dev/null || true
echo "== relevant action.yml lines =="
if [ -f .github/actions/build-android/action.yml ]; then
nl -ba .github/actions/build-android/action.yml | sed -n '1,90p'
fi
echo "== check local commit/action references to setup-java and distributions if present =="
rg -n "setup-java|distribution:\s*['\"]adopt|java-version|setup-go|actions/cache" .github/workflows .github/actions 2>/dev/null || trueRepository: netbirdio/android-client
Length of output: 319
🌐 Web query:
actions/setup-java v4 distribution adopt alias deprecation removed v6
💡 Result:
In the actions/setup-java GitHub Action, support for legacy AdoptOpenJDK distributions (specifically the adopt and adopt-openj9 aliases) has been deprecated in favor of Eclipse Temurin and Semeru [1]. Key points regarding this deprecation: 1. Removal: Legacy AdoptOpenJDK distributions (referred to as adopt or adopt-openj9) have been removed as primary options [1]. Users are instructed to use temurin instead of adopt or adopt-hotspot, and semeru instead of adopt-openj9 [1]. 2. Deprecation Status: While these legacy aliases were maintained for backward compatibility, they emit warnings and are considered deprecated [1]. 3. Versioning: As of August 2026, actions/setup-java versions v1 through v4 are officially deprecated [1]. The action has moved toward newer major versions, with v5 being a stable release and v6 under development [1]. 4. Action Required: If you are currently using distribution: 'adopt' or 'adopt-hotspot', you should update your workflow files to use distribution: 'temurin' [1][2]. If you are using 'adopt-openj9', you should switch to 'semeru' [1]. Continuing to use the deprecated aliases may lead to workflow failures in future releases as they are phased out [2].
Citations:
- 1: https://github.com/actions/setup-java/blob/main/README.md
- 2: GitHub workflow: adopt distribution for actions/setup-java is deprecated Adyen/adyen-java-api-library#1722
🌐 Web query:
GitHub actions/setup-java v4 action.yml distribution include adopt adopt-openj9
💡 Result:
In the GitHub actions/setup-java v4 action, the adopt and adopt-openj9 distribution keywords were historically supported for backward compatibility with the legacy AdoptOpenJDK project [1][2]. However, the AdoptOpenJDK project was discontinued and moved to the Eclipse Foundation as Eclipse Temurin [1][2]. Because AdoptOpenJDK no longer receives security updates or maintenance, it is strongly recommended—and in many configurations, effectively deprecated—to migrate away from these legacy keywords [1][2][3]. The recommended replacements are: * Replace adopt or adopt-hotspot with temurin (Eclipse Temurin) [1][2][3]. * Replace adopt-openj9 with semeru (IBM Semeru Runtime Open Edition) [1][2][3]. While some versions of the v4 action may still technically accept the adopt and adopt-openj9 keywords due to legacy support, they are no longer recommended for use [1][2]. Furthermore, please note that all versions of actions/setup-java from v1 through v4 are now deprecated; it is advised to upgrade your workflows to actions/setup-java@v5 [4][5][3].
Citations:
- 1: https://github.com/actions/setup-java/blob/f4f1212c/README.md
- 2: https://deepwiki.com/actions/setup-java/4.2-temurin-and-adoptopenjdk
- 3: https://github.com/marketplace/actions/setup-java-jdk
- 4: https://github.com/actions/setup-java/blob/main/README.md
- 5: https://github.com/actions/setup-java
🌐 Web query:
``
💡 Result:
No web search query provided.
🌐 Web query:
``
💡 Result:
No web search query provided.
🌐 Web query:
actions/setup-java v4 distribution adopt alias deprecation removed v6
💡 Result:
In actions/setup-java, the legacy AdoptOpenJDK distributions (commonly referenced by the adopt or adopt-hotspot distribution aliases) were removed in favor of temurin and semeru [1][2][3]. While these legacy aliases were deprecated and subsequently removed, they were not associated specifically with the release of v6; rather, the action's move to v6 primarily involved an internal migration to ECMAScript Modules (ESM) to support updated dependencies [4][5]. For workflows still using adopt or adopt-hotspot, you must migrate to the following recommended distributions to continue receiving software and security updates [1][6][7]: - Use temurin instead of adopt or adopt-hotspot [1][2][3]. - Use semeru (IBM Semeru Runtime Open Edition) instead of adopt-openj9 [1][2][3]. Note that versions v1 through v4 of the action itself are deprecated, and users are encouraged to upgrade to the latest stable release (v5 or higher) [1][2][3]. Existing workflows using deprecated aliases may continue to function if they have not been fully purged, but relying on removed distributions will cause failures; therefore, updating the distribution parameter in your workflow YAML is necessary [1][7].
Citations:
- 1: https://github.com/actions/setup-java/blob/main/README.md
- 2: https://github.com/actions/setup-java
- 3: http://github.com/actions/setup-java
- 4: https://gitea.rs.h3.ink/actions/setup-java/src/commit/b73b3683e55e1d5fa07bfe89d33aebe0089f688a/README.md
- 5: Migrate to ESM and upgrade dependencies actions/setup-java#1078
- 6: https://github.com/actions/setup-java/blob/f4f1212c/docs/advanced-usage.md
- 7: https://deepwiki.com/actions/setup-java/4.2-temurin-and-adoptopenjdk
🌐 Web query:
github actions/setup-java v4 distribution adopt includes adopt-openj9
💡 Result:
Yes, in actions/setup-java version v4, the adopt distribution option historically included support for adopt-openj9 [1][2][3]. Specifically: - The setup-java action provided adopt (or adopt-hotspot) for AdoptOpenJDK Hotspot builds and adopt-openj9 for AdoptOpenJDK OpenJ9 builds [1][4][2]. - These were legacy distributions [5][6]. The AdoptOpenJDK project was discontinued and succeeded by the Eclipse Temurin project [7][3]. - Because AdoptOpenJDK is no longer maintained, it is strongly recommended to migrate away from these legacy options to continue receiving security and software updates [7][1][3]. - The recommended migration path is to use temurin instead of adopt (or adopt-hotspot), and semeru instead of adopt-openj9 (IBM Semeru Runtime Open Edition) [5][6][3]. Note: The actions/setup-java@v4 action (and earlier versions) is deprecated, and users are encouraged to upgrade to the latest stable release [5][6].
Citations:
- 1: https://github.com/actions/setup-java/blob/f4f1212c/README.md
- 2: https://gitea.rs.h3.ink/actions/setup-java/src/tag/v4.7.0/src/distributions/distribution-factory.ts
- 3: https://deepwiki.com/actions/setup-java/4.2-temurin-and-adoptopenjdk
- 4: https://deepwiki.com/actions/setup-java/4-java-distributions
- 5: https://github.com/actions/setup-java/blob/main/README.md
- 6: https://github.com/actions/setup-java
- 7: https://github.com/actions/setup-java/blob/f4f1212c/docs/advanced-usage.md
🏁 Script executed:
#!/bin/sh
set -euo pipefail
echo "== action.yml lines 35-55 =="
awk 'NR>=35 && NR<=55' .github/actions/build-android/action.yml
echo "== exact setup-java action.yml distribution include lines in repo =="
if command -v grep >/dev/null 2>&1; then
grep -RInE "setup-java|distribution:\s*[\"']?adopt|temurin|adopt-openj9" .github/actions .github/workflows 2>/dev/null || true
fiRepository: netbirdio/android-client
Length of output: 1270
Correct the adopt failure claim.
actions/setup-java@v4 still supports the legacy adopt alias; it does not fail outright. Keep temurin, but update the comment to explain that AdoptOpenJDK moved to Eclipse Temurin and no longer receives updates.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/actions/build-android/action.yml around lines 45 - 46, Update the
comment near the Java distribution configuration to remove the incorrect claim
that the legacy “adopt” alias fails outright. Keep “temurin” unchanged and
explain that AdoptOpenJDK moved to Eclipse Temurin and no longer receives
updates.
| * original runs {@code dig <name>}; here we run a real {@code nslookup} on the | ||
| * device (via the shell, like the ping tests), so it exercises the device | ||
| * resolver / VpnService DNS exactly as a user's traffic would. | ||
| * | ||
| * <p>Mirrors the two original assertions: | ||
| * <ul> | ||
| * <li>FQDN resolves: {@code nslookup | ||
| * ip-172-20-3-158.eu-central-1.compute.internal} → 172.20.3.158;</li> | ||
| * <li>search-domain (unqualified) resolves: {@code nslookup ip-172-20-3-158} | ||
| * → 172.20.3.158.</li> | ||
| * </ul> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
The javadoc describes nslookup, but the test resolves with ping.
VpnTestHarness.resolve runs ping -c 1 -W 2 <host> and parses the resolved address from the first line. Its own javadoc states that nslookup is not present on these devices. Update this class javadoc so the documented mechanism matches the implementation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/androidTest/java/io/netbird/client/e2e/DnsResolutionTest.java` around
lines 25 - 35, Update the class Javadoc in DnsResolutionTest to describe
resolution through VpnTestHarness.resolve using the device’s ping command, not
nslookup. Replace the nslookup-specific wording and examples while preserving
the documented FQDN and search-domain resolution assertions.
| @RunWith(Suite.class) | ||
| @Suite.SuiteClasses({ | ||
| SetupKeyAuthTest.class, | ||
| PeerConnectivityTest.class, | ||
| PortAclTest.class, | ||
| DnsResolutionTest.class, | ||
| ExitNodeRouteTest.class, | ||
| }) | ||
| public class E2eSuite { | ||
|
|
||
| /** | ||
| * Force relay is a global setting that defaults ON and would stop the | ||
| * relay-less peer connecting. Turn it off ONCE, before any test runs. | ||
| */ | ||
| @BeforeClass | ||
| public static void disableForceRelay() throws Exception { | ||
| UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); | ||
| LoginFlow.setForceRelay(E2eAppRule.activity(), device, false); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check how the e2e instrumentation tests are invoked and filtered.
set -euo pipefail
rg -n -C5 'connectedDebugAndroidTest|connectedAndroidTest|E2eSuite|notPackage|androidTestInstrumentationRunnerArguments|testInstrumentationRunnerArguments' --glob '*.yml' --glob '*.yaml' --glob '*.gradle' --glob '*.gradle.kts' || trueRepository: netbirdio/android-client
Length of output: 162
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files =="
git ls-files | rg '(^|/)(AndroidManifest\.xml|.*Test\.java|.*Test\.kt|.*Spec\.java|.*Spec\.kt|.*Suite.*\.java|.*Suite.*\.kt|build\.gradle(\.kts)?|gradle\.properties|.*\.yml|.*\.yaml)$' || true
echo
echo "== locate E2e-related files =="
fd -i 'E2e|BuildConfig|Manifest|Test|Suite|CI|Workflow|Gradle' . || true
echo
echo "== Instrumentation references =="
rg -n -C4 'Instrument|notPackage|AndroidJUnit4|PackageLifecycle|android.test|androidTest|E2eSuite|PeerConnectivityTest|SetupKeyAuthTest|PortAclTest|DnsResolutionTest|ExitNodeRouteTest|forceRelay|ForceRelay' . || trueRepository: netbirdio/android-client
Length of output: 50380
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== AndroidManifest =="
cat -n app/src/main/AndroidManifest.xml
echo
echo "== app/build.gradle.kts relevant lines =="
sed -n '1,120p' app/build.gradle.kts | cat -n
echo
echo "== workflows/actions =="
for f in .github/workflows/*.yml .github/workflows/*.yaml .github/actions/*/action.yml; do
echo "--- $f"
sed -n '1,220p' "$f" | cat -n
done
echo
echo "== androidTest README =="
cat -n app/src/androidTest/README.md
echo
echo "== PeerConnectivityTest outline/snippet =="
wc -l app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java
sed -n '1,180p' app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java | cat -n
echo
echo "== shared e2e classes annotations =="
for f in app/src/androidTest/java/io/netbird/client/e2e/*.java; do
echo "--- $f"
sed -n '1,55p' "$f" | cat -n
done
echo
echo "== AndroidJUnitFilter references in manifest/build/workflows =="
rg -n 'androidJUnitFilter|filter|runOptions|tools:filter|packageLifecycle|PackageLifecycle|class=|io\.netbird\.client\.e2e' app/src/main/AndroidManifest.xml app/build.gradle.kts .github || trueRepository: netbirdio/android-client
Length of output: 17324
Remove the suite dependency from force-relay setup.
E2eSuite is no longer the CI entry point; public connectedDebugAndroidTest excludes io.netbird.client.e2e. The e2e classes still carry their own local @BeforeClass disableForceRelay() setup, so force relay stays enabled for relay-less peers when these tests are run directly unless E2eSuite is passed. Keep only one shared, idempotent setup in each e2e test class.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/androidTest/java/io/netbird/client/e2e/E2eSuite.java` around lines 23
- 41, Remove the suite-level force-relay setup from E2eSuite, including its
disableForceRelay() method and any now-unused imports. Ensure each e2e test
class retains its own shared, idempotent `@BeforeClass` setup so direct execution
disables force relay before tests run.
| * <p>If {@code exitNodeSetupKey} is not provided the test fails fast on its own | ||
| * assertion, so the rest of the suite is unaffected. |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
A missing exitNodeSetupKey aborts the rest of the suite, contrary to the javadoc.
The javadoc on Line 38 states the rest of the suite is unaffected. assertNotNull produces a test failure, FailFast.testFailure sets aborted, and every other scenario's @Before then throws AssumptionViolatedException. Today the damage is bounded because this class is last in E2eSuite, but any reordering makes one optional argument skip the whole suite.
If the argument is optional, skip the test instead of failing it.
🐛 Proposed fix using an assumption
+import static org.junit.Assume.assumeTrue;
@@
- assertNotNull("exitNodeSetupKey instrumentation argument is required", setupKey);
- assertTrue("exitNodeSetupKey must not be blank", !setupKey.trim().isEmpty());
+ assumeTrue("exitNodeSetupKey instrumentation argument is not set — skipping",
+ setupKey != null && !setupKey.trim().isEmpty());Also applies to: 82-87
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/androidTest/java/io/netbird/client/e2e/ExitNodeRouteTest.java` around
lines 38 - 39, Update ExitNodeRouteTest setup and the related lines around the
exitNodeSetupKey validation to use a test assumption that skips when the
optional key is missing, rather than assertNotNull or FailFast.testFailure.
Preserve the documented behavior so only this test is skipped and other E2eSuite
scenarios continue running.
| private static volatile boolean aborted = false; | ||
|
|
||
| @Override | ||
| public void testFailure(Failure failure) { | ||
| aborted = true; | ||
| } | ||
|
|
||
| /** Skip (not fail) the current test if an earlier test already failed. */ | ||
| static void skipIfAborted() { | ||
| if (aborted) { | ||
| throw new AssumptionViolatedException("Skipped: a previous test already failed (fail-fast)"); | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Find the runner `listener` argument registration for FailFast.
set -euo pipefail
# Search Gradle, manifests and CI workflows for the listener argument.
rg -n -C3 'testInstrumentationRunnerArgument|listener' --glob '*.gradle' --glob '*.gradle.kts' --glob '*.yml' --glob '*.yaml' --glob 'AndroidManifest.xml' || true
# Confirm any direct reference to the FailFast class outside its own source file.
rg -n 'io\.netbird\.client\.e2e\.FailFast' || trueRepository: netbirdio/android-client
Length of output: 162
🏁 Script executed:
#!/bin/bash
# Description: Inspect the FailFast source and any nearby test runner annotations without modifying or running repository code.
set -euo pipefail
echo "== Locate FailFast =="
fd -a 'FailFast' . | sed 's#^\./##'
echo
echo "== FailFast contents =="
fd 'FailFast' . -x sh -c 'echo "--- $1"; cat -n "$1"' sh {}
echo
echo "== Instrumentation e2e runner annotations in source =="
rg -n -C4 '`@AndroidJUnitRunner`|androidTest|androidTestInstrumentationRunner|AndroidManifest|package io\.netbird\.client\.e2e|class .*Test' . --glob '*.java' --glob '*.xml' || trueRepository: netbirdio/android-client
Length of output: 20749
🏁 Script executed:
#!/bin/bash
# Description: Inspect all tracked Gradle/workflow/manifest files for FailFast or instrumentation listener arguments.
set -euo pipefail
echo "== Track and inspect relevant files =="
git ls-files | rg '(^|/)(build\.gradle(\.kts)?|gradle\.properties|AndroidManifest\.xml|.*\.yml|.*\.yaml)$' || true
echo
echo "== FailFast/listener/testInstrumentationRunnerArgument matches in tracked files =="
python3 - <<'PY'
from pathlib import Path
import subprocess
files=subprocess.check_output(['git','ls-files'],text=True).splitlines()
target=Path('app/src/androidTest/java/io/netbird/client/e2e/FailFast.java')
if target.exists():
lines=target.read_text().splitlines()
print(f'FAILFAST_LINES={len(lines)}')
for i,l in enumerate(lines,1):
if 'skipIfAborted' in l or '`@Before`' in l:
print(f'FAILFAST_{i:04d}:{l}')
matches=['io.netbird.client.e2e.FailFast','listener','testInstrumentationRunnerArgument']
for f in files:
if Path(f).is_file() and f.endswith(('.gradle','.gradle.kts','.properties','.xml','.yml','.yaml','.md')):
try:
txt=Path(f).read_text(errors='ignore')
except Exception:
continue
if any(s in txt for s in matches):
print(f'--- {f}')
for j,line in enumerate(txt.splitlines(),1):
if any(s in line for s in matches):
print(f'{j}: {line}')
print('Done')
PYRepository: netbirdio/android-client
Length of output: 1299
Register FailFast with the test runner. skipIfAborted() depends on testFailure setting aborted, but only a listener runner argument can invoke FailFast.testFailure. If this argument is missing or does not name io.netbird.client.e2e.FailFast, fail-fast becomes silent and every instrumentation test continues after the first failure.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/androidTest/java/io/netbird/client/e2e/FailFast.java` around lines 19
- 31, Update the instrumentation test runner configuration to register
io.netbird.client.e2e.FailFast as the listener runner argument, ensuring its
testFailure method is invoked and skipIfAborted() can stop subsequent tests
after the first failure.
| for (int attempt = 1; attempt <= 3; attempt++) { | ||
| Log.i(TAG, "open profile editor attempt " + attempt); | ||
| openProfiles(device); | ||
|
|
||
| UiObject2 addBtn = device.wait( | ||
| Until.findObject(By.res(PACKAGE, "btn_add_profile")), UI_TIMEOUT_MS); | ||
| if (addBtn == null) { | ||
| continue; | ||
| } | ||
| addBtn.click(); | ||
|
|
||
| if (device.wait(Until.findObject(By.res(PACKAGE, "edit_text_profile_name")), | ||
| UI_TIMEOUT_MS) != null) { | ||
| return; | ||
| } | ||
| } | ||
| dumpScreenshot(device, "profile-editor-not-open"); | ||
| fail("profile editor (edit_text_profile_name) did not open after 3 attempts"); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
The retry loop cannot retry: openProfiles fails the test on the first attempt.
openProfiles calls tapTab and tapSettingsRow, and both call fail() when the expected view does not appear. fail() throws an AssertionError, so attempt 1 aborts openProfileEditor before the loop can try again. The comment on Line 228 states the loop exists to absorb the asynchronous teardown of the first-install fragment, but that transient state is exactly what triggers the inner fail().
Add tolerant, boolean-returning navigation helpers for the retry path, and keep the hard fail() only after the last attempt.
♻️ Sketch of a tolerant navigation path
for (int attempt = 1; attempt <= 3; attempt++) {
Log.i(TAG, "open profile editor attempt " + attempt);
- openProfiles(device);
+ if (!tryOpenProfiles(device)) {
+ continue;
+ }/** Navigate Settings → Profiles without failing the test. */
private static boolean tryOpenProfiles(UiDevice device) throws InterruptedException {
UiObject2 tab = device.wait(Until.findObject(By.res(PACKAGE, "nav_settings")), UI_TIMEOUT_MS);
if (tab == null) {
return false;
}
tab.click();
scrollToRes(device, "row_profiles");
UiObject2 row = device.wait(Until.findObject(By.res(PACKAGE, "row_profiles")), UI_TIMEOUT_MS);
if (row == null) {
return false;
}
row.click();
boolean shown = device.wait(Until.findObject(By.res(PACKAGE, "btn_add_profile")), UI_TIMEOUT_MS) != null;
device.waitForIdle();
return shown;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (int attempt = 1; attempt <= 3; attempt++) { | |
| Log.i(TAG, "open profile editor attempt " + attempt); | |
| openProfiles(device); | |
| UiObject2 addBtn = device.wait( | |
| Until.findObject(By.res(PACKAGE, "btn_add_profile")), UI_TIMEOUT_MS); | |
| if (addBtn == null) { | |
| continue; | |
| } | |
| addBtn.click(); | |
| if (device.wait(Until.findObject(By.res(PACKAGE, "edit_text_profile_name")), | |
| UI_TIMEOUT_MS) != null) { | |
| return; | |
| } | |
| } | |
| dumpScreenshot(device, "profile-editor-not-open"); | |
| fail("profile editor (edit_text_profile_name) did not open after 3 attempts"); | |
| for (int attempt = 1; attempt <= 3; attempt++) { | |
| Log.i(TAG, "open profile editor attempt " + attempt); | |
| if (!tryOpenProfiles(device)) { | |
| continue; | |
| } | |
| UiObject2 addBtn = device.wait( | |
| Until.findObject(By.res(PACKAGE, "btn_add_profile")), UI_TIMEOUT_MS); | |
| if (addBtn == null) { | |
| continue; | |
| } | |
| addBtn.click(); | |
| if (device.wait(Until.findObject(By.res(PACKAGE, "edit_text_profile_name")), | |
| UI_TIMEOUT_MS) != null) { | |
| return; | |
| } | |
| } | |
| dumpScreenshot(device, "profile-editor-not-open"); | |
| fail("profile editor (edit_text_profile_name) did not open after 3 attempts"); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/androidTest/java/io/netbird/client/e2e/LoginFlow.java` around lines
231 - 248, Update the retry path in openProfileEditor to use a tolerant
boolean-returning navigation helper instead of openProfiles, whose
tapTab/tapSettingsRow calls fail immediately. Add tryOpenProfiles (and any
needed tolerant tab/row navigation) to return false when navigation elements are
unavailable, allowing subsequent attempts; retain the final dumpScreenshot and
fail only after all three attempts fail.
| @After | ||
| public void tearDown() throws Exception { | ||
| if (profileName != null && harness != null) { | ||
| harness.disableTouchVisualization(); | ||
| LoginFlow.removeProfile(E2eAppRule.activity(), harness.device(), profileName); | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Teardown never disconnects the VPN, so a later test can pass on the previous session's Connected state. VpnTestHarness.connectAndAwait skips the toggle tap when btn_connect already reports checked, and then matches text_connection_status against "Connected". Each teardown removes the profile and disables touch visualization only, so the tunnel state carries into the next class.
app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java#L84-L90: turn the connect toggle off beforeLoginFlow.removeProfile, and wait for the status to leave "Connected".app/src/androidTest/java/io/netbird/client/e2e/DnsResolutionTest.java#L80-L86: apply the same disconnect step intearDown.app/src/androidTest/java/io/netbird/client/e2e/ExitNodeRouteTest.java#L72-L78: apply the same disconnect step intearDown; the exit-node route must not stay active for later classes.app/src/androidTest/java/io/netbird/client/e2e/PortAclTest.java#L86-L92: apply the same disconnect step intearDown.
Add the shared step as a disconnectAndAwait method on VpnTestHarness so all four teardowns call one implementation.
📍 Affects 4 files
app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java#L84-L90(this comment)app/src/androidTest/java/io/netbird/client/e2e/DnsResolutionTest.java#L80-L86app/src/androidTest/java/io/netbird/client/e2e/ExitNodeRouteTest.java#L72-L78app/src/androidTest/java/io/netbird/client/e2e/PortAclTest.java#L86-L92
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java`
around lines 84 - 90, Add a shared VpnTestHarness.disconnectAndAwait method that
turns off the connect toggle when needed and waits until text_connection_status
is no longer "Connected". Call it in tearDown before profile removal in
PeerConnectivityTest.java (84-90), DnsResolutionTest.java (80-86),
ExitNodeRouteTest.java (72-78), and PortAclTest.java (86-92), preserving the
existing visualization cleanup and profile removal.
| @Test | ||
| public void loginWithSetupKeyViaUi() throws Exception { | ||
| Bundle args = InstrumentationRegistry.getArguments(); | ||
| String setupKey = args.getString("setupKey"); | ||
|
|
||
| assertNotNull("setupKey instrumentation argument is required", setupKey); | ||
| assertTrue("setupKey must not be blank", !setupKey.trim().isEmpty()); | ||
|
|
||
| device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); | ||
| profileName = LoginFlow.createProfileAndLogin( | ||
| E2eAppRule.activity(), device, "login", setupKey); | ||
| } | ||
|
|
||
| /** | ||
| * Enrolling now creates a profile, so this test leaves one behind — remove | ||
| * it like the connectivity tests do, to keep the account's peer list and the | ||
| * device's profile list from growing with every run. | ||
| */ | ||
| @After | ||
| public void tearDown() throws Exception { | ||
| if (profileName != null && device != null) { | ||
| LoginFlow.removeProfile(E2eAppRule.activity(), device, profileName); | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
A failure inside LoginFlow.createProfileAndLogin leaks the created profile in every scenario. The helper creates the profile in the UI, then verifies enrolment and switches profiles. It returns the generated name only on success, so each caller leaves profileName null after a mid-flow failure and its tearDown skips LoginFlow.removeProfile.
app/src/androidTest/java/io/netbird/client/e2e/SetupKeyAuthTest.java#L45-L68: publish the generated profile name to the test before the risky steps run, for example by passing a result holder intoLoginFlow.createProfileAndLogin.app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java#L108-L125: consume the same early-publish mechanism inconnectAndPing.app/src/androidTest/java/io/netbird/client/e2e/DnsResolutionTest.java#L103-L104: consume the same early-publish mechanism.app/src/androidTest/java/io/netbird/client/e2e/ExitNodeRouteTest.java#L96-L97: consume the same early-publish mechanism.app/src/androidTest/java/io/netbird/client/e2e/PortAclTest.java#L110-L111: consume the same early-publish mechanism.
📍 Affects 5 files
app/src/androidTest/java/io/netbird/client/e2e/SetupKeyAuthTest.java#L45-L68(this comment)app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java#L108-L125app/src/androidTest/java/io/netbird/client/e2e/DnsResolutionTest.java#L103-L104app/src/androidTest/java/io/netbird/client/e2e/ExitNodeRouteTest.java#L96-L97app/src/androidTest/java/io/netbird/client/e2e/PortAclTest.java#L110-L111
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/androidTest/java/io/netbird/client/e2e/SetupKeyAuthTest.java` around
lines 45 - 68, Update LoginFlow.createProfileAndLogin and its callers so the
generated profile name is published to the owning test immediately after
creation, before enrolment or profile-switching steps can fail. In
app/src/androidTest/java/io/netbird/client/e2e/SetupKeyAuthTest.java:45-68, pass
a result holder and retain the early name for tearDown; apply the same mechanism
in connectAndPing at
app/src/androidTest/java/io/netbird/client/e2e/PeerConnectivityTest.java:108-125,
app/src/androidTest/java/io/netbird/client/e2e/DnsResolutionTest.java:103-104,
app/src/androidTest/java/io/netbird/client/e2e/ExitNodeRouteTest.java:96-97, and
app/src/androidTest/java/io/netbird/client/e2e/PortAclTest.java:110-111 so each
cleanup path can remove partially created profiles.
| @@ -1 +1 @@ | |||
| Subproject commit 426331552740c69f40a820314ca2833a4cd20ed9 | |||
| Subproject commit 19a6cedfffd85c938fb1272170e7dd93dcd9c7ab | |||
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
new_commit=19a6cedfffd85c938fb1272170e7dd93dcd9c7ab
old_commit=426331552740c69f40a820314ca2833a4cd20ed9
git -C netbird cat-file -e "${new_commit}^{commit}"
git -C netbird cat-file -e "${old_commit}^{commit}"
git -C netbird ls-tree -d --name-only "$new_commit" client/android | grep -Fxq client/android
test "$(git -C netbird rev-parse HEAD)" = "$new_commit"
rg -n -C 3 'gomobile bind|client/android' build-android-lib.sh
git -C netbird diff --stat "$old_commit" "$new_commit" -- client/androidRepository: netbirdio/android-client
Length of output: 338
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== submodule config =="
git config --file .gitmodules --list 2>/dev/null | grep -E '\.url|netbird|submodule' || true
echo "== netbird files =="
git ls-files netbird netbird/.gitmodules netbird/.git 2>/dev/null | sed -n '1,80p' || true
echo "== netbird directory =="
if [ -d netbird ]; then
ls -la netbird | sed -n '1,40p'
if [ -f netbird/.git ]; then cat netbird/.git; fi
if [ -f netbird/.gitmodules ]; then cat netbird/.gitmodules; fi
else
echo "netbird not as directory"
fi
echo "== gitlink candidates =="
git ls-files -s . | awk '$4 == "160000" {print}' || true
echo "== changed files in HEAD =="
git diff --name-only HEAD | sed -n '1,120p' || true
echo "== references for netbird commits =="
git rev-parse --verify HEAD 2>/dev/null || true
for ref in netbird HEAD; do
echo "--- $ref"
git rev-parse --verify --short=12 "${ref}^{commit}" 2>/dev/null || true
doneRepository: netbirdio/android-client
Length of output: 607
🌐 Web query:
netbirdio android-client commit 19a6cedfffd85c938fb1272170e7dd93dcd9c7ab client/android
💡 Result:
The commit hash 19a6cedfffd85c938fb1272170e7dd93dcd9c7ab does not appear in the public commit history of the netbirdio/android-client repository as of August 10, 2026 [1][2][3][4]. The repository makes extensive use of the netbird submodule [5][6][7], which is managed as a git submodule within the android-client project [1][6][7]. It is possible that this specific commit belongs to the main netbird repository rather than the android-client wrapper, or it may refer to a local or private branch not indexed in public history [1][8][9]. To verify a specific commit, you can navigate to the repository on GitHub and view the commit history directly [1].
Citations:
- 1: https://github.com/netbirdio/android-client
- 2: af289eb
- 3: cee5fa9
- 4: 9b0f0fa
- 5: Fix Android internet blackhole caused by stale route re-injection on TUN rebuild #160
- 6: v0.3.2...v0.4.0
- 7: 96e29f3
- 8: https://github.com/netbirdio/netbird?tab=readme-ov-file
- 9: https://github.com/netbirdio/netbird/blob/c4b2da4c/README.md
Resolve the missing submodule revision before merging.
Commit 19a6cedfffd85c938fb1272170e7dd93dcd9c7ab is not present in the netbird submodule reference namespace, so update-server.sh cannot fetch the submodule and the Android AAR build will fail. Pin a valid netbird commit or update the repository with the intended revision.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@netbird` at line 1, Update the netbird submodule reference to a commit that
exists in the submodule repository, or add the intended missing revision to its
remote history, so update-server.sh can fetch it and the Android AAR build
succeeds.
…/e2e-tests-new-design # Conflicts: # netbird
Summary by CodeRabbit
Tests
Chores