fix(android): verify public WebView smoke

This commit is contained in:
SimpleTest 2026-07-24 02:00:08 +03:00
parent ef9ac148e5
commit 17478f6303
5 changed files with 80 additions and 23 deletions

View File

@ -131,6 +131,7 @@ android {
"FIREBASE_GCM_SENDER_ID",
quotedBuildConfig(firebaseSenderId.get())
)
buildConfigField("boolean", "SAFE_PAGE_LOAD_DIAGNOSTICS", "false")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@ -178,6 +179,7 @@ android {
"BASE_URL",
"\"${releaseBaseUrl.get().replace("\\", "\\\\").replace("\"", "\\\"")}\""
)
buildConfigField("boolean", "SAFE_PAGE_LOAD_DIAGNOSTICS", "true")
manifestPlaceholders["usesCleartextTraffic"] = "false"
manifestPlaceholders["deepLinkHost"] =
releaseManifestOrigin?.host ?: "invalid.whoneedhelp.local"
@ -197,6 +199,7 @@ android {
"BASE_URL",
"\"${releaseBaseUrl.get().replace("\\", "\\\\").replace("\"", "\\\"")}\""
)
buildConfigField("boolean", "SAFE_PAGE_LOAD_DIAGNOSTICS", "true")
manifestPlaceholders["usesCleartextTraffic"] = "false"
manifestPlaceholders["deepLinkHost"] =
releaseManifestOrigin?.host ?: "invalid.whoneedhelp.local"

View File

