blob: 8e126830c4792e4673178c5e71ae1d33a3e45ce7 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/test_future.h"
#include "chrome/browser/actor/actor_test_util.h"
#include "chrome/browser/actor/tools/tool_request.h"
#include "chrome/browser/actor/tools/tools_test_util.h"
#include "chrome/common/actor.mojom.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
using base::test::TestFuture;
using content::EvalJs;
using content::GetDOMNodeId;
namespace actor {
namespace {
std::string GetSelectElementCurrentValue(content::WebContents* web_contents,
std::string_view query_selector) {
return content::EvalJs(web_contents,
content::JsReplace("document.querySelector($1).value",
query_selector))
.ExtractString();
}
class ActorSelectToolBrowserTest : public ActorToolsGeneralPageStabilityTest {
public:
ActorSelectToolBrowserTest() = default;
~ActorSelectToolBrowserTest() override = default;
void SetUpOnMainThread() override {
ActorToolsTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(embedded_https_test_server().Start());
}
};
INSTANTIATE_TEST_SUITE_P(
,
ActorSelectToolBrowserTest,
testing::ValuesIn(kActorGeneralPageStabilityModeValues),
ActorToolsGeneralPageStabilityTest::DescribeParam);
// Test that the SelectTool can select an ordinary <option> in a <select>
// element.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest, SelectTool_OptionSelected) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string plain_select_id = "#plainSelect";
const int32_t plain_select_dom_node_id =
GetDOMNodeId(*main_frame(), plain_select_id).value();
ASSERT_EQ(GetSelectElementCurrentValue(web_contents(), plain_select_id),
"alpha");
{
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), plain_select_dom_node_id, "beta");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
}
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), plain_select_id),
"beta");
{
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), plain_select_dom_node_id, "gamma");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
}
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), plain_select_id),
"gamma");
// Test selecting by value. The option with value last has text "omega".
{
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), plain_select_dom_node_id, "last");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
}
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), plain_select_id),
"last");
}
// Test that attempting to select in an offscreen <select> succeeds.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest, SelectTool_Offscreen) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
// Page starts unscrolled.
ASSERT_EQ(0, EvalJs(web_contents(), "window.scrollY"));
const std::string offscreen_select_id = "#offscreenSelect";
int32_t offscreen_select_dom_node_id =
GetDOMNodeId(*main_frame(), offscreen_select_id).value();
const std::string initial_value =
GetSelectElementCurrentValue(web_contents(), offscreen_select_id);
ASSERT_EQ(initial_value, "alpha");
const std::string new_value = "beta";
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), offscreen_select_dom_node_id, new_value);
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_GT(EvalJs(web_contents(), "window.scrollY"), 0);
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), offscreen_select_id),
new_value);
}
// Test that the SelectTool causes the change and input events to fire on the
// <select> element.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest, SelectTool_Events) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string plain_select_id = "#plainSelect";
const int32_t plain_select_dom_node_id =
GetDOMNodeId(*main_frame(), plain_select_id).value();
ASSERT_EQ(GetSelectElementCurrentValue(web_contents(), plain_select_id),
"alpha");
ASSERT_EQ("", EvalJs(web_contents(), "select_event_log.join(',')"));
{
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), plain_select_dom_node_id, "beta");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_EQ("input,change",
EvalJs(web_contents(), "select_event_log.join(',')"));
}
}
// Test that attempting to select a value that does not exist in the <option>
// list fails and does not change the current selection.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest,
SelectTool_NonExistentValueFails) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string plain_select_id = "#plainSelect";
int32_t plain_select_dom_node_id =
GetDOMNodeId(*main_frame(), plain_select_id).value();
const std::string initial_value =
GetSelectElementCurrentValue(web_contents(), plain_select_id);
ASSERT_EQ(initial_value, "alpha");
std::unique_ptr<ToolRequest> action = MakeSelectRequest(
*main_frame(), plain_select_dom_node_id, "nonexistentValue");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectErrorResult(result, mojom::ActionResultCode::kSelectNoSuchOption);
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), plain_select_id),
initial_value);
}
// Test that attempting to select a value corresponding to a non-<option>
// element fails. The select tool should only target valid options.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest,
SelectTool_NonOptionNodeValueFails) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string non_options_select_id = "#nonOptionsSelect";
int32_t non_options_select_dom_node_id =
GetDOMNodeId(*main_frame(), non_options_select_id).value();
const std::string initial_value =
GetSelectElementCurrentValue(web_contents(), non_options_select_id);
ASSERT_EQ(initial_value, "alpha");
// Attempt to select "beta", which is the text of a <span>, not an <option>
// value. Expect the action to fail.
{
std::unique_ptr<ToolRequest> action = MakeSelectRequest(
*main_frame(), non_options_select_dom_node_id, "beta");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectErrorResult(result, mojom::ActionResultCode::kSelectNoSuchOption);
}
// Expect the value to remain unchanged
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), non_options_select_id),
initial_value);
// Attempt to select "gamma", which is the value property of a <button>
// element, not an <option> value. Expect the action to fail.
{
std::unique_ptr<ToolRequest> action = MakeSelectRequest(
*main_frame(), non_options_select_dom_node_id, "gamma");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectErrorResult(result, mojom::ActionResultCode::kSelectNoSuchOption);
}
// Expect the value to remain unchanged
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), non_options_select_id),
initial_value);
// Attempt to select "epsilon". This should succeed as there is an <option>
// with value epsilon, despite there also being a <button> with value
// "epsilon".
{
std::unique_ptr<ToolRequest> action = MakeSelectRequest(
*main_frame(), non_options_select_dom_node_id, "epsilon");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_EQ(
GetSelectElementCurrentValue(web_contents(), non_options_select_id),
"epsilon");
}
}
// Test that matching option values is case-sensitive.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest,
SelectTool_ValueIsCaseSensitive) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string plain_select_id = "#plainSelect";
int32_t plain_select_dom_node_id =
GetDOMNodeId(*main_frame(), plain_select_id).value();
const std::string initial_value =
GetSelectElementCurrentValue(web_contents(), plain_select_id);
ASSERT_EQ(initial_value, "alpha");
// Attempt to select "BETA" which has different casing than the option "beta"
// Expect the action to fail due to case mismatch.
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), plain_select_dom_node_id, "BETA");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectErrorResult(result, mojom::ActionResultCode::kSelectNoSuchOption);
// The select value should be unchanged.
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), plain_select_id),
initial_value);
}
// Test that attempting to select a disabled <option> fails.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest,
SelectTool_DisabledOptionFails) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string plain_select_id = "#plainSelect";
int32_t plain_select_dom_node_id =
GetDOMNodeId(*main_frame(), plain_select_id).value();
const std::string initial_value =
GetSelectElementCurrentValue(web_contents(), plain_select_id);
ASSERT_EQ(initial_value, "alpha");
// Attempt to select the value of the disabled option. Expect the action to
// fail and the select's value to be unchanged.
std::unique_ptr<ToolRequest> action = MakeSelectRequest(
*main_frame(), plain_select_dom_node_id, "disabledOption");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectErrorResult(result, mojom::ActionResultCode::kSelectOptionDisabled);
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), plain_select_id),
initial_value);
}
// Test that attempting to select a <option> in a disabled <optgroup> fails.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest,
SelectTool_DisabledOptGroupFails) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string group_select_id = "#groupedSelect";
int32_t plain_select_dom_node_id =
GetDOMNodeId(*main_frame(), group_select_id).value();
const std::string initial_value =
GetSelectElementCurrentValue(web_contents(), group_select_id);
ASSERT_EQ(initial_value, "alpha");
// Attempt to select the option with value "foobar". The option itself is
// enabled but is in a disabled optgroup. Expect the action to fail and the
// select's value to be unchanged.
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), plain_select_dom_node_id, "foobar");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectErrorResult(result, mojom::ActionResultCode::kSelectOptionDisabled);
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), group_select_id),
initial_value);
}
// Test that attempting to select any option in a disabled <select> element
// fails.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest,
SelectTool_DisabledSelectFails) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string disabled_select_id = "#disabledSelect";
int32_t disabled_select_dom_node_id =
GetDOMNodeId(*main_frame(), disabled_select_id).value();
const std::string initial_value =
GetSelectElementCurrentValue(web_contents(), disabled_select_id);
ASSERT_EQ(initial_value, "alpha");
// Attempt to select an otherwise valid option value ("beta"). Expect the
// action to fail without affecting the <select>.
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), disabled_select_dom_node_id, "beta");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectErrorResult(result, mojom::ActionResultCode::kElementDisabled);
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), disabled_select_id),
initial_value);
}
// Test that options within <optgroup> elements can be selected.
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest,
SelectTool_GroupedOptionSelected) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string grouped_select_id = "#groupedSelect";
int32_t grouped_select_dom_node_id =
GetDOMNodeId(*main_frame(), grouped_select_id).value();
ASSERT_EQ(GetSelectElementCurrentValue(web_contents(), grouped_select_id),
"alpha");
// Select an option from the first group
{
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), grouped_select_dom_node_id, "gamma");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
}
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), grouped_select_id),
"gamma");
// Select an option from the second group
{
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), grouped_select_dom_node_id, "b");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
}
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), grouped_select_id),
"b");
}
// Test that an option can be selected in a <select> element rendered as a
// listbox (size attribute > 1).
IN_PROC_BROWSER_TEST_P(ActorSelectToolBrowserTest,
SelectTool_ListboxOptionSelected) {
const GURL url = embedded_test_server()->GetURL("/actor/select_tool.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const std::string listbox_select_id = "#listboxSelect";
int32_t listbox_select_dom_node_id =
GetDOMNodeId(*main_frame(), listbox_select_id).value();
// List box starts with no element selected.
ASSERT_EQ(GetSelectElementCurrentValue(web_contents(), listbox_select_id),
"");
{
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), listbox_select_dom_node_id, "beta");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
}
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), listbox_select_id),
"beta");
{
std::unique_ptr<ToolRequest> action =
MakeSelectRequest(*main_frame(), listbox_select_dom_node_id, "delta");
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
}
EXPECT_EQ(GetSelectElementCurrentValue(web_contents(), listbox_select_id),
"delta");
}
} // namespace
} // namespace actor