test(android): verify real FCM delivery
This commit is contained in:
parent
6c3d3ec16d
commit
a35b7cb8ba
|
|
@ -69,7 +69,7 @@ FROM android-base AS emulator
|
||||||
USER root
|
USER root
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
ARG ANDROID_EMULATOR_SYSTEM_IMAGE=system-images/android-37.0/google_apis_ps16k/x86_64
|
ARG ANDROID_EMULATOR_SYSTEM_IMAGE=system-images/android-37.1/google_apis_ps16k/x86_64
|
||||||
|
|
||||||
RUN android --no-metrics --sdk="${ANDROID_HOME}" sdk install \
|
RUN android --no-metrics --sdk="${ANDROID_HOME}" sdk install \
|
||||||
"emulator" \
|
"emulator" \
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ in either outcome.
|
||||||
|
|
||||||
## Emulator verification
|
## Emulator verification
|
||||||
|
|
||||||
The optional `emulator` target contains the API 37.0 Google APIs x86_64 system
|
The optional `emulator` target contains the API 37.1 Google APIs x86_64 system
|
||||||
image. Manual verification against the local Compose application requires KVM
|
image. Manual verification against the local Compose application requires KVM
|
||||||
and host networking. Pass the same `.env` value as a build argument, then
|
and host networking. Pass the same `.env` value as a build argument, then
|
||||||
expose the Compose proxy to Android with `adb reverse`:
|
expose the Compose proxy to Android with `adb reverse`:
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,13 @@ import static androidx.test.espresso.web.webdriver.DriverAtoms.webKeys;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.service.notification.StatusBarNotification;
|
||||||
|
|
||||||
import androidx.test.core.app.ActivityScenario;
|
import androidx.test.core.app.ActivityScenario;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
@ -44,6 +47,8 @@ public final class CrossClientStagingInstrumentedTest {
|
||||||
String androidMessage = requiredArgument("android_message");
|
String androidMessage = requiredArgument("android_message");
|
||||||
String browserReply = requiredArgument("browser_reply");
|
String browserReply = requiredArgument("browser_reply");
|
||||||
|
|
||||||
|
context.getSystemService(NotificationManager.class).cancelAll();
|
||||||
|
|
||||||
try (ActivityScenario<MainActivity> scenario = launch(loginPath)) {
|
try (ActivityScenario<MainActivity> scenario = launch(loginPath)) {
|
||||||
waitForElement("remember-login-button");
|
waitForElement("remember-login-button");
|
||||||
click("remember-login-button");
|
click("remember-login-button");
|
||||||
|
|
@ -53,6 +58,15 @@ public final class CrossClientStagingInstrumentedTest {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try (ActivityScenario<MainActivity> scenario = launch("/notifications")) {
|
||||||
|
waitForSelector(".phx-connected");
|
||||||
|
clickSelector("[data-enable-push]:not([disabled])");
|
||||||
|
waitForSelectorText(
|
||||||
|
"[data-push-status]",
|
||||||
|
"Push notifications are enabled on this Android device."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try (ActivityScenario<MainActivity> scenario = launch(requestPath)) {
|
try (ActivityScenario<MainActivity> scenario = launch(requestPath)) {
|
||||||
waitForSelector(".phx-connected");
|
waitForSelector(".phx-connected");
|
||||||
waitForElementText("messages", "No messages yet.");
|
waitForElementText("messages", "No messages yet.");
|
||||||
|
|
@ -62,6 +76,10 @@ public final class CrossClientStagingInstrumentedTest {
|
||||||
click("share-location-button");
|
click("share-location-button");
|
||||||
waitForServiceState(true);
|
waitForServiceState(true);
|
||||||
waitForElementText("messages", browserReply);
|
waitForElementText("messages", browserReply);
|
||||||
|
waitForNotification(
|
||||||
|
"New message",
|
||||||
|
"Open Who Need Help to read the conversation."
|
||||||
|
);
|
||||||
click("stop-location-button");
|
click("stop-location-button");
|
||||||
waitForServiceState(false);
|
waitForServiceState(false);
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +115,13 @@ public final class CrossClientStagingInstrumentedTest {
|
||||||
.perform(webClick());
|
.perform(webClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void clickSelector(String selector) throws InterruptedException {
|
||||||
|
waitForSelector(selector);
|
||||||
|
onWebView()
|
||||||
|
.withElement(findElement(Locator.CSS_SELECTOR, selector))
|
||||||
|
.perform(webClick());
|
||||||
|
}
|
||||||
|
|
||||||
private static void replaceText(String id, String value)
|
private static void replaceText(String id, String value)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
waitForElement(id);
|
waitForElement(id);
|
||||||
|
|
@ -111,6 +136,11 @@ public final class CrossClientStagingInstrumentedTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void waitForSelector(String selector)
|
private static void waitForSelector(String selector)
|
||||||
|
throws InterruptedException {
|
||||||
|
waitForSelectorText(selector, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void waitForSelectorText(String selector, String expected)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
long deadline =
|
long deadline =
|
||||||
System.nanoTime() + TimeUnit.SECONDS.toNanos(PAGE_TIMEOUT_SECONDS);
|
System.nanoTime() + TimeUnit.SECONDS.toNanos(PAGE_TIMEOUT_SECONDS);
|
||||||
|
|
@ -120,7 +150,7 @@ public final class CrossClientStagingInstrumentedTest {
|
||||||
try {
|
try {
|
||||||
onWebView()
|
onWebView()
|
||||||
.withElement(findElement(Locator.CSS_SELECTOR, selector))
|
.withElement(findElement(Locator.CSS_SELECTOR, selector))
|
||||||
.check(webMatches(getText(), containsString("")));
|
.check(webMatches(getText(), containsString(expected)));
|
||||||
return;
|
return;
|
||||||
} catch (AssertionError | RuntimeException failure) {
|
} catch (AssertionError | RuntimeException failure) {
|
||||||
lastFailure = failure;
|
lastFailure = failure;
|
||||||
|
|
@ -129,7 +159,10 @@ public final class CrossClientStagingInstrumentedTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
AssertionError timeout = new AssertionError(
|
AssertionError timeout = new AssertionError(
|
||||||
"WebView selector did not appear: " + selector
|
"WebView selector did not contain expected text: "
|
||||||
|
+ selector
|
||||||
|
+ " => "
|
||||||
|
+ expected
|
||||||
);
|
);
|
||||||
|
|
||||||
if (lastFailure != null) {
|
if (lastFailure != null) {
|
||||||
|
|
@ -139,6 +172,38 @@ public final class CrossClientStagingInstrumentedTest {
|
||||||
throw timeout;
|
throw timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void waitForNotification(String expectedTitle, String expectedBody)
|
||||||
|
throws InterruptedException {
|
||||||
|
NotificationManager manager =
|
||||||
|
context.getSystemService(NotificationManager.class);
|
||||||
|
long deadline =
|
||||||
|
System.nanoTime() + TimeUnit.SECONDS.toNanos(PAGE_TIMEOUT_SECONDS);
|
||||||
|
|
||||||
|
while (System.nanoTime() < deadline) {
|
||||||
|
for (StatusBarNotification active : manager.getActiveNotifications()) {
|
||||||
|
Notification notification = active.getNotification();
|
||||||
|
CharSequence title =
|
||||||
|
notification.extras.getCharSequence(Notification.EXTRA_TITLE);
|
||||||
|
CharSequence body =
|
||||||
|
notification.extras.getCharSequence(Notification.EXTRA_TEXT);
|
||||||
|
|
||||||
|
if (
|
||||||
|
expectedTitle.contentEquals(title == null ? "" : title)
|
||||||
|
&& expectedBody.contentEquals(body == null ? "" : body)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(250);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertionError(
|
||||||
|
"Android did not receive the expected FCM notification: "
|
||||||
|
+ expectedTitle
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static void waitForElementText(String id, String expected)
|
private static void waitForElementText(String id, String expected)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
long deadline =
|
long deadline =
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ defmodule Mix.Tasks.Wnh.StagingAndroidE2e do
|
||||||
alias WhoNeedHelp.Catalog.Category
|
alias WhoNeedHelp.Catalog.Category
|
||||||
alias WhoNeedHelp.Help.{Assignment, HelpRequest}
|
alias WhoNeedHelp.Help.{Assignment, HelpRequest}
|
||||||
alias WhoNeedHelp.Messaging.Message
|
alias WhoNeedHelp.Messaging.Message
|
||||||
|
alias WhoNeedHelp.Notifications.{Notification, PushDevice}
|
||||||
alias WhoNeedHelp.Repo
|
alias WhoNeedHelp.Repo
|
||||||
alias WhoNeedHelp.Tracking.{Position, TrackingSession}
|
alias WhoNeedHelp.Tracking.{Position, TrackingSession}
|
||||||
alias WhoNeedHelp.Trust.{AuditEvent, RateLimitBucket}
|
alias WhoNeedHelp.Trust.{AuditEvent, RateLimitBucket}
|
||||||
|
|
@ -195,7 +196,26 @@ defmodule Mix.Tasks.Wnh.StagingAndroidE2e do
|
||||||
|
|
||||||
validate_interactions!(assignment_id, user_ids)
|
validate_interactions!(assignment_id, user_ids)
|
||||||
|
|
||||||
job_ids = fixture_push_job_ids(user_ids, assignment_id)
|
notification_ids =
|
||||||
|
Notification
|
||||||
|
|> where([notification], notification.user_id in ^user_ids)
|
||||||
|
|> select([notification], notification.id)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
device_ids =
|
||||||
|
PushDevice
|
||||||
|
|> where([device], device.user_id in ^user_ids)
|
||||||
|
|> select([device], device.id)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
job_ids =
|
||||||
|
fixture_push_job_ids(
|
||||||
|
user_ids,
|
||||||
|
assignment_id,
|
||||||
|
notification_ids,
|
||||||
|
device_ids
|
||||||
|
)
|
||||||
|
|
||||||
scope_hashes = Enum.map(user_ids, &:crypto.hash(:sha256, &1))
|
scope_hashes = Enum.map(user_ids, &:crypto.hash(:sha256, &1))
|
||||||
|
|
||||||
{:ok, deleted} =
|
{:ok, deleted} =
|
||||||
|
|
@ -247,21 +267,32 @@ defmodule Mix.Tasks.Wnh.StagingAndroidE2e do
|
||||||
validate_fixture_links!(request_id, assignment_id, requester_id, helper_id)
|
validate_fixture_links!(request_id, assignment_id, requester_id, helper_id)
|
||||||
validate_interactions!(assignment_id, [requester_id, helper_id])
|
validate_interactions!(assignment_id, [requester_id, helper_id])
|
||||||
|
|
||||||
messages =
|
message_records =
|
||||||
Message
|
Message
|
||||||
|> where([message], message.assignment_id == ^assignment_id)
|
|> where([message], message.assignment_id == ^assignment_id)
|
||||||
|> select([message], {message.sender_id, message.body})
|
|> select([message], {message.id, message.sender_id, message.body})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
|
messages =
|
||||||
|
message_records
|
||||||
|
|> Enum.map(fn {_id, sender_id, body} -> {sender_id, body} end)
|
||||||
|> Enum.sort()
|
|> Enum.sort()
|
||||||
|
|
||||||
expected_messages =
|
expected_messages = Enum.sort([{helper_id, android_message}, {requester_id, browser_reply}])
|
||||||
[{helper_id, android_message}, {requester_id, browser_reply}]
|
|
||||||
|> Enum.sort()
|
|
||||||
|
|
||||||
unless messages == expected_messages do
|
unless messages == expected_messages do
|
||||||
Mix.raise("expected the exact Android/browser messages, observed #{inspect(messages)}")
|
Mix.raise("expected the exact Android/browser messages, observed #{inspect(messages)}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
browser_message_id =
|
||||||
|
Enum.find_value(message_records, fn
|
||||||
|
{message_id, ^requester_id, ^browser_reply} -> message_id
|
||||||
|
_other -> nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
{push_device, notification, delivery_job} =
|
||||||
|
verified_fcm_delivery!(helper_id, assignment_id, browser_message_id)
|
||||||
|
|
||||||
sessions =
|
sessions =
|
||||||
TrackingSession
|
TrackingSession
|
||||||
|> where(
|
|> where(
|
||||||
|
|
@ -310,6 +341,10 @@ defmodule Mix.Tasks.Wnh.StagingAndroidE2e do
|
||||||
"request_id" => request_id,
|
"request_id" => request_id,
|
||||||
"assignment_id" => assignment_id,
|
"assignment_id" => assignment_id,
|
||||||
"message_count" => length(messages),
|
"message_count" => length(messages),
|
||||||
|
"android_push_device_id" => push_device.id,
|
||||||
|
"fcm_notification_id" => notification.id,
|
||||||
|
"fcm_delivery_job_id" => delivery_job.id,
|
||||||
|
"fcm_delivery_state" => to_string(delivery_job.state),
|
||||||
"tracking_session_count" => length(sessions),
|
"tracking_session_count" => length(sessions),
|
||||||
"tracking_sample_count" => hd(sessions).sample_count,
|
"tracking_sample_count" => hd(sessions).sample_count,
|
||||||
"active_tracking_sessions" => 0,
|
"active_tracking_sessions" => 0,
|
||||||
|
|
@ -403,18 +438,82 @@ defmodule Mix.Tasks.Wnh.StagingAndroidE2e do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fixture_push_job_ids(user_ids, assignment_id) do
|
defp fixture_push_job_ids(user_ids, assignment_id, notification_ids, device_ids) do
|
||||||
recipients = Enum.map(user_ids, &"user:#{&1}")
|
recipients = Enum.map(user_ids, &"user:#{&1}")
|
||||||
|
notification_ids = MapSet.new(notification_ids)
|
||||||
|
device_ids = MapSet.new(device_ids)
|
||||||
|
|
||||||
Job
|
Job
|
||||||
|> where([job], job.worker == "WhoNeedHelp.Push.DeliveryWorker")
|
|
||||||
|> where(
|
|> where(
|
||||||
[job],
|
[job],
|
||||||
fragment("?->>'recipient' = ANY(?)", job.args, ^recipients) or
|
job.worker in [
|
||||||
fragment("?->>'assignment_id' = ?", job.args, ^assignment_id)
|
"WhoNeedHelp.Push.DeliveryWorker",
|
||||||
|
"WhoNeedHelp.Push.NotificationDispatchWorker",
|
||||||
|
"WhoNeedHelp.Push.DeviceDeliveryWorker",
|
||||||
|
"WhoNeedHelp.Push.NotificationEmailWorker"
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|> select([job], job.id)
|
|> select([job], {job.id, job.args})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|> Enum.filter(fn {_id, args} ->
|
||||||
|
args["recipient"] in recipients or
|
||||||
|
args["assignment_id"] == assignment_id or
|
||||||
|
MapSet.member?(notification_ids, args["notification_id"]) or
|
||||||
|
MapSet.member?(device_ids, args["device_id"])
|
||||||
|
end)
|
||||||
|
|> Enum.map(&elem(&1, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp verified_fcm_delivery!(helper_id, assignment_id, browser_message_id)
|
||||||
|
when is_binary(browser_message_id) do
|
||||||
|
push_device =
|
||||||
|
PushDevice
|
||||||
|
|> where(
|
||||||
|
[device],
|
||||||
|
device.user_id == ^helper_id and device.platform == :android and
|
||||||
|
device.provider == :fcm and is_nil(device.disabled_at)
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
notification =
|
||||||
|
Repo.get_by(
|
||||||
|
Notification,
|
||||||
|
user_id: helper_id,
|
||||||
|
idempotency_key: "message-created:#{browser_message_id}:#{helper_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
delivery_job =
|
||||||
|
if push_device && notification do
|
||||||
|
Job
|
||||||
|
|> where([job], job.worker == "WhoNeedHelp.Push.DeviceDeliveryWorker")
|
||||||
|
|> where(
|
||||||
|
[job],
|
||||||
|
fragment("?->>'notification_id' = ?", job.args, ^notification.id) and
|
||||||
|
fragment("?->>'device_id' = ?", job.args, ^push_device.id)
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
valid_notification? =
|
||||||
|
match?(
|
||||||
|
%Notification{
|
||||||
|
kind: :message_created,
|
||||||
|
title: "New message",
|
||||||
|
body: "Open Who Need Help to read the conversation."
|
||||||
|
},
|
||||||
|
notification
|
||||||
|
) and notification.data["assignment_id"] == assignment_id
|
||||||
|
|
||||||
|
unless match?(%PushDevice{}, push_device) and valid_notification? and
|
||||||
|
match?(%Job{state: "completed"}, delivery_job) do
|
||||||
|
Mix.raise("expected one completed Android FCM delivery for the browser reply")
|
||||||
|
end
|
||||||
|
|
||||||
|
{push_device, notification, delivery_job}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp verified_fcm_delivery!(_helper_id, _assignment_id, _browser_message_id) do
|
||||||
|
Mix.raise("could not identify the browser reply used for Android FCM delivery")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp assert_prefix_unused!(context) do
|
defp assert_prefix_unused!(context) do
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,11 @@ browser_image="who-need-help-e2e-tests:android-$run_id"
|
||||||
android_image="who-need-help-android:$variant-cross-client-$run_id"
|
android_image="who-need-help-android:$variant-cross-client-$run_id"
|
||||||
container="who-need-help-android-$variant-cross-client-$run_id"
|
container="who-need-help-android-$variant-cross-client-$run_id"
|
||||||
avd_volume="who-need-help-android-avd-$variant-cross-client-$run_id"
|
avd_volume="who-need-help-android-avd-$variant-cross-client-$run_id"
|
||||||
# Android's Automated Test Device image removes applications and background
|
# Firebase Messaging 25.1.1 requires Google Play services 26.12 or newer.
|
||||||
# services that are unrelated to app-level automation. Keep the API 37/16 KB
|
# The stable Android 37.1 Google APIs image supplies that version while keeping
|
||||||
# compatibility coverage in the instrumentation matrix and public smoke test;
|
# the 16 KB page-size coverage used by the public smoke workflow.
|
||||||
# use the Google ATD image for this resource-intensive two-client workflow.
|
android_api=37
|
||||||
android_api=35
|
android_system_image=system-images/android-37.1/google_apis_ps16k/x86_64
|
||||||
android_system_image=system-images/android-35/google_atd/x86_64
|
|
||||||
service_class=org.whoneedhelp.mobile.TrackingService
|
service_class=org.whoneedhelp.mobile.TrackingService
|
||||||
fixture_password="$(openssl rand -hex 24)"
|
fixture_password="$(openssl rand -hex 24)"
|
||||||
android_message="android-$run_id"
|
android_message="android-$run_id"
|
||||||
|
|
@ -311,6 +310,9 @@ UNION ALL SELECT 'users_tokens', count(*) FROM users_tokens
|
||||||
UNION ALL SELECT 'help_requests', count(*) FROM help_requests
|
UNION ALL SELECT 'help_requests', count(*) FROM help_requests
|
||||||
UNION ALL SELECT 'messages', count(*) FROM messages
|
UNION ALL SELECT 'messages', count(*) FROM messages
|
||||||
UNION ALL SELECT 'help_assignments', count(*) FROM help_assignments
|
UNION ALL SELECT 'help_assignments', count(*) FROM help_assignments
|
||||||
|
UNION ALL SELECT 'notifications', count(*) FROM notifications
|
||||||
|
UNION ALL SELECT 'notification_preferences', count(*) FROM notification_preferences
|
||||||
|
UNION ALL SELECT 'push_devices', count(*) FROM push_devices
|
||||||
UNION ALL SELECT 'reports', count(*) FROM reports
|
UNION ALL SELECT 'reports', count(*) FROM reports
|
||||||
UNION ALL SELECT 'reviews', count(*) FROM reviews
|
UNION ALL SELECT 'reviews', count(*) FROM reviews
|
||||||
UNION ALL SELECT 'audit_events', count(*) FROM audit_events
|
UNION ALL SELECT 'audit_events', count(*) FROM audit_events
|
||||||
|
|
@ -824,6 +826,8 @@ fi
|
||||||
printf 'magic_link_login=true\n'
|
printf 'magic_link_login=true\n'
|
||||||
printf 'android_chat_message_visible_in_browser=true\n'
|
printf 'android_chat_message_visible_in_browser=true\n'
|
||||||
printf 'browser_chat_reply_visible_in_android=true\n'
|
printf 'browser_chat_reply_visible_in_android=true\n'
|
||||||
|
printf 'android_fcm_device_registered=true\n'
|
||||||
|
printf 'android_fcm_notification_received=true\n'
|
||||||
printf 'android_foreground_tracking_visible_in_browser=true\n'
|
printf 'android_foreground_tracking_visible_in_browser=true\n'
|
||||||
printf 'android_tracking_stop_visible_in_browser=true\n'
|
printf 'android_tracking_stop_visible_in_browser=true\n'
|
||||||
printf 'retained_raw_position_after_stop=0\n'
|
printf 'retained_raw_position_after_stop=0\n'
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ docker build \
|
||||||
--build-arg "WNH_TRACKING_MIN_TIME_MS=$WNH_TRACKING_MIN_TIME_MS" \
|
--build-arg "WNH_TRACKING_MIN_TIME_MS=$WNH_TRACKING_MIN_TIME_MS" \
|
||||||
--build-arg "WNH_TRACKING_HTTP_TIMEOUT_MS=$WNH_TRACKING_HTTP_TIMEOUT_MS" \
|
--build-arg "WNH_TRACKING_HTTP_TIMEOUT_MS=$WNH_TRACKING_HTTP_TIMEOUT_MS" \
|
||||||
--build-arg \
|
--build-arg \
|
||||||
"ANDROID_EMULATOR_SYSTEM_IMAGE=system-images/android-37.0/google_apis_ps16k/x86_64" \
|
"ANDROID_EMULATOR_SYSTEM_IMAGE=system-images/android-37.1/google_apis_ps16k/x86_64" \
|
||||||
--target emulator \
|
--target emulator \
|
||||||
--tag "$image" \
|
--tag "$image" \
|
||||||
"$ROOT/android" >"$output/image-build.txt"
|
"$ROOT/android" >"$output/image-build.txt"
|
||||||
|
|
@ -380,7 +380,7 @@ fi
|
||||||
|
|
||||||
{
|
{
|
||||||
printf 'run_id=%s\n' "$run_id"
|
printf 'run_id=%s\n' "$run_id"
|
||||||
printf 'android_api=37.0\n'
|
printf 'android_api=37.1\n'
|
||||||
printf 'package=%s\n' "$package"
|
printf 'package=%s\n' "$package"
|
||||||
printf 'public_origin=%s\n' "$WNH_BASE_URL"
|
printf 'public_origin=%s\n' "$WNH_BASE_URL"
|
||||||
printf 'home_loaded=true\n'
|
printf 'home_loaded=true\n'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user