diff --git a/build/cromite_patches_list.txt b/build/cromite_patches_list.txt index a378b36d..3ca73a73 100644 --- a/build/cromite_patches_list.txt +++ b/build/cromite_patches_list.txt @@ -303,6 +303,7 @@ Disable-Viewport-Segments.patch Keep-disabled-cache-sharing-for-pervasive-scripts.patch High-Resolution-Timing-Mitigation.patch Enable-snackbar-swipe.patch +Add-cromite-test-support.patch Temp-disable-UseContextSnapshot.patch # temporary or wip patches diff --git a/build/patches/Add-cromite-test-support.patch b/build/patches/Add-cromite-test-support.patch new file mode 100644 index 00000000..311f3e9a --- /dev/null +++ b/build/patches/Add-cromite-test-support.patch @@ -0,0 +1,932 @@ +From: uazo +Date: Fri, 22 Aug 2025 15:41:58 +0000 +Subject: Add cromite test support + +Allows the activation of specific APIs used for cromite testing. +API are only available on the chrome://version page. +It allows the use of tools such as Browserstack for test automation. +Do not activate the corresponding options (available in the developer +settings) if you do not know what you are doing. + +License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html +--- + .../java/res/xml/developer_preferences.xml | 12 ++ + .../init/ProcessInitializationHandler.java | 6 +- + .../tracing/settings/DeveloperSettings.java | 2 +- + chrome/browser/BUILD.gn | 5 + + chrome/browser/android/devtools_server.cc | 10 + + ...ontent_browser_client_receiver_bindings.cc | 10 + + .../cromite/cromite_private_api_host.cc | 74 +++++++ + .../cromite/cromite_private_api_host.h | 54 +++++ + .../browser/flags/cromite/sActAsWebView.java | 33 +++ + .../cromite/sEnableCromiteTestSupport.java | 33 +++ + .../NotificationPermissionController.java | 1 + + chrome/browser/ui/startup/bad_flags_prompt.cc | 1 + + chrome/common/BUILD.gn | 1 + + .../cromite_private_api_extension.mojom | 7 + + chrome/renderer/BUILD.gn | 2 + + .../chrome_content_renderer_client.cc | 2 + + .../cromite/cromite_private_api_extension.cc | 203 ++++++++++++++++++ + .../cromite/cromite_private_api_extension.h | 65 ++++++ + .../component_updater_service.h | 1 + + .../browser/devtools/devtools_http_handler.cc | 7 +- + content/browser/log_console_message.cc | 15 +- + content/browser/log_console_message.h | 3 +- + .../renderer_host/render_frame_host_impl.cc | 13 +- + .../about_flags_cc/Enable-test-support.inc | 19 ++ + .../Enable-test-support.inc | 7 + + .../Enable-test-support.inc | 2 + + 26 files changed, 581 insertions(+), 7 deletions(-) + create mode 100644 chrome/browser/cromite/cromite_private_api_host.cc + create mode 100644 chrome/browser/cromite/cromite_private_api_host.h + create mode 100644 chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sActAsWebView.java + create mode 100644 chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sEnableCromiteTestSupport.java + create mode 100644 chrome/common/cromite_private_api_extension.mojom + create mode 100644 chrome/renderer/cromite/cromite_private_api_extension.cc + create mode 100644 chrome/renderer/cromite/cromite_private_api_extension.h + create mode 100644 cromite_flags/chrome/browser/about_flags_cc/Enable-test-support.inc + create mode 100644 cromite_flags/content/public/common/content_features_cc/Enable-test-support.inc + create mode 100644 cromite_flags/content/public/common/content_features_h/Enable-test-support.inc + +diff --git a/chrome/android/java/res/xml/developer_preferences.xml b/chrome/android/java/res/xml/developer_preferences.xml +--- a/chrome/android/java/res/xml/developer_preferences.xml ++++ b/chrome/android/java/res/xml/developer_preferences.xml +@@ -12,6 +12,18 @@ found in the LICENSE file. + android:fragment="org.chromium.chrome.browser.tracing.settings.TracingSettings" + android:key="tracing" + android:title="Tracing" /> ++ ++ + +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java +@@ -139,6 +139,7 @@ import org.chromium.ui.base.PhotoPickerListener; + import org.chromium.ui.base.SelectFileDialog; + import org.chromium.ui.base.WindowAndroid; + import org.chromium.url.GURL; ++import org.chromium.chrome.browser.flags.cromite.sActAsWebView; + + import java.io.File; + import java.nio.charset.StandardCharsets; +@@ -698,7 +699,10 @@ public class ProcessInitializationHandler { + + tasks.add( + () -> { +- mDevToolsServer = new DevToolsServer(DEV_TOOLS_SERVER_SOCKET_PREFIX); ++ String socketPrefix = DEV_TOOLS_SERVER_SOCKET_PREFIX; ++ if (sActAsWebView.getInstance().isEnabled()) ++ socketPrefix = "weblayer"; ++ mDevToolsServer = new DevToolsServer(socketPrefix); + mDevToolsServer.setRemoteDebuggingEnabled( + true, DevToolsServer.Security.ALLOW_DEBUG_PERMISSION); + }); +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tracing/settings/DeveloperSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/tracing/settings/DeveloperSettings.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/tracing/settings/DeveloperSettings.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/tracing/settings/DeveloperSettings.java +@@ -37,7 +37,7 @@ public class DeveloperSettings extends ChromeBaseSettingsFragment implements Emb + private final ObservableSupplier mPageTitle = + new ObservableSupplierImpl<>(MSG_DEVELOPER_OPTIONS_TITLE); + +- private static @Nullable Boolean sIsEnabledForTests; ++ private static @Nullable Boolean sIsEnabledForTests = true; + + public static boolean shouldShowDeveloperSettings() { + // Always enabled on canary, dev and local builds, otherwise can be enabled by tapping the +diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn +--- a/chrome/browser/BUILD.gn ++++ b/chrome/browser/BUILD.gn +@@ -194,6 +194,11 @@ source_set("shell_integration") { + # require many files from it. This makes linking more efficient. + static_library("browser") { + sources = [ ++ "cromite/cromite_private_api_host.cc", ++ "cromite/cromite_private_api_host.h", ++ ] ++ ++ sources += [ + "about_flags.cc", + "accessibility/accessibility_labels_service.cc", + "accessibility/accessibility_labels_service.h", +diff --git a/chrome/browser/android/devtools_server.cc b/chrome/browser/android/devtools_server.cc +--- a/chrome/browser/android/devtools_server.cc ++++ b/chrome/browser/android/devtools_server.cc +@@ -12,6 +12,7 @@ + #include "base/command_line.h" + #include "base/compiler_specific.h" + #include "base/files/file_path.h" ++#include "base/feature_list.h" + #include "base/functional/bind.h" + #include "base/functional/callback.h" + #include "base/strings/string_number_conversions.h" +@@ -32,6 +33,7 @@ + #include "content/public/browser/render_view_host.h" + #include "content/public/browser/web_contents.h" + #include "content/public/browser/web_contents_delegate.h" ++#include "content/public/common/content_features.h" + #include "content/public/common/content_switches.h" + #include "content/public/common/url_constants.h" + #include "net/base/net_errors.h" +@@ -64,6 +66,8 @@ const int kBackLog = 10; + + bool AuthorizeSocketAccessWithDebugPermission( + const net::UnixDomainServerSocket::Credentials& credentials) { ++ if (base::FeatureList::IsEnabled(features::kEnableCromiteTestSupport)) ++ return true; + JNIEnv* env = base::android::AttachCurrentThread(); + return Java_DevToolsServer_checkDebugPermission(env, credentials.process_id, + credentials.user_id) || +@@ -127,6 +131,10 @@ DevToolsServer::DevToolsServer(const std::string& socket_name_prefix) + : socket_name_(base::StringPrintf(kDevToolsChannelNameFormat, + socket_name_prefix.c_str())), + is_started_(false) { ++ if (base::FeatureList::IsEnabled(features::kActAsWebView)) { ++ socket_name_ = base::StringPrintf("%s_devtools_remote_%d", ++ socket_name_prefix.c_str(), getpid()); ++ } + // Override the socket name if one is specified on the command line. + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); +@@ -144,6 +152,8 @@ void DevToolsServer::Start(bool allow_debug_permission) { + if (is_started_) + return; + ++ DLOG(INFO) << "---DevToolsServer::Start allow_debug_permission=" << allow_debug_permission; ++ DLOG(INFO) << "---socket_name_=" << socket_name_; + net::UnixDomainServerSocket::AuthCallback auth_callback = + allow_debug_permission + ? base::BindRepeating(&AuthorizeSocketAccessWithDebugPermission) +diff --git a/chrome/browser/chrome_content_browser_client_receiver_bindings.cc b/chrome/browser/chrome_content_browser_client_receiver_bindings.cc +--- a/chrome/browser/chrome_content_browser_client_receiver_bindings.cc ++++ b/chrome/browser/chrome_content_browser_client_receiver_bindings.cc +@@ -10,6 +10,7 @@ + #include "build/build_config.h" + #include "chrome/browser/browser_process.h" + #include "chrome/browser/chrome_browser_interface_binders.h" ++#include "chrome/browser/cromite/cromite_private_api_host.h" + #include "chrome/browser/chrome_browser_interface_binders_webui.h" + #include "chrome/browser/chrome_content_browser_client.h" + #include "chrome/browser/chrome_content_browser_client_parts.h" +@@ -499,6 +500,15 @@ void ChromeContentBrowserClient:: + render_frame_host); + }, + &render_frame_host)); ++ associated_registry.AddInterface< ++ chrome::mojom::CromitePrivateApiExtension>(base::BindRepeating( ++ [](content::RenderFrameHost* render_frame_host, ++ mojo::PendingAssociatedReceiver< ++ chrome::mojom::CromitePrivateApiExtension> receiver) { ++ CromitePrivateApiHost::BindHost(std::move(receiver), ++ render_frame_host); ++ }, ++ &render_frame_host)); + associated_registry.AddInterface< + chrome::mojom::GoogleAccountsPrivateApiExtension>(base::BindRepeating( + [](content::RenderFrameHost* render_frame_host, +diff --git a/chrome/browser/cromite/cromite_private_api_host.cc b/chrome/browser/cromite/cromite_private_api_host.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/cromite/cromite_private_api_host.cc +@@ -0,0 +1,74 @@ ++#include "chrome/browser/cromite/cromite_private_api_host.h" ++ ++#include "base/json/json_writer.h" ++ ++#include "chrome/browser/browser_process.h" ++#include "components/component_updater/component_updater_service.h" ++#include "components/update_client/crx_update_item.h" ++#include "content/public/browser/document_user_data.h" ++#include "content/public/browser/navigation_handle.h" ++#include "content/public/browser/render_frame_host.h" ++#include "content/public/browser/site_instance.h" ++ ++CromitePrivateApiHost::CromitePrivateApiHost( ++ content::RenderFrameHost* rfh) ++ : DocumentUserData(rfh), ++ receiver_(this) {} ++ ++CromitePrivateApiHost::~CromitePrivateApiHost() = default; ++ ++DOCUMENT_USER_DATA_KEY_IMPL(CromitePrivateApiHost); ++ ++void CromitePrivateApiHost::BindReceiver( ++ mojo::PendingAssociatedReceiver< ++ chrome::mojom::CromitePrivateApiExtension> receiver) { ++ receiver_.Bind(std::move(receiver)); ++} ++ ++void CromitePrivateApiHost::GetComponentData( ++ const std::string& id, ++ GetComponentDataCallback callback) { ++ LOG(INFO) << "---CromitePrivateApiHost::GetComponentData " ++ << "id=" << id; ++ const raw_ptr ++ component_updater = g_browser_process->component_updater(); ++ ++ update_client::CrxUpdateItem item; ++ if (component_updater->GetComponentDetails(id, &item) && item.component) { ++ base::Value::Dict dict; ++ dict.Set("id", id); ++ dict.Set("version", item.component->version.GetString()); ++ dict.Set("status", (int)item.state); ++ ++ std::string json_string; ++ base::JSONWriter::WriteWithOptions( ++ dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string); ++ base::TrimWhitespaceASCII(json_string, base::TRIM_ALL, &json_string); ++ ++ std::move(callback).Run(json_string); ++ } ++} ++ ++void CromitePrivateApiHost::StartComponentUpdate(const std::string& id) { ++ LOG(INFO) << "---CromitePrivateApiHost::StartComponentUpdate " ++ << "id=" << id; ++ const raw_ptr ++ component_updater = g_browser_process->component_updater(); ++ component_updater->GetOnDemandUpdater().OnDemandUpdate( ++ id, component_updater::OnDemandUpdater::Priority::FOREGROUND, ++ component_updater::Callback()); ++} ++ ++void CromitePrivateApiHost::SetEnableLogConsole(bool enabled) { ++ enable_log_console_ = enabled; ++} ++ ++// static ++void CromitePrivateApiHost::BindHost( ++ mojo::PendingAssociatedReceiver< ++ chrome::mojom::CromitePrivateApiExtension> receiver, ++ content::RenderFrameHost* render_frame_host) { ++ CromitePrivateApiHost* api_host = ++ CromitePrivateApiHost::GetOrCreateForCurrentDocument(render_frame_host); ++ api_host->BindReceiver(std::move(receiver)); ++} +diff --git a/chrome/browser/cromite/cromite_private_api_host.h b/chrome/browser/cromite/cromite_private_api_host.h +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/cromite/cromite_private_api_host.h +@@ -0,0 +1,54 @@ ++#ifndef CHROME_BROWSER_CROMITE_PRIVATE_API_HOST_H_ ++#define CHROME_BROWSER_CROMITE_PRIVATE_API_HOST_H_ ++ ++#include ++ ++#include "base/functional/callback.h" ++#include "chrome/common/cromite_private_api_extension.mojom.h" ++#include "content/public/browser/document_user_data.h" ++#include "mojo/public/cpp/bindings/associated_receiver.h" ++ ++namespace content { ++class NavigationHandle; ++class RenderFrameHost; ++} // namespace content ++ ++class CromitePrivateApiHost ++ : public chrome::mojom::CromitePrivateApiExtension, ++ public content::DocumentUserData { ++ public: ++ ~CromitePrivateApiHost() override; ++ CromitePrivateApiHost(const CromitePrivateApiHost&) = delete; ++ CromitePrivateApiHost& operator=(const CromitePrivateApiHost&) = ++ delete; ++ ++ static void BindHost( ++ mojo::PendingAssociatedReceiver< ++ chrome::mojom::CromitePrivateApiExtension> receiver, ++ content::RenderFrameHost* render_frame_host); ++ ++ void BindReceiver(mojo::PendingAssociatedReceiver< ++ chrome::mojom::CromitePrivateApiExtension> receiver); ++ ++ bool IsLogConsoleEnabled() const { return enable_log_console_; } ++ ++ // chrome::mojom::CromitePrivateApiExtension: ++ void GetComponentData(const std::string& id, ++ GetComponentDataCallback callback) override; ++ void StartComponentUpdate(const std::string& id) override; ++ void SetEnableLogConsole(bool can_show) override; ++ ++ private: ++ explicit CromitePrivateApiHost( ++ content::RenderFrameHost* rfh); ++ ++ friend DocumentUserData; ++ DOCUMENT_USER_DATA_KEY_DECL(); ++ ++ mojo::AssociatedReceiver ++ receiver_; ++ ++ bool enable_log_console_ = false; ++}; ++ ++#endif // CHROME_BROWSER_CROMITE_PRIVATE_API_HOST_H_ +diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sActAsWebView.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sActAsWebView.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sActAsWebView.java +@@ -0,0 +1,33 @@ ++/* ++ This file is part of Cromite. ++ ++ Cromite is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ Cromite is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with Cromite. If not, see . ++*/ ++ ++package org.chromium.chrome.browser.flags.cromite; ++ ++import org.chromium.components.cached_flags.CachedFlag; ++import org.chromium.chrome.browser.flags.ChromeFeatureMap; ++ ++public class sActAsWebView { ++ private static final CachedFlag sInstance = ++ new CachedFlag(ChromeFeatureMap.getInstance(), ++ "ActAsWebView", false); ++ ++ private sActAsWebView() {} ++ ++ public static CachedFlag getInstance() { ++ return sInstance; ++ } ++} +diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sEnableCromiteTestSupport.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sEnableCromiteTestSupport.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sEnableCromiteTestSupport.java +@@ -0,0 +1,33 @@ ++/* ++ This file is part of Cromite. ++ ++ Cromite is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ Cromite is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with Cromite. If not, see . ++*/ ++ ++package org.chromium.chrome.browser.flags.cromite; ++ ++import org.chromium.components.cached_flags.CachedFlag; ++import org.chromium.chrome.browser.flags.ChromeFeatureMap; ++ ++public class sEnableCromiteTestSupport { ++ private static final CachedFlag sInstance = ++ new CachedFlag(ChromeFeatureMap.getInstance(), ++ "EnableCromiteTestSupport", false); ++ ++ private sEnableCromiteTestSupport() {} ++ ++ public static CachedFlag getInstance() { ++ return sInstance; ++ } ++} +diff --git a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/permissions/NotificationPermissionController.java b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/permissions/NotificationPermissionController.java +--- a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/permissions/NotificationPermissionController.java ++++ b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/permissions/NotificationPermissionController.java +@@ -182,6 +182,7 @@ public class NotificationPermissionController implements UnownedUserData { + * @return True if any UI was shown (either rationale dialog or OS prompt), false otherwise. + */ + public boolean requestPermissionIfNeeded(boolean contextual) { ++ if (!contextual) return false; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU + || ApiCompatibilityUtils.isDemoUser()) { + return false; +diff --git a/chrome/browser/ui/startup/bad_flags_prompt.cc b/chrome/browser/ui/startup/bad_flags_prompt.cc +--- a/chrome/browser/ui/startup/bad_flags_prompt.cc ++++ b/chrome/browser/ui/startup/bad_flags_prompt.cc +@@ -188,6 +188,7 @@ static const base::Feature* kBadFeatureFlagsInAboutFlags[] = { + + #if BUILDFLAG(IS_ANDROID) + &chrome::android::kCommandLineOnNonRooted, ++ &features::kEnableCromiteTestSupport, + #endif + + // This flag disables security for the Page Embedded Permission Control, for +diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn +--- a/chrome/common/BUILD.gn ++++ b/chrome/common/BUILD.gn +@@ -689,6 +689,7 @@ mojom("mojo_bindings") { + sources = [ + "chrome_render_frame.mojom", + "google_accounts_private_api_extension.mojom", ++ "cromite_private_api_extension.mojom", + "media/webrtc_logging.mojom", + "net/net_error_page_support.mojom", + "net_benchmarking.mojom", +diff --git a/chrome/common/cromite_private_api_extension.mojom b/chrome/common/cromite_private_api_extension.mojom +new file mode 100644 +--- /dev/null ++++ b/chrome/common/cromite_private_api_extension.mojom +@@ -0,0 +1,7 @@ ++module chrome.mojom; ++ ++interface CromitePrivateApiExtension { ++ GetComponentData(string id) => (string response); ++ StartComponentUpdate(string id); ++ SetEnableLogConsole(bool enabled); ++}; +diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn +--- a/chrome/renderer/BUILD.gn ++++ b/chrome/renderer/BUILD.gn +@@ -78,6 +78,8 @@ static_library("renderer") { + "chrome_render_thread_observer.cc", + "chrome_render_thread_observer.h", + "custom_menu_commands.h", ++ "cromite/cromite_private_api_extension.cc", ++ "cromite/cromite_private_api_extension.h", + "google_accounts_private_api_extension.cc", + "google_accounts_private_api_extension.h", + "google_accounts_private_api_util.cc", +diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc +--- a/chrome/renderer/chrome_content_renderer_client.cc ++++ b/chrome/renderer/chrome_content_renderer_client.cc +@@ -56,6 +56,7 @@ + #include "chrome/renderer/chrome_render_frame_observer.h" + #include "chrome/renderer/chrome_render_thread_observer.h" + #include "chrome/renderer/controlled_frame/controlled_frame_extensions_renderer_api_provider.h" ++#include "chrome/renderer/cromite/cromite_private_api_extension.h" + #include "chrome/renderer/google_accounts_private_api_extension.h" + #include "chrome/renderer/loadtimes_extension_bindings.h" + #include "chrome/renderer/media/flash_embed_rewrite.h" +@@ -665,6 +666,7 @@ void ChromeContentRendererClient::RenderFrameCreated( + + TrustedVaultEncryptionKeysExtension::Create(render_frame); + GoogleAccountsPrivateApiExtension::Create(render_frame); ++ CromitePrivateApiExtension::Create(render_frame); + + if (render_frame->IsMainFrame()) + new webapps::WebPageMetadataAgent(render_frame); +diff --git a/chrome/renderer/cromite/cromite_private_api_extension.cc b/chrome/renderer/cromite/cromite_private_api_extension.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/renderer/cromite/cromite_private_api_extension.cc +@@ -0,0 +1,203 @@ ++#include "chrome/renderer/cromite/cromite_private_api_extension.h" ++ ++#include "chrome/common/chrome_features.h" ++#include "content/public/common/content_features.h" ++#include "content/public/common/isolated_world_ids.h" ++#include "content/public/renderer/chrome_object_extensions_utils.h" ++#include "content/public/renderer/render_frame.h" ++#include "gin/arguments.h" ++#include "gin/function_template.h" ++#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" ++#include "third_party/blink/public/platform/platform.h" ++#include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h" ++#include "third_party/blink/public/web/web_local_frame.h" ++#include "v8/include/v8-context.h" ++#include "v8/include/v8-function.h" ++ ++namespace { ++ ++bool ShouldExposeCromitePrivateApi( ++ content::RenderFrame* render_frame) { ++ DCHECK(render_frame); ++ if (!base::FeatureList::IsEnabled(features::kEnableCromiteTestSupport)) ++ return false; ++ ++ GURL origin_url = GURL("chrome://version"); ++ const url::Origin& allowed_origin = url::Origin::Create(origin_url); ++ ++ const url::Origin origin = render_frame->GetWebFrame()->GetSecurityOrigin(); ++ return origin == allowed_origin; ++} ++ ++} ++ ++CromitePrivateApiExtension::PendingRequest::PendingRequest( ++ v8::Isolate* isolate, ++ v8::Local context, ++ v8::Local resolver) ++ : isolate(isolate), ++ context(isolate, context), ++ promise_resolver(isolate, resolver) {} ++ ++CromitePrivateApiExtension::PendingRequest::~PendingRequest() = default; ++ ++// static ++void CromitePrivateApiExtension::Create(content::RenderFrame* frame) { ++ new CromitePrivateApiExtension(frame); ++} ++ ++CromitePrivateApiExtension::CromitePrivateApiExtension( ++ content::RenderFrame* frame) ++ : content::RenderFrameObserver(frame) {} ++ ++CromitePrivateApiExtension::~CromitePrivateApiExtension() = ++ default; ++ ++void CromitePrivateApiExtension::OnDestruct() { ++ delete this; ++} ++ ++void CromitePrivateApiExtension::DidCreateScriptContext( ++ v8::Local v8_context, ++ int32_t world_id) { ++ if (!render_frame() || world_id != content::ISOLATED_WORLD_ID_GLOBAL) { ++ return; ++ } ++ ++ if (ShouldExposeCromitePrivateApi(render_frame())) { ++ InjectScript(); ++ } ++} ++ ++void CromitePrivateApiExtension::InjectScript() { ++ DCHECK(render_frame()); ++ ++ blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame(); ++ v8::Isolate* isolate = web_frame->GetAgentGroupScheduler()->Isolate(); ++ v8::HandleScope handle_scope(isolate); ++ v8::Local context = web_frame->MainWorldScriptContext(); ++ if (context.IsEmpty()) { ++ return; ++ } ++ ++ v8::Context::Scope context_scope(context); ++ ++ v8::Local window = ++ content::GetOrCreateObject(isolate, context, "window"); ++ v8::Local oCromiteObject = ++ content::GetOrCreateObject(isolate, context, window, "cromite"); ++ ++ oCromiteObject ++ ->Set( ++ context, gin::StringToSymbol(isolate, "getComponentData"), ++ gin::CreateFunctionTemplate( ++ isolate, base::BindRepeating( ++ &CromitePrivateApiExtension::GetComponentData, ++ weak_ptr_factory_.GetWeakPtr())) ++ ->GetFunction(context) ++ .ToLocalChecked()) ++ .Check(); ++ ++ oCromiteObject ++ ->Set( ++ context, gin::StringToSymbol(isolate, "startComponentUpdate"), ++ gin::CreateFunctionTemplate( ++ isolate, base::BindRepeating( ++ &CromitePrivateApiExtension::StartComponentUpdate, ++ weak_ptr_factory_.GetWeakPtr())) ++ ->GetFunction(context) ++ .ToLocalChecked()) ++ .Check(); ++ ++ oCromiteObject ++ ->Set( ++ context, gin::StringToSymbol(isolate, "setEnableLogConsole"), ++ gin::CreateFunctionTemplate( ++ isolate, base::BindRepeating( ++ &CromitePrivateApiExtension::SetEnableLogConsole, ++ weak_ptr_factory_.GetWeakPtr())) ++ ->GetFunction(context) ++ .ToLocalChecked()) ++ .Check(); ++} ++ ++void CromitePrivateApiExtension::GetComponentData(gin::Arguments* args) { ++ std::string component_id; ++ if (!args->GetNext(&component_id)) { ++ args->ThrowError(); ++ return; ++ } ++ ++ if (!remote_.is_bound()) { ++ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(&remote_); ++ } ++ ++ v8::Isolate* isolate = args->isolate(); ++ v8::Local context = isolate->GetCurrentContext(); ++ ++ v8::Local resolver = ++ v8::Promise::Resolver::New(context).ToLocalChecked(); ++ v8::Local promise = resolver->GetPromise(); ++ ++ base::UnguessableToken request_id = base::UnguessableToken::Create(); ++ pending_requests_.emplace( ++ request_id, ++ std::make_unique(isolate, context, resolver)); ++ ++ remote_->GetComponentData(component_id, ++ base::BindOnce(&CromitePrivateApiExtension::OnGetComponentData, ++ weak_ptr_factory_.GetWeakPtr(), request_id)); ++ ++ args->Return(promise); ++} ++ ++void CromitePrivateApiExtension::OnGetComponentData(const base::UnguessableToken callback_id, ++ const std::string& result) { ++ auto iter = pending_requests_.find(callback_id); ++ if (iter == pending_requests_.end()) ++ return; ++ ++ std::unique_ptr request = std::move(iter->second); ++ pending_requests_.erase(iter); ++ ++ v8::HandleScope handle_scope(request->isolate); ++ v8::Local context = ++ request->context.Get(request->isolate); ++ v8::Local resolver = ++ request->promise_resolver.Get(request->isolate); ++ ++ LOG(INFO) << "---CromitePrivateApiExtension::OnGetComponentData4"; ++ resolver->Resolve(context, ++ gin::StringToV8(request->isolate, result)) ++ .ToChecked(); ++ ++ LOG(INFO) << "---CromitePrivateApiExtension::OnGetComponentData5"; ++} ++ ++void CromitePrivateApiExtension::StartComponentUpdate(gin::Arguments* args) { ++ std::string component_id; ++ if (!args->GetNext(&component_id)) { ++ args->ThrowError(); ++ return; ++ } ++ ++ if (!remote_.is_bound()) { ++ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(&remote_); ++ } ++ ++ remote_->StartComponentUpdate(component_id); ++} ++ ++void CromitePrivateApiExtension::SetEnableLogConsole(gin::Arguments* args) { ++ bool enabled; ++ if (!args->GetNext(&enabled)) { ++ args->ThrowError(); ++ return; ++ } ++ ++ if (!remote_.is_bound()) { ++ render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(&remote_); ++ } ++ ++ remote_->SetEnableLogConsole(enabled); ++} +diff --git a/chrome/renderer/cromite/cromite_private_api_extension.h b/chrome/renderer/cromite/cromite_private_api_extension.h +new file mode 100644 +--- /dev/null ++++ b/chrome/renderer/cromite/cromite_private_api_extension.h +@@ -0,0 +1,65 @@ ++#ifndef CHROME_RENDERER_CROMITE_PRIVATE_API_EXTENSION_H_ ++#define CHROME_RENDERER_CROMITE_PRIVATE_API_EXTENSION_H_ ++ ++#include "base/memory/weak_ptr.h" ++#include "chrome/common/cromite_private_api_extension.mojom.h" ++#include "content/public/renderer/render_frame_observer.h" ++#include "mojo/public/cpp/bindings/associated_remote.h" ++#include "v8/include/v8.h" ++ ++namespace gin { ++class Arguments; ++} // namespace gin ++ ++class CromitePrivateApiExtension : public content::RenderFrameObserver { ++ public: ++ // Creates a new instance, with ownership transferred to |*frame|. ++ static void Create(content::RenderFrame* frame); ++ ++ CromitePrivateApiExtension(const CromitePrivateApiExtension&) = ++ delete; ++ CromitePrivateApiExtension& operator=( ++ const CromitePrivateApiExtension&) = delete; ++ ++ ~CromitePrivateApiExtension() override; ++ ++ // content::RenderFrameObserver: ++ void OnDestruct() override; ++ void DidCreateScriptContext(v8::Local v8_context, ++ int32_t world_id) override; ++ ++ private: ++ ++ struct PendingRequest { ++ PendingRequest( ++ v8::Isolate* isolate, ++ v8::Local context, ++ v8::Local resolver); ++ ++ ~PendingRequest(); ++ ++ raw_ptr isolate; ++ v8::Global context; ++ v8::Global promise_resolver; ++ }; ++ ++ explicit CromitePrivateApiExtension(content::RenderFrame* frame); ++ ++ void InjectScript(); ++ ++ void GetComponentData(gin::Arguments* args); ++ void OnGetComponentData(const base::UnguessableToken callback_id, ++ const std::string& label); ++ void StartComponentUpdate(gin::Arguments* args); ++ void SetEnableLogConsole(gin::Arguments* args); ++ ++ std::map> pending_requests_; ++ ++ mojo::AssociatedRemote ++ remote_; ++ base::WeakPtrFactory weak_ptr_factory_{ ++ this}; ++}; ++ ++#endif // CHROME_RENDERER_CROMITE_PRIVATE_API_EXTENSION_H_ +diff --git a/components/component_updater/component_updater_service.h b/components/component_updater/component_updater_service.h +--- a/components/component_updater/component_updater_service.h ++++ b/components/component_updater/component_updater_service.h +@@ -242,6 +242,7 @@ class OnDemandUpdater { + // the update will be applied. The caller can subscribe to component update + // service notifications and provide an optional callback to get the result + // of the call. The function does not implement any cooldown interval. ++ public: + virtual void OnDemandUpdate(const std::string& id, + Priority priority, + Callback callback) = 0; +diff --git a/content/browser/devtools/devtools_http_handler.cc b/content/browser/devtools/devtools_http_handler.cc +--- a/content/browser/devtools/devtools_http_handler.cc ++++ b/content/browser/devtools/devtools_http_handler.cc +@@ -16,6 +16,7 @@ + #include "base/command_line.h" + #include "base/compiler_specific.h" + #include "base/files/file_util.h" ++#include "base/feature_list.h" + #include "base/functional/bind.h" + #include "base/functional/callback_helpers.h" + #include "base/json/json_writer.h" +@@ -46,6 +47,7 @@ + #include "content/public/browser/devtools_manager_delegate.h" + #include "content/public/browser/devtools_socket_factory.h" + #include "content/public/common/content_client.h" ++#include "content/public/common/content_features.h" + #include "content/public/common/content_switches.h" + #include "content/public/common/url_constants.h" + #include "net/base/io_buffer.h" +@@ -587,9 +589,12 @@ void DevToolsHttpHandler::OnJsonRequest( + + if (command == "version") { + base::Value::Dict version; ++ auto browser = GetContentClient()->browser()->GetProduct(); ++ if (base::FeatureList::IsEnabled(features::kEnableCromiteTestSupport)) ++ browser = "Chrome/0.0.0.0"; + version.Set("Protocol-Version", DevToolsAgentHost::GetProtocolVersion()); + version.Set("WebKit-Version", embedder_support::GetWebKitVersion()); +- version.Set("Browser", GetContentClient()->browser()->GetProduct()); ++ version.Set("Browser", browser); + version.Set("User-Agent", GetContentClient()->browser()->GetUserAgent()); + version.Set("V8-Version", V8_VERSION_STRING); + std::string host = info.GetHeaderValue("host"); +diff --git a/content/browser/log_console_message.cc b/content/browser/log_console_message.cc +--- a/content/browser/log_console_message.cc ++++ b/content/browser/log_console_message.cc +@@ -17,10 +17,23 @@ void LogConsoleMessage(blink::mojom::ConsoleMessageLevel log_level, + int32_t line_number, + bool is_builtin_component, + bool is_off_the_record, +- const std::u16string& source_id) { ++ const std::u16string& source_id, ++ bool is_log_console_enabled) { ++ if (base::FeatureList::IsEnabled(features::kEnableCromiteTestSupport)) { ++ LOG(INFO) << "CONSOLE:" ++ << "\"" << message << "\", source: " << source_id << " (" << line_number ++ << ")"; ++ return; ++ } + const int32_t resolved_level = + is_builtin_component ? ConsoleMessageLevelToLogSeverity(log_level) + : ::logging::LOGGING_INFO; ++ if (is_log_console_enabled) { ++ logging::LogMessage("CONSOLE", line_number, resolved_level).stream() ++ << "\"" << message << "\", source: " << source_id << " (" << line_number ++ << ")"; ++ return; ++ } + if (::logging::GetMinLogLevel() > resolved_level) + return; + +diff --git a/content/browser/log_console_message.h b/content/browser/log_console_message.h +--- a/content/browser/log_console_message.h ++++ b/content/browser/log_console_message.h +@@ -19,7 +19,8 @@ void LogConsoleMessage(blink::mojom::ConsoleMessageLevel log_level, + int32_t line_number, + bool is_builtin_component, + bool is_off_the_record, +- const std::u16string& source_id); ++ const std::u16string& source_id, ++ bool is_log_console_enabled = false); + + } // namespace content + +diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc +--- a/content/browser/renderer_host/render_frame_host_impl.cc ++++ b/content/browser/renderer_host/render_frame_host_impl.cc +@@ -319,6 +319,8 @@ + #include "url/origin.h" + #include "url/url_constants.h" + ++#include "chrome/browser/cromite/cromite_private_api_host.h" ++ + #if BUILDFLAG(IS_ANDROID) + #include "content/browser/accessibility/browser_accessibility_manager_android.h" + #include "content/browser/android/content_url_loader_factory.h" +@@ -4726,7 +4728,8 @@ void RenderFrameHostImpl::DidAddMessageToConsole( + if (delegate_->DidAddMessageToConsole(this, log_level, message, line_no, + updated_source_id, + untrusted_stack_trace)) { +- return; ++ if (!base::FeatureList::IsEnabled(features::kEnableCromiteTestSupport)) ++ return; + } + + // Pass through log severity only on builtin components pages to limit console +@@ -4743,8 +4746,14 @@ void RenderFrameHostImpl::DidAddMessageToConsole( + const bool is_off_the_record = + GetSiteInstance()->GetBrowserContext()->IsOffTheRecord(); + ++ bool is_log_console_enabled = false; ++ CromitePrivateApiHost* cromite_data = ++ CromitePrivateApiHost::GetForCurrentDocument(this); ++ if (cromite_data) { ++ is_log_console_enabled = cromite_data->IsLogConsoleEnabled(); ++ } + LogConsoleMessage(log_level, message, line_no, is_builtin_component, +- is_off_the_record, updated_source_id); ++ is_off_the_record, updated_source_id, is_log_console_enabled); + } + + void RenderFrameHostImpl::FrameSizeChanged(const gfx::Size& frame_size) { +diff --git a/cromite_flags/chrome/browser/about_flags_cc/Enable-test-support.inc b/cromite_flags/chrome/browser/about_flags_cc/Enable-test-support.inc +new file mode 100644 +--- /dev/null ++++ b/cromite_flags/chrome/browser/about_flags_cc/Enable-test-support.inc +@@ -0,0 +1,19 @@ ++#ifdef FLAG_SECTION ++ ++ {"enable-cromite-test-support", ++ "Enable support for cromite test", ++ "Setting allows the activation of specific APIs used for cromite testing. " ++ "Do not activate if you do not know what you are doing.", kOsAll, ++ FEATURE_VALUE_TYPE(features::kEnableCromiteTestSupport)}, ++ ++#if BUILDFLAG(IS_ANDROID) ++ ++ {"act-as-webview", ++ "Enable webview support for cromite test", ++ "Setting allows the activation of specific APIs used for cromite testing. " ++ "Do not activate if you do not know what you are doing.", kOsAndroid, ++ FEATURE_VALUE_TYPE(features::kActAsWebView)}, ++ ++#endif ++ ++#endif +diff --git a/cromite_flags/content/public/common/content_features_cc/Enable-test-support.inc b/cromite_flags/content/public/common/content_features_cc/Enable-test-support.inc +new file mode 100644 +--- /dev/null ++++ b/cromite_flags/content/public/common/content_features_cc/Enable-test-support.inc +@@ -0,0 +1,7 @@ ++CROMITE_FEATURE(kEnableCromiteTestSupport, ++ "EnableCromiteTestSupport", ++ base::FEATURE_DISABLED_BY_DEFAULT); ++ ++CROMITE_FEATURE(kActAsWebView, ++ "ActAsWebView", ++ base::FEATURE_DISABLED_BY_DEFAULT); +diff --git a/cromite_flags/content/public/common/content_features_h/Enable-test-support.inc b/cromite_flags/content/public/common/content_features_h/Enable-test-support.inc +new file mode 100644 +--- /dev/null ++++ b/cromite_flags/content/public/common/content_features_h/Enable-test-support.inc +@@ -0,0 +1,2 @@ ++CONTENT_EXPORT BASE_DECLARE_FEATURE(kEnableCromiteTestSupport); ++CONTENT_EXPORT BASE_DECLARE_FEATURE(kActAsWebView); +-- diff --git a/tools/tests/devices-list b/tools/tests/devices-list new file mode 100644 index 00000000..5892725a --- /dev/null +++ b/tools/tests/devices-list @@ -0,0 +1,74 @@ +Samsung Galaxy S23 Ultra +Samsung Galaxy S23 +## !Samsung Galaxy S22 Ultra +Samsung Galaxy S22 Plus +Samsung Galaxy S22 +Samsung Galaxy S21 +Samsung Galaxy S21 Ultra +Samsung Galaxy S21 +Samsung Galaxy S21 Plus +Samsung Galaxy S20 +## Samsung Galaxy S20 Plus +Samsung Galaxy S20 Ultra +## !Samsung Galaxy S24 Ultra +Samsung Galaxy S24 +Samsung Galaxy M52 +Samsung Galaxy M32 +Samsung Galaxy A52 +## Samsung Galaxy Note 20 Ultra +Samsung Galaxy Note 20 +## Samsung Galaxy A51 +# Samsung Galaxy A11 (arm) +## Samsung Galaxy S10e +## Samsung Galaxy S10 Plus +## Samsung Galaxy S10 +## Samsung Galaxy Note 10 +# Samsung Galaxy A10 (arm) +## Samsung Galaxy Note 9 +## Samsung Galaxy S9 +## Samsung Galaxy S8 +## Samsung Galaxy S7 + +Google Pixel 8 Pro +Google Pixel 8 +Google Pixel 7 Pro +Google Pixel 7 +Google Pixel 6 Pro +# Google Pixel 6 Pro A12 +Google Pixel 6 +Google Pixel 5 +# Google Pixel 5 A11 +# Google Pixel 4 (not supported) +Google Pixel 4 XL +# Google Pixel 4 A10 + +OnePlus 11R +OnePlus 9 +OnePlus 8 +# OnePlus 7T +##OnePlus 7 + +Xiaomi Redmi Note 11 +Xiaomi Redmi Note 9 +## Xiaomi Redmi Note 8 + +Vivo Y21 +Vivo V21 +Vivo Y50 + +Oppo Reno 6 +Oppo A96 +Oppo Reno 3 Pro + +Motorola Moto G71 5G +Motorola Moto G9 Play +# Motorola Moto G7 Play (arm) + +Huawei P30 + +# tablet +Samsung Galaxy Tab S9 +Samsung Galaxy Tab S8 +Samsung Galaxy Tab S7 +## Samsung Galaxy Tab S6 +## Samsung Galaxy Tab S5e diff --git a/tools/tests/package.json b/tools/tests/package.json new file mode 100644 index 00000000..127ea835 --- /dev/null +++ b/tools/tests/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "global-agent": "^3.0.0", + "webdriverio": "^8.39.0" + } +} diff --git a/tools/tests/start-creepjs.sh b/tools/tests/start-creepjs.sh new file mode 100644 index 00000000..d19b9e33 --- /dev/null +++ b/tools/tests/start-creepjs.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +FILE_LIST=devices-list + +cat $FILE_LIST | while read current +do + if [[ $current =~ ^#.* ]]; then + : # skip comments + + elif [ ! -z "$current" ]; then + + echo $current + node test_webdriver.js bs "$current" cromite + fi + +done diff --git a/tools/tests/test_webdriver.js b/tools/tests/test_webdriver.js new file mode 100644 index 00000000..a85984bb --- /dev/null +++ b/tools/tests/test_webdriver.js @@ -0,0 +1,337 @@ +const { remote } = require('webdriverio'); +const { bootstrap } = require('global-agent'); +const path = require('node:path'); +const fs = require('node:fs/promises'); +const { appendFile } = require('node:fs'); +const { count } = require('node:console'); + +// export GLOBAL_AGENT_HTTP_PROXY=$HTTPS_PROXY +bootstrap(); + +let driver; + +const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) +const click = async el => { + try { + const x = await driver.$(el); + await x.click(); + return true; + } + catch { + return false; + } +} +const clickByUIautomator = async el => { + const x = await driver.$("-android uiautomator:" + el); + await x.click(); + return x; +} +const pressBack = async times => { + for (let t = 0; t < times; t++) + await driver.executeScript("mobile: pressKey", [{ "keycode": 4 }]); // BACK +} +const downFor = async times => { + for (let t = 0; t < times; t++) + await driver.executeScript("mobile: pressKey", [{ "keycode": 20 }]); // DOWN +} +const goto = async (selectorKey, url) => { + let el4 = await clickByUIautomator( + 'new UiSelector().resourceId("' + selectorKey + ':id/url_bar")'); + await el4.addValue(url); + await clickByUIautomator( + 'new UiSelector().resourceId("' + selectorKey + ':id/line_1")'); +} + +function MakePath(config, file) { + return path.join( + config.outputFolder, + config.deviceName.replaceAll(' ', '_'), + file + ) +} + +// driver.addCommand('sendCommandFromWebSocket', command('POST', '/session/:sessionId/chromium/send_command', { +// command: 'sendCommandFromWebSocket', +// description: 'sendCommandFromWebSocket chromium command', +// ref: 'https://vendor.com/commands/#myNewCommand', +// // variables: [{ +// // name: 'someId', +// // description: 'some id to something' +// // }], +// parameters: [{ +// name: 'method', +// type: 'string', +// description: 'a valid parameter', +// required: true +// }, { +// name: 'params', +// type: 'object', +// description: 'a valid parameter', +// required: true +// }, { +// name: 'id', +// type: 'number', +// description: 'a valid parameter', +// required: true +// }] +// })) +// await driver.sendCommandFromWebSocket("lll", {}, -1); + +async function testApp(isLocal, config) { + console.log("Starting"); + + const logDirectory = MakePath(config, ""); + try { + await fs.mkdir(logDirectory, { recursive: true }); + } catch { + if (!config.skipCheckDirectory) { + console.log("Directory " + logDirectory + " already exists."); + process.exit(0); + } + } + + let selectorKey = "org.cromite.cromite"; + let isChromium = false; + if (config.appName == "chromium") { + selectorKey = "org.chromium.chrome"; //.stable"; //"org.chromium.chrome"; + isChromium = true; + } + + const capabilities_local = { + platformName: 'android', + 'appium:automationName': 'UiAutomator2', + 'appium:appPackage': selectorKey, + 'appium:appActivity': 'com.google.android.apps.chrome.Main', + + 'appium:showChromedriverLog': true, + //'appium:recreateChromeDriverSessions': true, + + 'appium:chromeOptions': { + 'androidDeviceSocket': 'chrome_devtools_remote', // cat /proc/net/unix + 'androidExecName': 'unusedbutimportant', + }, + }; + + const capabilities_bs = { + "platformName": "android", + + "appium:deviceName": config.deviceName, + "appium:app": config.appName, + + 'bstack:options': { + "appiumVersion": "2.4.1", + "disableAnimations": "true", + "userName": config.BROWSERSTACK_USERNAME, + "accessKey": config.BROWSERSTACK_ACCESS_KEY, + }, + + // Set other BrowserStack capabilities + // 'project' : 'First NodeJS project', + // 'build' : 'Node Android', + // 'name': 'first_test', + // 'unicodeKeyboard': 'true', + // 'resetKeyboard': 'true', + }; + + if (!isLocal) { + driver = await remote({ + capabilities: capabilities_bs, + hostname: 'hub-cloud.browserstack.com', + path: '/wd/hub', + protocol: 'https', + port: 443, + strictSSL: true, + logLevel: 'trace', + }); + } else { + driver = await remote({ + capabilities: capabilities_local, + hostname: '127.0.0.1', + port: 4723, + }); + } + + try { + const info = await driver.executeScript("mobile: deviceInfo", [{}]); + console.log(info); + + await fs.writeFile(MakePath(config, "info.json"), JSON.stringify(info)); + + // cromite + const terms_accept_present = await driver.findElement("id", selectorKey + ":id/terms_accept"); + console.log(!terms_accept_present.error); + if (!terms_accept_present.error) { + await click("id:" + selectorKey + ":id/terms_accept"); + + await clickByUIautomator( + 'new UiSelector().resourceId("' + selectorKey + ':id/default_search_engine_dialog_options")' + + '.childSelector(new UiSelector().className("android.widget.RadioButton"))'); + + await click("id:" + selectorKey + ":id/button_primary"); + } + + // chromium + if (isChromium) await sleep(4000); + const signin_fre_dismiss_button = await await driver.findElement("id", selectorKey + ":id/signin_fre_dismiss_button"); + if (!signin_fre_dismiss_button.error) { + await click("id:" + selectorKey + ":id/signin_fre_dismiss_button"); + } + else { + const signin_fre_continue_button = await await driver.findElement("id", selectorKey + ":id/signin_fre_continue_button"); + if (!signin_fre_continue_button.error) { + await click("id:" + selectorKey + ":id/signin_fre_continue_button"); + } + } + + // click menu + await clickByUIautomator( + 'new UiSelector().resourceId("' + selectorKey + ':id/menu_button")'); + await sleep(1000); + + // click on preferences + if (!await click("id:" + selectorKey + ":id/preferences_id")) { + await driver.executeScript("mobile:flingGesture", + [{ "left": 400, "top": 100, "width": 200, "height": 200, "direction": "down" }]); + await click("id:" + selectorKey + ":id/preferences_id"); + } + await sleep(1000); + + // disable adblock + await clickByUIautomator( + 'new UiSelector().text("Adblock Plus settings")'); + await sleep(1000); + await clickByUIautomator( + 'new UiSelector().text("Enable Adblock Plus")'); + + // go back on preferences + await pressBack(1); + await sleep(1000); + + // move preferences layout + if (!await driver.executeScript("mobile:flingGesture", + [{ "left": 100, "top": 100, "width": 200, "height": 400, "direction": "down" }])) { + await downFor(20); + } + await sleep(1000); + + // click on developer options + await clickByUIautomator( + 'new UiSelector().text("Developer options")'); + + // enable cromite test pref + await clickByUIautomator( + 'new UiSelector().text("Enable support for cromite test")'); + + // enable webview mode (only if chromium or is local) + if (!isLocal && !isChromium) { + await clickByUIautomator( + 'new UiSelector().text("Enable webview support for cromite test")'); + } + + // enable pixel perfect mode + await clickByUIautomator( + 'new UiSelector().text("Pixel Perfect Mode")'); + + // restart cromite + await clickByUIautomator( + 'new UiSelector().resourceId("' + selectorKey + ':id/snackbar_button")'); + await sleep(2000); + + // wait for restart + let count = 0; + while ((await driver.findElement("id", selectorKey + ":id/url_bar")).error) { + await sleep(1000); + count++; + if (count > 10) break; + } + + // open chrome://version + await goto(selectorKey, "chrome://version"); + + // change the context + let contexts = await driver.getContexts(); + console.log(contexts); + + if (!isLocal && !isChromium) { + await driver.switchContext('WEBVIEW_' + selectorKey); + } else { + await driver.switchContext('WEBVIEW_chrome'); + } + + contexts = await driver.getContexts(); + console.log(contexts); + + // start update of cromite fonts pack + await driver.executeScript( + 'await window.cromite.startComponentUpdate("gcmjkmgdlgnkkcocmoeiminaijmmjnii")', []); + + count = 0; + for (; ;) { + let version = await driver.executeScript( + 'return JSON.parse(await window.cromite.getComponentData("gcmjkmgdlgnkkcocmoeiminaijmmjnii")).version;', []); + console.log(version); + if (version != "0.0.0.0") break; + + count++; + if (count > 20) break; + await sleep(10000); + } + + // open creepjs + await driver.executeScript('window.location.href="https://abrahamjuliot.github.io/creepjs"', []); + if (!isLocal) + await sleep(10000); + else + await sleep(30000); + + // get creepjs json + let creep = await driver.executeScript("return JSON.stringify(window.Creep);", []); + await fs.writeFile(MakePath(config, "creep.json"), creep); + + let fingerprint = await driver.executeScript("return JSON.stringify(window.Fingerprint);", []); + await fs.writeFile(MakePath(config, "fingerprint.json"), fingerprint); + } finally { + await driver.deleteSession(); + driver = undefined; + } +} + +if (process.argv.length <= 3) { + console.error('Args:'); + console.error(' [local|bs] running environment'); + console.error(' [devicename] device'); + console.error(' [app name] bs device name'); + process.exit(1); +} + +if (process.env.BROWSERSTACK_OUTPUT_FOLDER === undefined) { + console.error('set BROWSERSTACK_OUTPUT_FOLDER env variable'); + process.exit(1); +} + +let isLocal = process.argv[2] === 'local'; +if (!isLocal) { + if (process.env.BROWSERSTACK_USERNAME === undefined) { + console.error('set BROWSERSTACK_USERNAME env variable'); + process.exit(1); + } + if (process.env.BROWSERSTACK_ACCESS_KEY === undefined) { + console.error('set BROWSERSTACK_ACCESS_KEY env variable'); + process.exit(1); + } + testApp(/*isLocal*/ false, { + deviceName: process.argv[3], + appName: process.argv[4], + BROWSERSTACK_USERNAME: process.env.BROWSERSTACK_USERNAME, + BROWSERSTACK_ACCESS_KEY: process.env.BROWSERSTACK_ACCESS_KEY, + outputFolder: process.env.BROWSERSTACK_OUTPUT_FOLDER, + skipCheckDirectory: false, + }); +} else { + testApp(/*isLocal*/ true, { + deviceName: process.argv[3], + appName: process.argv[4], + outputFolder: process.env.BROWSERSTACK_OUTPUT_FOLDER, + skipCheckDirectory: true, + }); +} diff --git a/tools/tests/upload_bs_app.sh b/tools/tests/upload_bs_app.sh new file mode 100644 index 00000000..c03ea4e2 --- /dev/null +++ b/tools/tests/upload_bs_app.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +APP_NAME=$1 +APP_APK=$2 + +echo Removing $APP_NAME +for id in $(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X GET "https://api-cloud.browserstack.com/app-automate/recent_apps" | \ + jq --arg APP_NAME "$APP_NAME" -rc '.[] | select (.custom_id == $APP_NAME) | .app_id') ; do + echo ..Removing $id + curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X DELETE "https://api-cloud.browserstack.com/app-automate/app/delete/${id}" +done + +echo Uploading $APP_NAME from $APP_APK +curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ + -F "file=@$APP_APK" \ + -F "custom_id=$APP_NAME"