blob: 5779e817b0e542bf78bd7e33454b2b1025e38f9f [file]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/interaction/interactive_browser_test.h"
#include <sstream>
#include <tuple>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/test/base/test_switches.h"
#include "components/constrained_window/constrained_window_views.h"
#include "content/public/test/browser_test.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/expect_call_in_scope.h"
#include "ui/base/interaction/interaction_sequence.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/test/ui_controls.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/window/dialog_delegate.h"
#include "url/gurl.h"
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsId);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContents2Id);
constexpr char kDocumentWithNamedElement[] = "/select.html";
constexpr char kDocumentWithLinks[] = "/links.html";
constexpr char kDocumentWithClickDetection[] = "/click.html";
constexpr char kScrollableDocument[] =
"/scroll/scrollable_page_with_content.html";
} // namespace
class InteractiveBrowserTestBrowsertest : public InteractiveBrowserTest {
public:
InteractiveBrowserTestBrowsertest() = default;
~InteractiveBrowserTestBrowsertest() override = default;
void SetUp() override {
set_open_about_blank_on_browser_launch(true);
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
InteractiveBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
InteractiveBrowserTest::SetUpOnMainThread();
embedded_test_server()->StartAcceptingConnections();
}
void TearDownOnMainThread() override {
EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
InteractiveBrowserTest::TearDownOnMainThread();
}
};
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
EnsurePresentNotPresent) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
EnsurePresent(kWebContentsId, DeepQuery({"#select"})),
EnsureNotPresent(kWebContentsId, DeepQuery{"#doesNotExist"}));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
EnsureNotPresent_Fails) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
EnsureNotPresent(kWebContentsId, DeepQuery{"#select"})));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest, EnsurePresent_Fails) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
EnsurePresent(kWebContentsId, DeepQuery{"#doesNotExist"})));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest, ExecuteJs) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
ExecuteJs(kWebContentsId, "() => { window.value = 1; }"),
WithElement(kWebContentsId, base::BindOnce([](ui::TrackedElement* el) {
const auto result = AsInstrumentedWebContents(el)->Evaluate(
"() => window.value");
EXPECT_EQ(1, result.GetInt());
})));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
ExecuteJsFireAndForget) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
ExecuteJs(kWebContentsId, "() => { window.value = 1; }",
ExecuteJsMode::kFireAndForget),
WithElement(kWebContentsId, base::BindOnce([](ui::TrackedElement* el) {
const auto result = AsInstrumentedWebContents(el)->Evaluate(
"() => window.value");
EXPECT_EQ(1, result.GetInt());
})));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
ExecuteJsFailsOnThrow) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
ExecuteJs(kWebContentsId, "() => { throw new Error('an error'); }")));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest, CheckJsResult) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
const std::string str("a string");
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
ExecuteJs(kWebContentsId,
R"(() => {
window.intValue = 1;
window.boolValue = true;
window.doubleValue = 2.0;
window.stringValue = 'a string';
})"),
CheckJsResult(kWebContentsId, "() => window.intValue"),
CheckJsResult(kWebContentsId, "() => window.intValue", 1),
CheckJsResult(kWebContentsId, "() => window.intValue", testing::Lt(2)),
CheckJsResult(kWebContentsId, "() => window.boolValue"),
CheckJsResult(kWebContentsId, "() => window.boolValue", true),
CheckJsResult(kWebContentsId, "() => window.boolValue",
testing::Ne(false)),
CheckJsResult(kWebContentsId, "() => window.doubleValue"),
CheckJsResult(kWebContentsId, "() => window.doubleValue", 2.0),
CheckJsResult(kWebContentsId, "() => window.doubleValue",
testing::Gt(1.5)),
CheckJsResult(kWebContentsId, "() => window.stringValue"),
CheckJsResult(kWebContentsId, "() => window.stringValue", "a string"),
CheckJsResult(kWebContentsId, "() => window.stringValue", str),
CheckJsResult(kWebContentsId, "() => window.stringValue",
std::string("a string")),
CheckJsResult(kWebContentsId, "() => window.stringValue",
testing::Ne(std::string("another string"))));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResultWithPromise) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
CheckJsResult(kWebContentsId,
"() => new Promise((resolve, reject) => "
"setTimeout(() => resolve(true), 100))"),
CheckJsResult(kWebContentsId,
"() => new Promise((resolve, reject) => "
"setTimeout(() => resolve(1), 100))",
1));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResultWithPromiseFailsOnReject) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
CheckJsResult(kWebContentsId,
"() => new Promise((resolve, reject) => "
"setTimeout(() => reject('rejected'), 100))")));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest, CheckJsResult_Fails) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
ExecuteJs(kWebContentsId, "() => { window.value = 1; }"),
CheckJsResult(kWebContentsId, "() => window.value", 2)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResult_ThrowError_Fails) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
CheckJsResult(kWebContentsId,
"() => { throw new Error('an error'); }", 2)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResult_NoArgument_Fails) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
ExecuteJs(kWebContentsId, "() => { window.value = 0; }"),
CheckJsResult(kWebContentsId, "() => window.value")));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest, ExecuteJsAt) {
const DeepQuery kWhere{"#select"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
ExecuteJsAt(kWebContentsId, kWhere, "(el) => { el.intValue = 1; }"),
WithElement(kWebContentsId,
base::BindLambdaForTesting([&kWhere](ui::TrackedElement* el) {
const auto result =
AsInstrumentedWebContents(el)->EvaluateAt(
kWhere, "(el) => el.intValue");
EXPECT_EQ(1, result.GetInt());
})));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
ExecuteJsAtFireAndForget) {
const DeepQuery kWhere{"#select"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
ExecuteJsAt(kWebContentsId, kWhere, "(el) => { el.intValue = 1; }",
ExecuteJsMode::kFireAndForget),
WithElement(kWebContentsId,
base::BindLambdaForTesting([&kWhere](ui::TrackedElement* el) {
const auto result =
AsInstrumentedWebContents(el)->EvaluateAt(
kWhere, "(el) => el.intValue");
EXPECT_EQ(1, result.GetInt());
})));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
ExecuteJsAtFailsIfElementNotPresent) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const DeepQuery kWhere{"#aaaaa"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
ExecuteJsAt(kWebContentsId, kWhere, "(el) => { el.intValue = 1; }")));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
ExecuteJsAtFailsOnThrow) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const DeepQuery kWhere{"#select"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
ExecuteJsAt(kWebContentsId, kWhere,
"(el) => { throw new Error('an error'); }")));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest, CheckJsResultAt) {
const DeepQuery kWhere{"#select"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
const std::string str("a string");
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
ExecuteJsAt(kWebContentsId, kWhere,
R"((el) => {
el.intValue = 1;
el.boolValue = true;
el.doubleValue = 2.0;
el.stringValue = 'a string';
})"),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.intValue"),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.intValue", 1),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.intValue",
testing::Lt(2)),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.boolValue"),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.boolValue", true),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.boolValue",
testing::Ne(false)),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.doubleValue"),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.doubleValue", 2.0),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.doubleValue",
testing::Gt(1.5)),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.stringValue"),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.stringValue",
"a string"),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.stringValue", str),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.stringValue",
std::string("a string")),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.stringValue",
testing::Ne(std::string("another string"))));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResultAtWithPromise) {
const DeepQuery kWhere{"#select"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
ExecuteJsAt(kWebContentsId, kWhere,
R"((el) => {
el.intValue = 1;
el.stringValue = 'a string';
})"),
CheckJsResultAt(
kWebContentsId, kWhere,
"(el) => new Promise((resolve, reject) => resolve(el.intValue))"),
CheckJsResultAt(
kWebContentsId, kWhere,
"(el) => new Promise((resolve, reject) => resolve(el.intValue))", 1),
CheckJsResultAt(
kWebContentsId, kWhere,
"(el) => new Promise((resolve, reject) => resolve(el.stringValue))"),
CheckJsResultAt(
kWebContentsId, kWhere,
"(el) => new Promise((resolve, reject) => resolve(el.stringValue))",
"a string"));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResultAtWithPromiseFailsOnReject) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const DeepQuery kWhere{"#select"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
CheckJsResultAt(kWebContentsId, kWhere,
"(el) => new Promise((resolve, reject) "
"=> reject('rejected!'))")));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResultAt_Fails) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const DeepQuery kWhere{"#select"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
ExecuteJsAt(kWebContentsId, kWhere, "(el) => { el.intValue = 1; }"),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.intValue", 2)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResultAt_ThrowsError_Fails) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const DeepQuery kWhere{"#select"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
CheckJsResultAt(kWebContentsId, kWhere,
"(el) => { throw new Error('an error'); }", 2)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResultAt_BadPath_Fails) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const DeepQuery kWhere{"#aaaaa"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.intValue", 2)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
CheckJsResultAt_NoArgument_Fails) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
private_test_impl().set_aborted_callback_for_testing(aborted.Get());
const DeepQuery kWhere{"#select"};
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
EXPECT_CALL_IN_SCOPE(
aborted, Run,
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
ExecuteJsAt(kWebContentsId, kWhere,
"(el) => { el.stringValue = ''; }"),
CheckJsResultAt(kWebContentsId, kWhere, "(el) => el.stringValue")));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
InstrumentTabsAsTestSteps) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTab1Id);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTab2Id);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTab3Id);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kIncognito1Id);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kIncognito2Id);
const char kIncognitoNtbName[] = "Incognito NTB";
auto verify_is_at_tab_index = [](Browser* where, ui::ElementIdentifier id,
int expected_index) {
return CheckElement(
id, base::BindLambdaForTesting([where](ui::TrackedElement* el) {
return where->tab_strip_model()->GetIndexOfWebContents(
AsInstrumentedWebContents(el)->web_contents());
}),
expected_index);
};
const GURL url1 = embedded_test_server()->GetURL(kDocumentWithNamedElement);
const GURL url2 = embedded_test_server()->GetURL(kDocumentWithLinks);
Browser* incognito_browser = CreateIncognitoBrowser();
RunTestSequence(
// Instrument an existing tab.
InstrumentTab(kTab1Id), verify_is_at_tab_index(browser(), kTab1Id, 0),
// Instrument the next tab, then insert a tab and verify it's there.
InstrumentNextTab(kTab2Id), PressButton(kNewTabButtonElementId),
NavigateWebContents(kTab2Id, url1),
verify_is_at_tab_index(browser(), kTab2Id, 1),
// Add and instrument tab all in one fell swoop.
AddInstrumentedTab(kTab3Id, url2),
verify_is_at_tab_index(browser(), kTab3Id, 2),
// Instrument the next tab in any browser, then insert the tab and verify
// it's there.
InstrumentNextTab(kIncognito1Id, AnyBrowser()),
NameView(kIncognitoNtbName,
base::BindLambdaForTesting([incognito_browser]() {
return AsView(
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kNewTabButtonElementId,
incognito_browser->window()->GetElementContext()));
})),
PressButton(kIncognitoNtbName),
InAnyContext(verify_is_at_tab_index(incognito_browser, kIncognito1Id, 1)),
Do(base::BindOnce([]() { LOG(WARNING) << 1; })),
// Instrument a final tab by inserting it. Specify an index so the other
// tabs are re-ordered.
AddInstrumentedTab(kIncognito2Id, url2, 1, incognito_browser),
InAnyContext(verify_is_at_tab_index(incognito_browser, kIncognito2Id, 1)),
InAnyContext(
verify_is_at_tab_index(incognito_browser, kIncognito1Id, 2)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest, ScrollIntoView) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTabId);
const GURL url = embedded_test_server()->GetURL(kScrollableDocument);
const DeepQuery kLink{"#link"};
const DeepQuery kText{"#text"};
constexpr char kElementIsInViewport[] = R"(
(el) => {
const bounds = el.getBoundingClientRect();
return bounds.right >= 0 && bounds.bottom >= 0 &&
bounds.x < window.innerWidth && bounds.y < window.innerHeight;
}
)";
RunTestSequence(InstrumentTab(kTabId), NavigateWebContents(kTabId, url),
CheckJsResultAt(kTabId, kLink, kElementIsInViewport, true),
CheckJsResultAt(kTabId, kText, kElementIsInViewport, false),
ScrollIntoView(kTabId, kText),
CheckJsResultAt(kTabId, kLink, kElementIsInViewport, false),
CheckJsResultAt(kTabId, kText, kElementIsInViewport, true));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
WaitForStateChangeAcrossNavigation) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTabId);
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kFoundElementEvent);
const GURL url1 = embedded_test_server()->GetURL(kDocumentWithLinks);
const GURL url2 = embedded_test_server()->GetURL(kDocumentWithNamedElement);
StateChange state_change;
state_change.type = StateChange::Type::kExists;
state_change.where = {"#select"};
state_change.continue_across_navigation = true;
state_change.event = kFoundElementEvent;
RunTestSequence(
InstrumentTab(kTabId),
InParallel(Steps(NavigateWebContents(kTabId, url1),
NavigateWebContents(kTabId, url2)),
WaitForStateChange(kTabId, state_change)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestBrowsertest,
WaitForStateChangeWithConditionAcrossNavigation) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTabId);
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kFoundElementEvent);
const GURL url1 = embedded_test_server()->GetURL(kDocumentWithLinks);
const GURL url2 = embedded_test_server()->GetURL(kDocumentWithNamedElement);
StateChange state_change;
state_change.type = StateChange::Type::kExistsAndConditionTrue;
state_change.where = {"#select option[selected]"};
state_change.test_function = "(el) => (el.innerText === 'Apple')";
state_change.continue_across_navigation = true;
state_change.event = kFoundElementEvent;
RunTestSequence(
InstrumentTab(kTabId),
InParallel(Steps(NavigateWebContents(kTabId, url1),
NavigateWebContents(kTabId, url2)),
WaitForStateChange(kTabId, state_change)));
}
using ClickElementParams =
std::tuple<ui_controls::MouseButton, ui_controls::AcceleratorState>;
class InteractiveBrowserTestClickElementTest
: public InteractiveBrowserTestBrowsertest,
public testing::WithParamInterface<ClickElementParams> {
public:
InteractiveBrowserTestClickElementTest() = default;
~InteractiveBrowserTestClickElementTest() override = default;
};
INSTANTIATE_TEST_SUITE_P(
,
InteractiveBrowserTestClickElementTest,
testing::Combine(
testing::Values(ui_controls::LEFT,
ui_controls::MIDDLE,
ui_controls::RIGHT),
testing::Values(ui_controls::kNoAccelerator,
ui_controls::kShift,
ui_controls::kControl,
ui_controls::kAlt,
ui_controls::kCommand,
static_cast<ui_controls::AcceleratorState>(
ui_controls::kAlt | ui_controls::kShift),
static_cast<ui_controls::AcceleratorState>(
ui_controls::kControl | ui_controls::kCommand |
ui_controls::kAlt | ui_controls::kShift))),
[](const testing::TestParamInfo<ClickElementParams>& params) {
std::ostringstream oss;
switch (std::get<0>(params.param)) {
case ui_controls::LEFT:
oss << "Left";
break;
case ui_controls::MIDDLE:
oss << "Middle";
break;
case ui_controls::RIGHT:
oss << "Right";
break;
}
const auto accel = std::get<1>(params.param);
if (accel & ui_controls::kControl) {
oss << "_Control";
}
if (accel & ui_controls::kAlt) {
oss << "_Alt";
}
if (accel & ui_controls::kShift) {
oss << "_Shift";
}
if (accel & ui_controls::kCommand) {
oss << "_Meta";
}
return oss.str();
});
IN_PROC_BROWSER_TEST_P(InteractiveBrowserTestClickElementTest, ClickElement) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithClickDetection);
const auto mouse_button = std::get<0>(GetParam());
const auto modifier = std::get<1>(GetParam());
const DeepQuery kButton = {"#button"};
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
ClickElement(kWebContentsId, kButton, mouse_button, modifier),
CheckJsResultAt(kWebContentsId, kButton, "el => el.lastClickEvent.button",
static_cast<int>(mouse_button)),
CheckJsResultAt(kWebContentsId, kButton, "el => el.lastClickEvent.altKey",
(modifier & ui_controls::kAlt) != 0),
CheckJsResultAt(kWebContentsId, kButton,
"el => el.lastClickEvent.shiftKey",
(modifier & ui_controls::AcceleratorState::kShift) != 0),
CheckJsResultAt(
kWebContentsId, kButton, "el => el.lastClickEvent.ctrlKey",
(modifier & ui_controls::AcceleratorState::kControl) != 0),
CheckJsResultAt(
kWebContentsId, kButton, "el => el.lastClickEvent.metaKey",
(modifier & ui_controls::AcceleratorState::kCommand) != 0),
CheckJsResultAt(kWebContentsId, kButton,
R"(
function(el) {
const x = el.lastClickEvent.x;
const y = el.lastClickEvent.y;
const rect = el.getBoundingClientRect();
return x >= rect.left && x < rect.right &&
y >= rect.top && y < rect.bottom;
}
)"));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestClickElementTest,
ClickElementOpensLink) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithClickDetection);
const GURL url2 = embedded_test_server()->GetURL(kDocumentWithLinks);
const DeepQuery kLink = {"#link"};
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
ClickElement(kWebContentsId, kLink),
WaitForWebContentsNavigation(kWebContentsId, url2));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestClickElementTest,
MiddleClickElementOpensLink) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithClickDetection);
const GURL url2 = embedded_test_server()->GetURL(kDocumentWithLinks);
const DeepQuery kLink = {"#link"};
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
InstrumentNextTab(kWebContents2Id),
ClickElement(kWebContentsId, kLink, ui_controls::MIDDLE),
WaitForWebContentsReady(kWebContents2Id, url2));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestClickElementTest,
ControlClickElementOpensLink) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithClickDetection);
const GURL url2 = embedded_test_server()->GetURL(kDocumentWithLinks);
const DeepQuery kLink = {"#link"};
RunTestSequence(InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, url),
InstrumentNextTab(kWebContents2Id),
ClickElement(kWebContentsId, kLink, ui_controls::LEFT,
#if BUILDFLAG(IS_MAC)
ui_controls::kCommand
#else
ui_controls::kControl
#endif
),
WaitForWebContentsReady(kWebContents2Id, url2));
}
// TODO(crbug.com/370724585): Re-enable this test.
#if BUILDFLAG(IS_MAC)
#define MAYBE_ShiftClickElementOpensLink DISABLED_ShiftClickElementOpensLink
#else
#define MAYBE_ShiftClickElementOpensLink ShiftClickElementOpensLink
#endif
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestClickElementTest,
MAYBE_ShiftClickElementOpensLink) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithClickDetection);
const GURL url2 = embedded_test_server()->GetURL(kDocumentWithLinks);
const DeepQuery kLink = {"#link"};
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
InstrumentNextTab(kWebContents2Id, AnyBrowser()),
ClickElement(kWebContentsId, kLink, ui_controls::LEFT,
#if BUILDFLAG(IS_MAC)
static_cast<ui_controls::AcceleratorState>(
ui_controls::kCommand | ui_controls::kAlt)
#else
ui_controls::kShift
#endif
),
InAnyContext(WaitForWebContentsReady(kWebContents2Id, url2)),
InSameContext(CheckView(
kBrowserViewElementId,
[](BrowserView* browser_view) { return browser_view->browser(); },
testing::Ne(browser()))));
}
// Parameter for WebUI coverage tests.
struct CoverageConfig {
// Whether to set the --devtools-code-coverage flag. If it's not set, nothing
// should be captured, and the test is simply verifying that no errors are
// generated as a result.
bool command_line_flag = false;
// Whether coverage is actively enabled. If the command line flag is also set,
// the test will check whether data got written to the code coverage folder.
bool enable_coverage = false;
};
// Test fixture to verify that when EnableWebUICodeCoverage() is called with the
// correct command-line arguments, coverage data actually gets written out. It
// also verifies that
class InteractiveBrowserTestCodeCoverageBrowsertest
: public InteractiveBrowserTestBrowsertest,
public testing::WithParamInterface<CoverageConfig> {
public:
void SetUp() override {
{
// This is required for file IO.
base::ScopedAllowBlockingForTesting allow_blocking;
CHECK(tmp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(base::IsDirectoryEmpty(tmp_dir_.GetPath()));
}
InteractiveBrowserTestBrowsertest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
if (GetParam().command_line_flag) {
command_line->AppendSwitchPath(switches::kDevtoolsCodeCoverage,
tmp_dir_.GetPath());
} else {
command_line->RemoveSwitch(switches::kDevtoolsCodeCoverage);
}
InteractiveBrowserTestBrowsertest::SetUpCommandLine(command_line);
}
void SetUpOnMainThread() override {
InteractiveBrowserTestBrowsertest::SetUpOnMainThread();
if (GetParam().enable_coverage) {
EnableWebUICodeCoverage();
}
}
// This is where we actually verify that the data has been written out, since
// coverage output doesn't happen until teardown.
void TearDownOnMainThread() override {
InteractiveBrowserTestBrowsertest::TearDownOnMainThread();
if (GetParam().command_line_flag) {
// This is required for file IO.
base::ScopedAllowBlockingForTesting allow_blocking;
if (GetParam().enable_coverage) {
// Scripts and tests are special directories under the WebUI specific
// directory, ensure they have been created and are not empty.
base::FilePath coverage_dir =
tmp_dir_.GetPath().AppendASCII("webui_javascript_code_coverage");
EXPECT_FALSE(
base::IsDirectoryEmpty(coverage_dir.AppendASCII("scripts")));
EXPECT_FALSE(base::IsDirectoryEmpty(coverage_dir.AppendASCII("tests")));
} else {
EXPECT_TRUE(base::IsDirectoryEmpty(tmp_dir_.GetPath()));
}
}
}
protected:
base::ScopedTempDir tmp_dir_;
};
INSTANTIATE_TEST_SUITE_P(,
InteractiveBrowserTestCodeCoverageBrowsertest,
testing::Values(CoverageConfig{false, false},
CoverageConfig{false, true},
CoverageConfig{true, false},
CoverageConfig{true, true}));
IN_PROC_BROWSER_TEST_P(InteractiveBrowserTestCodeCoverageBrowsertest,
TestCoverageEmits) {
// Navigate and load the New Tab Page, which we know works with code coverage.
RunTestSequence(
InstrumentTab(kWebContentsId),
NavigateWebContents(kWebContentsId, GURL("chrome://history")));
}
namespace {
class TestDialog : public views::DialogDelegateView {
public:
DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kElementId);
TestDialog() { SetProperty(views::kElementIdentifierKey, kElementId); }
~TestDialog() override = default;
gfx::Size CalculatePreferredSize(
const views::SizeBounds& available_size) const override {
return gfx::Size(200, 200);
}
static views::Widget* Show(Browser* parent, ui::mojom::ModalType modal_type) {
auto dialog = std::make_unique<TestDialog>();
dialog->SetModalType(modal_type);
views::Widget* widget = nullptr;
switch (modal_type) {
case ui::mojom::ModalType::kWindow:
widget = constrained_window::CreateBrowserModalDialogViews(
std::move(dialog), parent->window()->GetNativeWindow());
break;
case ui::mojom::ModalType::kChild:
widget = constrained_window::CreateWebModalDialogViews(
dialog.release(),
parent->tab_strip_model()->GetActiveWebContents());
break;
case ui::mojom::ModalType::kSystem:
case ui::mojom::ModalType::kNone:
widget = views::DialogDelegate::CreateDialogWidget(
std::move(dialog), nullptr,
BrowserView::GetBrowserViewForBrowser(parent)
->GetWidget()
->GetNativeView());
break;
}
widget->Show();
return widget;
}
};
// Scoped object that closes a widget it does not own.
class SafeWidgetRef {
public:
SafeWidgetRef() = default;
SafeWidgetRef(const SafeWidgetRef&) = delete;
SafeWidgetRef& operator=(views::Widget* widget) {
if (widget != widget_) {
Close();
widget_ = widget;
}
return *this;
}
~SafeWidgetRef() { Close(); }
void Close() {
if (views::Widget* widget = widget_.get()) {
widget_ = nullptr;
if (!widget->IsClosed()) {
widget->CloseNow();
}
}
}
views::Widget* operator->() { return widget_.get(); }
private:
raw_ptr<views::Widget> widget_ = nullptr;
};
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(TestDialog, kElementId);
} // namespace
class InteractiveBrowserTestDialogBrowsertest
: public InteractiveBrowserTest,
public testing::WithParamInterface<ui::mojom::ModalType> {
public:
InteractiveBrowserTestDialogBrowsertest() = default;
~InteractiveBrowserTestDialogBrowsertest() override = default;
};
INSTANTIATE_TEST_SUITE_P(
,
InteractiveBrowserTestDialogBrowsertest,
::testing::Values(
ui::mojom::ModalType::kNone,
ui::mojom::ModalType::kChild,
ui::mojom::ModalType::kWindow
#if !BUILDFLAG(IS_MAC)
// System modals not supported on mac; see crbug.com/335864910
,
ui::mojom::ModalType::kSystem
#endif
),
[](const testing::TestParamInfo<ui::mojom::ModalType>& param) {
switch (param.param) {
case ui::mojom::ModalType::kNone:
return "None";
case ui::mojom::ModalType::kChild:
return "Child";
case ui::mojom::ModalType::kWindow:
return "Window";
case ui::mojom::ModalType::kSystem:
return "System";
}
});
IN_PROC_BROWSER_TEST_P(InteractiveBrowserTestDialogBrowsertest,
BrowserModalDialogContext) {
SafeWidgetRef widget;
RunTestSequence(
Do([this, &widget]() {
widget = TestDialog::Show(browser(), GetParam());
}),
InAnyContext(WaitForShow(TestDialog::kElementId)),
InSameContext(Steps(
CheckView(
TestDialog::kElementId,
[](views::View* view) { return view->GetWidget()->parent(); },
BrowserView::GetBrowserViewForBrowser(browser())->GetWidget()),
CheckElement(
TestDialog::kElementId,
[](ui::TrackedElement* el) { return el->context(); },
browser()->window()->GetElementContext()))));
}