fix(android): verify public WebView smoke
This commit is contained in:
parent
ef9ac148e5
commit
17478f6303
|
|
@ -131,6 +131,7 @@ android {
|
||||||
"FIREBASE_GCM_SENDER_ID",
|
"FIREBASE_GCM_SENDER_ID",
|
||||||
quotedBuildConfig(firebaseSenderId.get())
|
quotedBuildConfig(firebaseSenderId.get())
|
||||||
)
|
)
|
||||||
|
buildConfigField("boolean", "SAFE_PAGE_LOAD_DIAGNOSTICS", "false")
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
@ -178,6 +179,7 @@ android {
|
||||||
"BASE_URL",
|
"BASE_URL",
|
||||||
"\"${releaseBaseUrl.get().replace("\\", "\\\\").replace("\"", "\\\"")}\""
|
"\"${releaseBaseUrl.get().replace("\\", "\\\\").replace("\"", "\\\"")}\""
|
||||||
)
|
)
|
||||||
|
buildConfigField("boolean", "SAFE_PAGE_LOAD_DIAGNOSTICS", "true")
|
||||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||||
manifestPlaceholders["deepLinkHost"] =
|
manifestPlaceholders["deepLinkHost"] =
|
||||||
releaseManifestOrigin?.host ?: "invalid.whoneedhelp.local"
|
releaseManifestOrigin?.host ?: "invalid.whoneedhelp.local"
|
||||||
|
|
@ -197,6 +199,7 @@ android {
|
||||||
"BASE_URL",
|
"BASE_URL",
|
||||||
"\"${releaseBaseUrl.get().replace("\\", "\\\\").replace("\"", "\\\"")}\""
|
"\"${releaseBaseUrl.get().replace("\\", "\\\\").replace("\"", "\\\"")}\""
|
||||||
)
|
)
|
||||||
|
buildConfigField("boolean", "SAFE_PAGE_LOAD_DIAGNOSTICS", "true")
|
||||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||||
manifestPlaceholders["deepLinkHost"] =
|
manifestPlaceholders["deepLinkHost"] =
|
||||||
releaseManifestOrigin?.host ?: "invalid.whoneedhelp.local"
|
releaseManifestOrigin?.host ?: "invalid.whoneedhelp.local"
|
||||||
|
|
|
||||||
|
|
@ -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.sugar.Web.onWebView;
|
||||||
import static androidx.test.espresso.web.webdriver.DriverAtoms.findElement;
|
import static androidx.test.espresso.web.webdriver.DriverAtoms.findElement;
|
||||||
import static androidx.test.espresso.web.webdriver.DriverAtoms.getText;
|
import static androidx.test.espresso.web.webdriver.DriverAtoms.getText;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
@ -28,10 +29,15 @@ public final class PublicStagingInstrumentedTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void publicHomeAndSafetyDeepLinkRenderExpectedDom() throws Exception {
|
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("/")) {
|
try (ActivityScenario<MainActivity> scenario = launch("/")) {
|
||||||
waitForElementText(
|
waitForElementText(
|
||||||
"main-content",
|
"main-content",
|
||||||
"Help can be closer than you think."
|
"Need help nearby? Ask the community."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ import org.json.JSONObject;
|
||||||
|
|
||||||
public final class MainActivity extends ComponentActivity {
|
public final class MainActivity extends ComponentActivity {
|
||||||
private static final String LOG_TAG = "WhoNeedHelpWebView";
|
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 WebView webView;
|
||||||
private TrustedOrigin trustedOrigin;
|
private TrustedOrigin trustedOrigin;
|
||||||
private GeolocationPermissions.Callback pendingLocationCallback;
|
private GeolocationPermissions.Callback pendingLocationCallback;
|
||||||
|
|
@ -201,13 +203,26 @@ public final class MainActivity extends ComponentActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String safeLogPath(Uri uri) {
|
private static String safeLogPath(Uri uri) {
|
||||||
|
if (uri == null) {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
String path = uri.getPath();
|
String path = uri.getPath();
|
||||||
|
|
||||||
if (path != null && path.startsWith("/users/log-in/")) {
|
if (path != null && path.startsWith("/users/log-in/")) {
|
||||||
return "/users/log-in/[redacted]";
|
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
|
@Override
|
||||||
|
|
@ -886,6 +901,10 @@ public final class MainActivity extends ComponentActivity {
|
||||||
SslError error
|
SslError error
|
||||||
) {
|
) {
|
||||||
mainFrameLoadFailed = true;
|
mainFrameLoadFailed = true;
|
||||||
|
logMainFrameFailure(
|
||||||
|
"ssl_error=" + error.getPrimaryError(),
|
||||||
|
Uri.parse(error.getUrl() == null ? "" : error.getUrl())
|
||||||
|
);
|
||||||
handler.cancel();
|
handler.cancel();
|
||||||
showPageLoadError(R.string.secure_connection_failed);
|
showPageLoadError(R.string.secure_connection_failed);
|
||||||
}
|
}
|
||||||
|
|
@ -898,17 +917,7 @@ public final class MainActivity extends ComponentActivity {
|
||||||
) {
|
) {
|
||||||
if (request.isForMainFrame()) {
|
if (request.isForMainFrame()) {
|
||||||
mainFrameLoadFailed = true;
|
mainFrameLoadFailed = true;
|
||||||
|
logMainFrameFailure("code=" + error.getErrorCode(), request.getUrl());
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
Log.e(
|
|
||||||
LOG_TAG,
|
|
||||||
"Main-frame load failed: code="
|
|
||||||
+ error.getErrorCode()
|
|
||||||
+ " path="
|
|
||||||
+ safeLogPath(request.getUrl())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
showPageLoadError(R.string.page_load_failed);
|
showPageLoadError(R.string.page_load_failed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -921,6 +930,10 @@ public final class MainActivity extends ComponentActivity {
|
||||||
) {
|
) {
|
||||||
if (request.isForMainFrame()) {
|
if (request.isForMainFrame()) {
|
||||||
mainFrameLoadFailed = true;
|
mainFrameLoadFailed = true;
|
||||||
|
logMainFrameFailure(
|
||||||
|
"http_status=" + errorResponse.getStatusCode(),
|
||||||
|
request.getUrl()
|
||||||
|
);
|
||||||
showPageLoadError(R.string.page_load_failed);
|
showPageLoadError(R.string.page_load_failed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -930,7 +943,7 @@ public final class MainActivity extends ComponentActivity {
|
||||||
mainFrameLoadFailed = false;
|
mainFrameLoadFailed = false;
|
||||||
dismissPageLoadError();
|
dismissPageLoadError();
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (PAGE_LOAD_DIAGNOSTICS) {
|
||||||
Log.d(LOG_TAG, "Main-frame load started: path=" + safeLogPath(Uri.parse(url)));
|
Log.d(LOG_TAG, "Main-frame load started: path=" + safeLogPath(Uri.parse(url)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -946,7 +959,7 @@ public final class MainActivity extends ComponentActivity {
|
||||||
dismissPageLoadError();
|
dismissPageLoadError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (PAGE_LOAD_DIAGNOSTICS) {
|
||||||
Log.d(LOG_TAG, "Main-frame load finished: path=" + safeLogPath(Uri.parse(url)));
|
Log.d(LOG_TAG, "Main-frame load finished: path=" + safeLogPath(Uri.parse(url)));
|
||||||
scheduleMapDiagnostics(view, Uri.parse(url));
|
scheduleMapDiagnostics(view, Uri.parse(url));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1413,6 +1413,22 @@ None of the observations below describe the current delivery path.
|
||||||
production pipeline separately passed release tests, lint, R8/resource
|
production pipeline separately passed release tests, lint, R8/resource
|
||||||
shrinking, APK/AAB signing checks, Bundletool validation, and production App
|
shrinking, APK/AAB signing checks, Bundletool validation, and production App
|
||||||
Links identity validation.
|
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,
|
- The complete isolated quality/security gate passed with 353 ExUnit tests,
|
||||||
ShellCheck, Hadolint, actionlint, Compose/Helm validation, format/compiler,
|
ShellCheck, Hadolint, actionlint, Compose/Helm validation, format/compiler,
|
||||||
xref, Credo, Sobelow, Dialyzer, Hex/npm audits, source scanning, and all
|
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
|
after that promotion; the current test origin still depends on its configured
|
||||||
workstation/VPN/gateway path.
|
workstation/VPN/gateway path.
|
||||||
- The final Android application ID is `org.whoneedhelp.mobile`. The application
|
- The final Android application ID is `org.whoneedhelp.mobile`. The application
|
||||||
publishes environment-specific `/.well-known/assetlinks.json`. The previous
|
publishes environment-specific `/.well-known/assetlinks.json`. The online
|
||||||
staging identity was checked against its HTTPS response; repeat that online
|
development response now agrees with
|
||||||
check for the new `org.whoneedhelp.mobile.development` identity after the
|
`org.whoneedhelp.mobile.development` and its signed certificate, and the
|
||||||
controlled development rebuild. Before a Play release, register the
|
explicit same-origin deep link rendered in the API 37 smoke. Android still
|
||||||
application, add the Play App Signing certificate fingerprint alongside any
|
reported the fresh emulator's domain-verification state as `none`, so a
|
||||||
sideload/upload fingerprint, repeat Android's domain verification on a
|
verified implicit App Link has not yet been observed on a device. Before a
|
||||||
device, and complete store policy/release work. A dedicated upload key and
|
Play release, register the application, add the Play App Signing certificate
|
||||||
signed APK/AAB exist, but no Play application has been registered.
|
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,
|
- Operate PostgreSQL/PostGIS with off-site backups, recovery testing, monitoring,
|
||||||
and the availability model selected for real usage.
|
and the availability model selected for real usage.
|
||||||
- The development Brevo SMTP transport and sender have completed both an
|
- The development Brevo SMTP transport and sender have completed both an
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user