The test intermittently fails with "deepQuery is not defined" because InjectHelpers injects the helper function into the bubble's WebContents before the WebUI navigation has committed. WaitForLoadStop returns immediately when no navigation is pending (e.g., the WebContents is still at about:blank), so deepQuery is injected into the pre-navigation context, then lost when chrome://email-aliases.panel/ loads. Wait for the WebContents to commit a non-empty, non-about:blank URL before calling WaitForLoadStop and injecting JavaScript. Fix brave/brave-browser#55000
510 lines
18 KiB
C++
510 lines
18 KiB
C++
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "base/check_deref.h"
|
|
#include "base/functional/callback_helpers.h"
|
|
#include "base/path_service.h"
|
|
#include "base/test/bind.h"
|
|
#include "base/test/run_until.h"
|
|
#include "base/time/time.h"
|
|
#include "brave/browser/brave_account/brave_account_service_factory.h"
|
|
#include "brave/browser/email_aliases/email_aliases_service_factory.h"
|
|
#include "brave/browser/ui/email_aliases/email_aliases_controller.h"
|
|
#include "brave/browser/ui/webui/brave_settings_ui.h"
|
|
#include "brave/components/brave_account/features.h"
|
|
#include "brave/components/brave_account/mock_brave_account_authentication.h"
|
|
#include "brave/components/constants/brave_paths.h"
|
|
#include "brave/components/email_aliases/email_aliases_api.h"
|
|
#include "brave/components/email_aliases/email_aliases_service.h"
|
|
#include "brave/components/email_aliases/features.h"
|
|
#include "brave/components/email_aliases/test_utils.h"
|
|
#include "chrome/browser/profiles/profile.h"
|
|
#include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
|
|
#include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h"
|
|
#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
|
|
#include "chrome/browser/ui/browser.h"
|
|
#include "chrome/browser/ui/browser_commands.h"
|
|
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
|
|
#include "chrome/browser/ui/views/bubble/webui_bubble_manager.h"
|
|
#include "chrome/test/base/in_process_browser_test.h"
|
|
#include "chrome/test/base/ui_test_utils.h"
|
|
#include "components/grit/brave_components_strings.h"
|
|
#include "components/user_prefs/user_prefs.h"
|
|
#include "content/public/browser/render_frame_host.h"
|
|
#include "content/public/browser/render_view_host.h"
|
|
#include "content/public/browser/render_widget_host.h"
|
|
#include "content/public/browser/storage_partition.h"
|
|
#include "content/public/browser/web_contents.h"
|
|
#include "content/public/test/browser_test.h"
|
|
#include "content/public/test/browser_test_utils.h"
|
|
#include "content/public/test/content_mock_cert_verifier.h"
|
|
#include "content/public/test/test_navigation_observer.h"
|
|
#include "services/network/public/cpp/network_switches.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "ui/base/l10n/l10n_util.h"
|
|
|
|
namespace email_aliases {
|
|
|
|
namespace {
|
|
constexpr char kSuccessEmail[] = "success@domain.com";
|
|
|
|
std::unique_ptr<net::test_server::HttpResponse> ManageHandler(
|
|
const net::test_server::HttpRequest& request) {
|
|
if (!request.GetURL().has_path() ||
|
|
!request.GetURL().path().starts_with("/manage")) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
|
|
|
|
if (request.method == net::test_server::HttpMethod::METHOD_GET) {
|
|
auto make_entry = [](const std::string& alias) {
|
|
email_aliases::AliasListEntry le;
|
|
le.alias = alias;
|
|
le.email = kSuccessEmail;
|
|
le.status = "active";
|
|
return le;
|
|
};
|
|
email_aliases::AliasListResponse list;
|
|
list.result.push_back(make_entry("first@alias.com"));
|
|
list.result.push_back(make_entry("second@alias.com"));
|
|
list.result.push_back(make_entry("third@alias.com"));
|
|
|
|
response->set_code(net::HTTP_OK);
|
|
response->set_content_type("application/json");
|
|
response->set_content(*base::WriteJson(list.ToValue()));
|
|
} else if (request.method == net::test_server::HttpMethod::METHOD_POST) {
|
|
response->set_code(net::HTTP_OK);
|
|
response->set_content_type("application/json");
|
|
email_aliases::GenerateAliasResponse generate;
|
|
generate.alias = "new@alias.com";
|
|
generate.message = "created";
|
|
response->set_content(*base::WriteJson(generate.ToValue()));
|
|
} else if (request.method == net::test_server::HttpMethod::METHOD_PUT) {
|
|
response->set_code(net::HTTP_OK);
|
|
response->set_content_type("application/json");
|
|
email_aliases::AliasEditedResponse save;
|
|
save.message = "updated";
|
|
response->set_content(*base::WriteJson(save.ToValue()));
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class EmailAliasesBrowserTestBase : public InProcessBrowserTest {
|
|
public:
|
|
EmailAliasesBrowserTestBase() {
|
|
BraveSettingsUI::ShouldExposeElementsForTesting() = true;
|
|
}
|
|
|
|
~EmailAliasesBrowserTestBase() override {
|
|
BraveSettingsUI::ShouldExposeElementsForTesting() = false;
|
|
}
|
|
|
|
void SetUpBrowserContextKeyedServices(
|
|
content::BrowserContext* context) override {
|
|
if (features::IsEmailAliasesEnabled()) {
|
|
EmailAliasesServiceFactory::GetInstance()->SetTestingFactory(
|
|
context,
|
|
base::BindLambdaForTesting([&](content::BrowserContext* context)
|
|
-> std::unique_ptr<KeyedService> {
|
|
return std::make_unique<EmailAliasesService>(
|
|
brave_account_auth_.BindAndGetRemote(),
|
|
context->GetDefaultStoragePartition()
|
|
->GetURLLoaderFactoryForBrowserProcess(),
|
|
CHECK_DEREF(user_prefs::UserPrefs::Get(context)));
|
|
}));
|
|
}
|
|
}
|
|
|
|
void SetUpOnMainThread() override {
|
|
https_server_.RegisterRequestHandler(base::BindRepeating(&ManageHandler));
|
|
https_server_.ServeFilesFromDirectory(
|
|
base::PathService::CheckedGet(brave::DIR_TEST_DATA));
|
|
|
|
https_server_.StartAcceptingConnections();
|
|
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
|
|
|
|
ON_CALL(GetBraveAccountAuth(),
|
|
GetServiceToken(brave_account::mojom::Service::kEmailAliases,
|
|
testing::_))
|
|
.WillByDefault([](auto service, auto callback) {
|
|
auto token = brave_account::mojom::GetServiceTokenResult::New();
|
|
token->serviceToken = "email-aliases-token";
|
|
std::move(callback).Run(base::ok(std::move(token)));
|
|
});
|
|
}
|
|
|
|
void SetUp() override {
|
|
ASSERT_TRUE(https_server_.InitializeAndListen());
|
|
InProcessBrowserTest::SetUp();
|
|
}
|
|
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
InProcessBrowserTest::SetUpCommandLine(command_line);
|
|
mock_cert_verifier_.SetUpCommandLine(command_line);
|
|
command_line->AppendSwitchASCII(
|
|
network::switches::kHostResolverRules,
|
|
"MAP * " + https_server_.host_port_pair().ToString());
|
|
}
|
|
|
|
void SetUpInProcessBrowserTestFixture() override {
|
|
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
|
|
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
|
|
}
|
|
|
|
void TearDownInProcessBrowserTestFixture() override {
|
|
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
|
|
InProcessBrowserTest::TearDownInProcessBrowserTestFixture();
|
|
}
|
|
|
|
content::WebContents* ActiveWebContents() {
|
|
return browser()->tab_strip_model()->GetActiveWebContents();
|
|
}
|
|
|
|
content::WebContents* Navigate(const GURL& url) {
|
|
ui_test_utils::NavigateToURLWithDisposition(
|
|
browser(), url, WindowOpenDisposition::CURRENT_TAB,
|
|
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
|
|
return ActiveWebContents();
|
|
}
|
|
|
|
void InjectHelpers(content::WebContents* contents) {
|
|
ASSERT_TRUE(base::test::RunUntil([&]() {
|
|
return !contents->GetLastCommittedURL().is_empty() &&
|
|
!contents->GetLastCommittedURL().IsAboutBlank();
|
|
}));
|
|
ASSERT_TRUE(content::WaitForLoadStop(contents));
|
|
constexpr char kDeepQuery[] = R"js(
|
|
function deepQuery(selector) {
|
|
const query = (root, selector) =>{
|
|
const e = root.querySelector(selector);
|
|
if (e) return e;
|
|
for (const el of root.querySelectorAll('*')) {
|
|
if (!el.shadowRoot) continue;
|
|
const found = query(el.shadowRoot, selector);
|
|
if (found) return found;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return query(document, selector);
|
|
};
|
|
)js";
|
|
|
|
ASSERT_TRUE(content::ExecJs(contents, kDeepQuery));
|
|
}
|
|
|
|
void Wait(const std::string& id, content::WebContents* contents = nullptr) {
|
|
constexpr const char kScript[] = R"js(
|
|
(async () => {
|
|
let waiter = () => {
|
|
return !deepQuery($1)
|
|
};
|
|
while (waiter()) {
|
|
await new Promise(r => setTimeout(r, 10));
|
|
}
|
|
return true;
|
|
})();
|
|
)js";
|
|
|
|
ASSERT_EQ(true, content::EvalJs((contents ? contents : ActiveWebContents()),
|
|
content::JsReplace(kScript, id)));
|
|
}
|
|
|
|
void WaitDisappear(const std::string& selector,
|
|
content::WebContents* contents = nullptr) {
|
|
constexpr const char kScript[] = R"js(
|
|
(async () => {
|
|
let waiter = () => {
|
|
return deepQuery($1)
|
|
};
|
|
while (waiter()) {
|
|
await new Promise(r => setTimeout(r, 10));
|
|
}
|
|
return true;
|
|
})();
|
|
)js";
|
|
|
|
ASSERT_EQ(true, content::EvalJs((contents ? contents : ActiveWebContents()),
|
|
content::JsReplace(kScript, selector)));
|
|
}
|
|
|
|
void SetText(const std::string& id,
|
|
const std::string& text,
|
|
content::WebContents* contents = nullptr) {
|
|
constexpr char kSetText[] = R"js(
|
|
(() => {
|
|
const element = deepQuery($1);
|
|
element.value = $2;
|
|
element.dispatchEvent(new Event('input', {bubbles: true}));
|
|
element.dispatchEvent(new Event('change', {bubbles: true}));
|
|
return true;
|
|
})();
|
|
)js";
|
|
ASSERT_EQ(true, content::EvalJs((contents ? contents : ActiveWebContents()),
|
|
content::JsReplace(kSetText, id, text)));
|
|
}
|
|
|
|
std::string GetText(const std::string& id) {
|
|
constexpr char kGetText[] = R"js( deepQuery($1).value )js";
|
|
return content::EvalJs(ActiveWebContents(),
|
|
content::JsReplace(kGetText, id))
|
|
.ExtractString();
|
|
}
|
|
|
|
bool AwaitText(const std::string& id, const std::string& expected_text) {
|
|
constexpr char kAwaitText[] = R"js(
|
|
(async () => {
|
|
let waiter = () => {
|
|
return deepQuery($1).value !== $2
|
|
};
|
|
while (waiter()) {
|
|
await new Promise(r => setTimeout(r, 10));
|
|
}
|
|
return true;
|
|
})()
|
|
)js";
|
|
return content::EvalJs(ActiveWebContents(),
|
|
content::JsReplace(kAwaitText, id, expected_text))
|
|
.ExtractBool();
|
|
}
|
|
|
|
void Click(const std::string& id, content::WebContents* contents = nullptr) {
|
|
constexpr const char kClick[] = R"js(
|
|
(async () => {
|
|
let waiter = () => {
|
|
return !deepQuery($1)
|
|
};
|
|
while (waiter()) {
|
|
await new Promise(r => setTimeout(r, 10));
|
|
}
|
|
return deepQuery($1).onClick();
|
|
})();
|
|
)js";
|
|
auto ignore = content::ExecJs((contents ? contents : ActiveWebContents()),
|
|
content::JsReplace(kClick, id));
|
|
}
|
|
|
|
EmailAliasesService* email_aliases_service() {
|
|
return EmailAliasesServiceFactory::GetServiceForProfile(
|
|
browser()->profile());
|
|
}
|
|
|
|
void RunContextMenuOn(const std::string& element_id) {
|
|
const int x =
|
|
content::EvalJs(ActiveWebContents(),
|
|
content::JsReplace("getElementX($1)", element_id))
|
|
.ExtractInt();
|
|
const int y =
|
|
content::EvalJs(ActiveWebContents(),
|
|
content::JsReplace("getElementY($1)", element_id))
|
|
.ExtractInt();
|
|
|
|
ASSERT_TRUE(content::ExecJs(
|
|
ActiveWebContents(),
|
|
content::JsReplace("document.getElementById($1).focus()", element_id)));
|
|
|
|
ActiveWebContents()
|
|
->GetPrimaryMainFrame()
|
|
->GetRenderViewHost()
|
|
->GetWidget()
|
|
->ShowContextMenuAtPoint(gfx::Point(x, y),
|
|
ui::mojom::MenuSourceType::kMouse);
|
|
}
|
|
|
|
testing::NiceMock<brave_account::MockBraveAccountAuthentication>&
|
|
GetBraveAccountAuth() {
|
|
return brave_account_auth_;
|
|
}
|
|
|
|
private:
|
|
content::ContentMockCertVerifier mock_cert_verifier_;
|
|
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
|
|
testing::NiceMock<brave_account::MockBraveAccountAuthentication>
|
|
brave_account_auth_;
|
|
};
|
|
|
|
class EmailAliasesBrowserTest : public EmailAliasesBrowserTestBase {
|
|
public:
|
|
EmailAliasesBrowserTest() {
|
|
feature_list_.InitWithFeatures(
|
|
{{email_aliases::features::kEmailAliases},
|
|
brave_account::features::BraveAccountFeatureForTesting()},
|
|
{});
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList feature_list_;
|
|
};
|
|
|
|
class EmailAliasesBrowserNoFeatureTest : public EmailAliasesBrowserTestBase {
|
|
public:
|
|
EmailAliasesBrowserNoFeatureTest() {
|
|
feature_list_.InitAndDisableFeature(email_aliases::features::kEmailAliases);
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList feature_list_;
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(EmailAliasesBrowserNoFeatureTest, NoContextMenuItem) {
|
|
Navigate(GURL("https://a.test/email_aliases/inputs.html"));
|
|
|
|
ContextMenuWaiter menu_waiter(IDC_NEW_EMAIL_ALIAS);
|
|
RunContextMenuOn("type-email");
|
|
menu_waiter.WaitForMenuOpenAndClose();
|
|
EXPECT_FALSE(std::ranges::contains(menu_waiter.GetCapturedEnabledCommandIds(),
|
|
IDC_NEW_EMAIL_ALIAS));
|
|
EXPECT_FALSE(menu_waiter.IsCommandExecuted());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(EmailAliasesBrowserTest,
|
|
NoContextMenuItemOnNonsuitableField) {
|
|
Navigate(GURL("https://a.test/email_aliases/inputs.html"));
|
|
|
|
ContextMenuWaiter menu_waiter(IDC_NEW_EMAIL_ALIAS);
|
|
RunContextMenuOn("type-url");
|
|
menu_waiter.WaitForMenuOpenAndClose();
|
|
EXPECT_FALSE(std::ranges::contains(menu_waiter.GetCapturedEnabledCommandIds(),
|
|
IDC_NEW_EMAIL_ALIAS));
|
|
EXPECT_FALSE(menu_waiter.IsCommandExecuted());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(EmailAliasesBrowserTest, ContextMenuNotAuthorized) {
|
|
const GURL settings_page("chrome://settings/email-aliases");
|
|
|
|
Navigate(GURL("https://a.test/email_aliases/inputs.html"));
|
|
|
|
content::TestNavigationObserver waiter(settings_page);
|
|
waiter.StartWatchingNewWebContents();
|
|
|
|
ContextMenuWaiter menu_waiter(IDC_NEW_EMAIL_ALIAS);
|
|
RunContextMenuOn("type-email");
|
|
menu_waiter.WaitForMenuOpenAndClose();
|
|
waiter.WaitForNavigationFinished();
|
|
|
|
EXPECT_EQ(ActiveWebContents()->GetLastCommittedURL(), settings_page);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(EmailAliasesBrowserTest, ContextMenuAuthorized) {
|
|
auto* service = email_aliases_service();
|
|
{
|
|
auto initilized = test::AuthStateObserver::Setup(service, true);
|
|
}
|
|
service->GetAuth()->SetAuthEmailForTesting(kSuccessEmail);
|
|
Navigate(GURL("https://a.test/email_aliases/inputs.html"));
|
|
InjectHelpers(ActiveWebContents());
|
|
|
|
EmailAliasesController::DisableAutoCloseBubbleForTesting(true);
|
|
auto* email_aliases_controller =
|
|
browser()->GetFeatures().email_aliases_controller();
|
|
|
|
EXPECT_EQ("", GetText("#type-email"));
|
|
|
|
ContextMenuWaiter menu_waiter(IDC_NEW_EMAIL_ALIAS);
|
|
RunContextMenuOn("type-email");
|
|
menu_waiter.WaitForMenuOpenAndClose();
|
|
// Wait for bubble.
|
|
ASSERT_TRUE(base::test::RunUntil(
|
|
[&]() { return !!email_aliases_controller->GetBubbleForTesting(); }));
|
|
|
|
auto* bubble = email_aliases_controller->GetBubbleForTesting();
|
|
InjectHelpers(bubble);
|
|
Wait("#create-alias-button:not([isDisabled=\"true\"])", bubble);
|
|
Click("#create-alias-button:not([isDisabled=\"true\"])", bubble);
|
|
|
|
// Wait for bubble close
|
|
ASSERT_TRUE(base::test::RunUntil(
|
|
[&]() { return !email_aliases_controller->GetBubbleForTesting(); }));
|
|
|
|
EXPECT_TRUE(AwaitText("#type-email", "new@alias.com"));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(EmailAliasesBrowserTest, ContextMenuAuthorizedManage) {
|
|
email_aliases_service()->GetAuth()->SetAuthEmailForTesting(kSuccessEmail);
|
|
|
|
const GURL settings_page("chrome://settings/email-aliases");
|
|
|
|
Navigate(GURL("https://a.test/email_aliases/inputs.html"));
|
|
|
|
content::TestNavigationObserver waiter(settings_page);
|
|
waiter.StartWatchingNewWebContents();
|
|
|
|
EmailAliasesController::DisableAutoCloseBubbleForTesting(true);
|
|
auto* email_aliases_controller =
|
|
browser()->GetFeatures().email_aliases_controller();
|
|
|
|
ContextMenuWaiter menu_waiter(IDC_NEW_EMAIL_ALIAS);
|
|
RunContextMenuOn("type-email");
|
|
menu_waiter.WaitForMenuOpenAndClose();
|
|
// Wait for bubble.
|
|
ASSERT_TRUE(base::test::RunUntil(
|
|
[&]() { return !!email_aliases_controller->GetBubbleForTesting(); }));
|
|
|
|
auto* bubble = email_aliases_controller->GetBubbleForTesting();
|
|
InjectHelpers(bubble);
|
|
Wait("#manage-button", bubble);
|
|
Click("#manage-button", bubble);
|
|
|
|
waiter.WaitForNavigationFinished();
|
|
|
|
EXPECT_EQ(ActiveWebContents()->GetLastCommittedURL(), settings_page);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(EmailAliasesBrowserTest, ContextMenuAuthorizedCancel) {
|
|
email_aliases_service()->GetAuth()->SetAuthEmailForTesting(kSuccessEmail);
|
|
|
|
Navigate(GURL("https://a.test/email_aliases/inputs.html"));
|
|
InjectHelpers(ActiveWebContents());
|
|
|
|
EmailAliasesController::DisableAutoCloseBubbleForTesting(true);
|
|
auto* email_aliases_controller =
|
|
browser()->GetFeatures().email_aliases_controller();
|
|
|
|
ContextMenuWaiter menu_waiter(IDC_NEW_EMAIL_ALIAS);
|
|
RunContextMenuOn("type-email");
|
|
menu_waiter.WaitForMenuOpenAndClose();
|
|
// Wait for bubble.
|
|
ASSERT_TRUE(base::test::RunUntil(
|
|
[&]() { return !!email_aliases_controller->GetBubbleForTesting(); }));
|
|
|
|
auto* bubble = email_aliases_controller->GetBubbleForTesting();
|
|
InjectHelpers(bubble);
|
|
Wait("#cancel-button", bubble);
|
|
Click("#cancel-button", bubble);
|
|
|
|
// Wait for bubble close
|
|
ASSERT_TRUE(base::test::RunUntil(
|
|
[&]() { return !email_aliases_controller->GetBubbleForTesting(); }));
|
|
|
|
EXPECT_TRUE(AwaitText("#type-email", "")); // text not changed
|
|
}
|
|
IN_PROC_BROWSER_TEST_F(EmailAliasesBrowserTest, LogInLogOut) {
|
|
// Prepare auth token
|
|
email_aliases_service()->GetAuth()->SetAuthEmailForTesting(kSuccessEmail);
|
|
|
|
// Settings in logged-in state
|
|
Navigate(GURL("chrome://settings/email-aliases"));
|
|
InjectHelpers(ActiveWebContents());
|
|
Wait("#create-new-item-button");
|
|
|
|
// Reset token
|
|
email_aliases_service()->GetAuth()->SetAuthEmailForTesting("");
|
|
WaitDisappear("#create-new-item-button"); // Settings in sing-in state.
|
|
|
|
// Logged-in again
|
|
email_aliases_service()->GetAuth()->SetAuthEmailForTesting(kSuccessEmail);
|
|
Wait("#create-new-item-button"); // Logged-in state.
|
|
}
|
|
|
|
} // namespace email_aliases
|