@ -4,6 +4,7 @@ import static androidx.test.espresso.web.assertion.WebViewAssertions.webMatches;
import static androidx.test.espresso.web.sugar.Web.onWebView;
import static androidx.test.espresso.web.webdriver.DriverAtoms.findElement;
import static androidx.test.espresso.web.webdriver.DriverAtoms.getText;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.Matchers.containsString;
import android.content.Context;
@ -28,10 +29,15 @@ public final class PublicStagingInstrumentedTest {
@Test
public void publicHomeAndSafetyDeepLinkRenderExpectedDom() throws Exception {
assertTrue(
"Public non-production smoke builds must expose redacted page-load diagnostics",
BuildConfig.SAFE_PAGE_LOAD_DIAGNOSTICS
);
try (ActivityScenario<MainActivity> scenario = launch("/")) {
waitForElementText(
"main-content",
"Help can be closer than you think."
"Need help nearby? Ask the community."
);
}

View File

@ -61,6 +61,8 @@ import org.json.JSONObject;
public final class MainActivity extends ComponentActivity {
private static final String LOG_TAG = "WhoNeedHelpWebView";
private static final boolean PAGE_LOAD_DIAGNOSTICS =
BuildConfig.DEBUG || BuildConfig.SAFE_PAGE_LOAD_DIAGNOSTICS;
private WebView webView;
private TrustedOrigin trustedOrigin;
private GeolocationPermissions.Callback pendingLocationCallback;
@ -201,13 +203,26 @@ public final class MainActivity extends ComponentActivity {
}
private static String safeLogPath(Uri uri) {
if (uri == null) {
return "/";
}
String path = uri.getPath();
if (path != null && path.startsWith("/users/log-in/")) {
return "/users/log-in/[redacted]";
}
return path;
return path == null || path.isEmpty() ? "/" : path;
}
private static void logMainFrameFailure(String detail, Uri uri) {
if (PAGE_LOAD_DIAGNOSTICS) {
Log.e(
LOG_TAG,
"Main-frame load failed: " + detail + " path=" + safeLogPath(uri)
);
}
}
@Override
@ -886,6 +901,10 @@ public final class MainActivity extends ComponentActivity {
SslError error
) {
mainFrameLoadFailed = true;
logMainFrameFailure(
"ssl_error=" + error.getPrimaryError(),
Uri.parse(error.getUrl() == null ? "" : error.getUrl())
);
handler.cancel();
showPageLoadError(R.string.secure_connection_failed);
}
@ -898,17 +917,7 @@ public final class MainActivity extends ComponentActivity {
) {
if (request.isForMainFrame()) {
mainFrameLoadFailed = true;
if (BuildConfig.DEBUG) {
Log.e(
LOG_TAG,
"Main-frame load failed: code="
+ error.getErrorCode()
+ " path="
+ safeLogPath(request.getUrl())
);
}
logMainFrameFailure("code=" + error.getErrorCode(), request.getUrl());
showPageLoadError(R.string.page_load_failed);
}
}
@ -921,6 +930,10 @@ public final class MainActivity extends ComponentActivity {
) {
if (request.isForMainFrame()) {
mainFrameLoadFailed = true;
logMainFrameFailure(
"http_status=" + errorResponse.getStatusCode(),
request.getUrl()
);
showPageLoadError(R.string.page_load_failed);
}
}
@ -930,7 +943,7 @@ public final class MainActivity extends ComponentActivity {
mainFrameLoadFailed = false;
dismissPageLoadError();
if (BuildConfig.DEBUG) {
if (PAGE_LOAD_DIAGNOSTICS) {
Log.d(LOG_TAG, "Main-frame load started: path=" + safeLogPath(Uri.parse(url)));
}
@ -946,7 +959,7 @@ public final class MainActivity extends ComponentActivity {
dismissPageLoadError();
}
if (BuildConfig.DEBUG) {
if (PAGE_LOAD_DIAGNOSTICS) {
Log.d(LOG_TAG, "Main-frame load finished: path=" + safeLogPath(Uri.parse(url)));
scheduleMapDiagnostics(view, Uri.parse(url));
}

View File

@ -0,0 +1,16 @@
package org.whoneedhelp.mobile;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public final class BuildTypeConfigurationTest {
@Test
public void safePageLoadDiagnosticsAreLimitedToPublicNonProductionBuilds() {
boolean expected =
BuildConfig.APPLICATION_ID.endsWith(".development")
|| BuildConfig.APPLICATION_ID.endsWith(".staging");
assertEquals(expected, BuildConfig.SAFE_PAGE_LOAD_DIAGNOSTICS);
}
}

View File

@ -1413,6 +1413,22 @@ None of the observations below describe the current delivery path.
production pipeline separately passed release tests, lint, R8/resource
shrinking, APK/AAB signing checks, Bundletool validation, and production App
Links identity validation.
- The API 37 development emulator smoke now passes the signed
`org.whoneedhelp.mobile.development` APK against
`https://whoneedhelp.imalto.site`. It observed the home and `/safety`
main-frame loads, asserted both DOMs, rejected an unrelated HTTPS origin,
found no load/TLS error, and captured the rendered phone screenshots.
Evidence is retained at
`output/android-development-smoke/20260723225620-2568739`. Its run-scoped
container, AVD volume, and image were all absent after cleanup.
- This replay exposed and fixed two stale checks rather than treating a build as
runtime proof. Development/staging APKs deliberately have Android debugging
disabled, so their previous smoke harness waited for a debug-only page-load
message even after WebView rendered successfully. Redacted path-only load
diagnostics are now enabled only for the signed non-production public build
types, while WebView debugging and the release build remain disabled. The
public DOM assertion was also updated from the retired hero copy to the
current `Need help nearby? Ask the community.` heading.
- The complete isolated quality/security gate passed with 353 ExUnit tests,
ShellCheck, Hadolint, actionlint, Compose/Helm validation, format/compiler,
xref, Credo, Sobelow, Dialyzer, Hex/npm audits, source scanning, and all
@ -1459,14 +1475,17 @@ None of the observations below describe the current delivery path.
after that promotion; the current test origin still depends on its configured
workstation/VPN/gateway path.
- The final Android application ID is `org.whoneedhelp.mobile`. The application
publishes environment-specific `/.well-known/assetlinks.json`. The previous
staging identity was checked against its HTTPS response; repeat that online
check for the new `org.whoneedhelp.mobile.development` identity after the
controlled development rebuild. Before a Play release, register the
application, add the Play App Signing certificate fingerprint alongside any
sideload/upload fingerprint, repeat Android's domain verification on a
device, and complete store policy/release work. A dedicated upload key and
signed APK/AAB exist, but no Play application has been registered.
publishes environment-specific `/.well-known/assetlinks.json`. The online
development response now agrees with
`org.whoneedhelp.mobile.development` and its signed certificate, and the
explicit same-origin deep link rendered in the API 37 smoke. Android still
reported the fresh emulator's domain-verification state as `none`, so a
verified implicit App Link has not yet been observed on a device. Before a
Play release, register the application, add the Play App Signing certificate
fingerprint alongside any sideload/upload fingerprint, repeat Android's
domain verification on a device, and complete store policy/release work. A
dedicated upload key and signed APK/AAB exist, but no Play application has
been registered.
- Operate PostgreSQL/PostGIS with off-site backups, recovery testing, monitoring,
and the availability model selected for real usage.
- The development Brevo SMTP transport and sender have completed both an