blob: 5029860026e1f7821b739df78e5d65c78c2689db [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/json/json_parser.h"
#include "third_party/blink/renderer/platform/json/json_values.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
namespace blink {
class HTMLFormMcpToolTest : public PageTestBase {
public:
HTMLInputElement* GetInputElement(const char* id) {
return DynamicTo<HTMLInputElement>(
GetDocument().getElementById(AtomicString(id)));
}
HTMLFormElement* GetFormElement(const char* id) {
return DynamicTo<HTMLFormElement>(
GetDocument().getElementById(AtomicString(id)));
}
// Private functions exposed via class friendship:
static bool IsValidWebMCPForm(HTMLFormElement& form_element) {
return form_element.IsValidWebMCPForm();
}
static bool FillFormControls(HTMLFormElement& form_element,
const String& input_arguments) {
CHECK(IsValidWebMCPForm(form_element));
CHECK(form_element.active_webmcp_tool_);
bool require_submit_button = false;
HTMLFormControlElement* submit_button;
return form_element.active_webmcp_tool_->FillFormControls(
input_arguments, require_submit_button, &submit_button);
}
static String ComputeInputSchema(HTMLFormElement& form_element) {
CHECK(IsValidWebMCPForm(form_element));
CHECK(form_element.active_webmcp_tool_);
return form_element.active_webmcp_tool_->ComputeInputSchema();
}
private:
ScopedWebMCPForTest scoped_feature{true};
};
// Note that both toolname *and* tooldescription must be present
// for a <form> element to become a valid declarative WebMCP tool.
TEST_F(HTMLFormMcpToolTest, NoTool) {
SetBodyInnerHTML(
R"HTML(
<form id=form>
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
EXPECT_FALSE(IsValidWebMCPForm(*form_element));
}
TEST_F(HTMLFormMcpToolTest, NoTool_NameOnly) {
SetBodyInnerHTML(
R"HTML(
<form id=form toolname="mytool">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
EXPECT_FALSE(IsValidWebMCPForm(*form_element));
}
TEST_F(HTMLFormMcpToolTest, NoTool_DescriptionOnly) {
SetBodyInnerHTML(
R"HTML(
<form id=form tooldescription="perform task">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
EXPECT_FALSE(IsValidWebMCPForm(*form_element));
}
TEST_F(HTMLFormMcpToolTest, ToolPresent_Basic) {
SetBodyInnerHTML(
R"HTML(
<form id=form toolname="mytool" tooldescription="perform task">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
EXPECT_TRUE(IsValidWebMCPForm(*form_element));
}
TEST_F(HTMLFormMcpToolTest, ToolRemovedWithAttribute_Name) {
SetBodyInnerHTML(
R"HTML(
<form id=form toolname="mytool" tooldescription="perform task">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
EXPECT_TRUE(IsValidWebMCPForm(*form_element));
form_element->removeAttribute(html_names::kToolnameAttr);
EXPECT_FALSE(IsValidWebMCPForm(*form_element));
}
TEST_F(HTMLFormMcpToolTest, ToolRemovedWithAttribute_Description) {
SetBodyInnerHTML(
R"HTML(
<form id=form toolname="mytool" tooldescription="perform task">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
EXPECT_TRUE(IsValidWebMCPForm(*form_element));
form_element->removeAttribute(html_names::kTooldescriptionAttr);
EXPECT_FALSE(IsValidWebMCPForm(*form_element));
}
TEST_F(HTMLFormMcpToolTest, ToolAppearsWhenAttributeSet_NameFirst) {
SetBodyInnerHTML(
R"HTML(
<form id=form>
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
EXPECT_FALSE(IsValidWebMCPForm(*form_element));
form_element->setAttribute(html_names::kToolnameAttr, AtomicString("mytool"));
EXPECT_FALSE(IsValidWebMCPForm(*form_element)); // Still need a description.
form_element->setAttribute(html_names::kTooldescriptionAttr,
AtomicString("description"));
EXPECT_TRUE(IsValidWebMCPForm(*form_element));
}
TEST_F(HTMLFormMcpToolTest, ToolAppearsWhenAttributeSet_DescriptionFirst) {
SetBodyInnerHTML(
R"HTML(
<form id=form>
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
EXPECT_FALSE(IsValidWebMCPForm(*form_element));
form_element->setAttribute(html_names::kTooldescriptionAttr,
AtomicString("description"));
EXPECT_FALSE(IsValidWebMCPForm(*form_element)); // Still need a name.
form_element->setAttribute(html_names::kToolnameAttr, AtomicString("mytool"));
EXPECT_TRUE(IsValidWebMCPForm(*form_element));
}
TEST_F(HTMLFormMcpToolTest, Tool_AppendFormElement) {
UpdateAllLifecyclePhasesForTest();
HTMLFormElement* form_element =
MakeGarbageCollected<HTMLFormElement>(GetDocument());
form_element->setAttribute(html_names::kToolnameAttr, AtomicString("mytool"));
form_element->setAttribute(html_names::kTooldescriptionAttr,
AtomicString("description"));
EXPECT_FALSE(IsValidWebMCPForm(*form_element)); // Not connected.
GetDocument().body()->AppendChild(form_element);
EXPECT_TRUE(IsValidWebMCPForm(*form_element));
}
TEST_F(HTMLFormMcpToolTest, FillFormControls_Basic) {
SetBodyInnerHTML(
R"HTML(
<form id=form toolname="mytool" tooldescription="perform task">
<input id=text1 name=text1 type=text>
<input id=text2 name=text2 type=text>
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String json_string =
R"JSON(
{
"text1": "foo",
"text2": "bar"
}
)JSON";
EXPECT_TRUE(FillFormControls(*form_element, json_string));
HTMLInputElement* text1 = GetInputElement("text1");
HTMLInputElement* text2 = GetInputElement("text2");
ASSERT_TRUE(text1);
ASSERT_TRUE(text2);
EXPECT_EQ("foo", text1->Value());
EXPECT_EQ("bar", text2->Value());
}
TEST_F(HTMLFormMcpToolTest, FillFormControls_Partial) {
SetBodyInnerHTML(
R"HTML(
<form id=form toolname="mytool" tooldescription="perform task">
<input id=text1 name=text1 type=text value="initial1">
<input id=text2 name=text2 type=text value="initial2">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String json_string =
R"JSON(
{
"text2": "bar"
}
)JSON";
EXPECT_TRUE(FillFormControls(*form_element, json_string));
HTMLInputElement* text1 = GetInputElement("text1");
HTMLInputElement* text2 = GetInputElement("text2");
ASSERT_TRUE(text1);
ASSERT_TRUE(text2);
EXPECT_EQ("initial1", text1->Value());
EXPECT_EQ("bar", text2->Value());
}
TEST_F(HTMLFormMcpToolTest, FillFormControls_InvalidJsonFailure) {
SetBodyInnerHTML(
R"HTML(
<form id=form toolname="mytool" tooldescription="perform task"> </form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
EXPECT_FALSE(FillFormControls(*form_element, R"JSON({"x":"y",})JSON"));
EXPECT_FALSE(FillFormControls(*form_element, R"JSON(["unknown"])JSON"));
}
TEST_F(HTMLFormMcpToolTest, FillFormControls_UnknownParamFailure) {
SetBodyInnerHTML(
R"HTML(
<form id=form toolname="mytool" tooldescription="perform task">
<input id=text1 name=text1 type=text>
<input id=text2 name=text2 type=text>
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String json_string =
R"JSON(
{
"unknown": "UNKNOWN"
}
)JSON";
EXPECT_FALSE(FillFormControls(*form_element, json_string));
}
TEST_F(HTMLFormMcpToolTest, FillFormControls_Transactional) {
SetBodyInnerHTML(
R"HTML(
<form id=form toolname="mytool" tooldescription="perform task">
<input id=text1 name=text1 type=text value="initial1">
<input id=text2 name=text2 type=text value="initial2">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String json_string =
R"JSON(
{
"text1": "foo",
"unknown": "bar",
"text2": "bar"
}
)JSON";
EXPECT_FALSE(FillFormControls(*form_element, json_string));
HTMLInputElement* text1 = GetInputElement("text1");
HTMLInputElement* text2 = GetInputElement("text2");
ASSERT_TRUE(text1);
ASSERT_TRUE(text2);
// A failure means no form control values were changed.
EXPECT_EQ("initial1", text1->Value());
EXPECT_EQ("initial2", text2->Value());
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_TextInput) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<input name="text1" type="text">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"text1": {
"type": "string"
}
},
"required": []
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_TextInput_Required) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<input name="text1" type="text">
<input name="text2" type="text" required>
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"text1": {
"type": "string"
},
"text2": {
"type": "string"
}
},
"required": ["text2"]
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_TextInput_Title) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<input name="text1" type="text" toolparamtitle="Surname">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"text1": {
"type": "string",
"title": "Surname"
}
},
"required": []
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_TextInput_Description) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<input name="text1" type="text" toolparamdescription="Some nice text">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"text1": {
"type": "string",
"description": "Some nice text"
}
},
"required": []
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_TextInput_Label) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<label for="text">Some text</label>
<input id="text" name="text1" type="text">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"text1": {
"type": "string",
"description": "Some text"
}
},
"required": []
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_TextInput_Label_Multiple) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<label for="text">Label one</label>
<label for="text">Label two</label>
<input id="text" name="text1" type="text">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"text1": {
"type": "string",
"description": "Label one; Label two"
}
},
"required": []
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_TextInput_AriaDescription) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<input name="text1" type="text" aria-description="ARIA">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"text1": {
"type": "string",
"description": "ARIA"
}
},
"required": []
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_TextInput_PreferLabelOverAria) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<label for="text">Label</label>
<input id="text" name="text1" type="text" aria-description="ARIA">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"text1": {
"type": "string",
"description": "Label"
}
},
"required": []
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_TextInput_PreferAttrOverLabel) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<label for="text">Label</label>
<input
id="text"
name="text1"
type="text"
toolparamdescription="ATTR"
aria-description="ARIA">
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"text1": {
"type": "string",
"description": "ATTR"
}
},
"required": []
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_Select) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<select name="select" required>
<option value="Option 1">This is option 1</option>
<option value="Option 2">This is option 2</option>
<option value="Option 3">This is option 3</option>
</select>
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"select": {
"type": "string",
"oneOf": [
{ "const": "Option 1", "title": "This is option 1" },
{ "const": "Option 2", "title": "This is option 2" },
{ "const": "Option 3", "title": "This is option 3" }
]
}
},
"required": ["select"]
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
TEST_F(HTMLFormMcpToolTest, ParameterSchema_Select_Title) {
SetBodyInnerHTML(
R"HTML(
<form id="form" toolname="mytool" tooldescription="perform task">
<select name="select" toolparamtitle="Possible Options">
<option value="Option 1">This is option 1</option>
</select>
</form>
)HTML");
HTMLFormElement* form_element = GetFormElement("form");
ASSERT_TRUE(form_element);
ASSERT_TRUE(IsValidWebMCPForm(*form_element));
String actual = ComputeInputSchema(*form_element);
std::unique_ptr<JSONValue> expected_json = ParseJSON(R"JSON(
{
"type": "object",
"properties": {
"select": {
"type": "string",
"oneOf": [
{ "const": "Option 1", "title": "This is option 1" }
],
"title": "Possible Options"
}
},
"required": []
}
)JSON");
ASSERT_TRUE(expected_json);
EXPECT_EQ(expected_json->ToJSONString(), actual);
}
} // namespace blink