diff --git a/Userland/Services/WebContent/WebDriverClient.ipc b/Userland/Services/WebContent/WebDriverClient.ipc index 9ca42eddc7..68fcd0043b 100644 --- a/Userland/Services/WebContent/WebDriverClient.ipc +++ b/Userland/Services/WebContent/WebDriverClient.ipc @@ -9,4 +9,5 @@ endpoint WebDriverClient { set_window_rect(JsonValue payload) => (Web::WebDriver::Response response) maximize_window() => (Web::WebDriver::Response response) minimize_window() => (Web::WebDriver::Response response) + find_element(JsonValue payload) => (Web::WebDriver::Response response) } diff --git a/Userland/Services/WebContent/WebDriverConnection.cpp b/Userland/Services/WebContent/WebDriverConnection.cpp index 5f52ed7b6f..57ab7f2f86 100644 --- a/Userland/Services/WebContent/WebDriverConnection.cpp +++ b/Userland/Services/WebContent/WebDriverConnection.cpp @@ -50,6 +50,51 @@ static Gfx::IntRect compute_window_rect(Web::Page const& page) }; } +// https://w3c.github.io/webdriver/#dfn-get-or-create-a-web-element-reference +static String get_or_create_a_web_element_reference(Web::DOM::Node const& element) +{ + // FIXME: 1. For each known element of the current browsing context’s list of known elements: + // FIXME: 1. If known element equals element, return success with known element’s web element reference. + // FIXME: 2. Add element to the list of known elements of the current browsing context. + // FIXME: 3. Return success with the element’s web element reference. + + return String::number(element.id()); +} + +// https://w3c.github.io/webdriver/#dfn-web-element-reference-object +static JsonObject web_element_reference_object(Web::DOM::Node const& element) +{ + // https://w3c.github.io/webdriver/#dfn-web-element-identifier + static String const web_element_identifier = "element-6066-11e4-a52e-4f735466cecf"sv; + + // 1. Let identifier be the web element identifier. + auto identifier = web_element_identifier; + + // 2. Let reference be the result of get or create a web element reference given element. + auto reference = get_or_create_a_web_element_reference(element); + + // 3. Return a JSON Object initialized with a property with name identifier and value reference. + JsonObject object; + object.set("name"sv, identifier); + object.set("value"sv, reference); + return object; +} + +static ErrorOr get_property(JsonValue const& payload, StringView key) +{ + if (!payload.is_object()) + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object"); + + auto const* property = payload.as_object().get_ptr(key); + + if (!property) + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("No property called '{}' present", key)); + if (!property->is_string()) + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Property '{}' is not a String", key)); + + return property->as_string(); +} + ErrorOr> WebDriverConnection::connect(ConnectionFromClient& web_content_client, PageHost& page_host, String const& webdriver_ipc_path) { dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path); @@ -261,6 +306,43 @@ Messages::WebDriverClient::MinimizeWindowResponse WebDriverConnection::minimize_ return serialize_rect(window_rect); } +// 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element +Messages::WebDriverClient::FindElementResponse WebDriverConnection::find_element(JsonValue const& payload) +{ + // 1. Let location strategy be the result of getting a property called "using". + auto location_strategy_string = TRY(get_property(payload, "using"sv)); + auto location_strategy = Web::WebDriver::location_strategy_from_string(location_strategy_string); + + // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument. + if (!location_strategy.has_value()) + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, String::formatted("Location strategy '{}' is invalid", location_strategy_string)); + + // 3. Let selector be the result of getting a property called "value". + // 4. If selector is undefined, return error with error code invalid argument. + auto selector = TRY(get_property(payload, "value"sv)); + + // 5. If the current browsing context is no longer open, return error with error code no such window. + TRY(ensure_open_top_level_browsing_context()); + + // FIXME: 6. Handle any user prompts and return its value if it is an error. + + // 7. Let start node be the current browsing context’s document element. + auto* start_node = m_page_host.page().top_level_browsing_context().active_document(); + + // 8. If start node is null, return error with error code no such element. + if (!start_node) + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"sv); + + // 9. Let result be the result of trying to Find with start node, location strategy, and selector. + auto result = TRY(find(*start_node, *location_strategy, selector)); + + // 10. If result is empty, return error with error code no such element. Otherwise, return the first element of result. + if (result.is_empty()) + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"sv); + + return make_success_response(result.at(0)); +} + // https://w3c.github.io/webdriver/#dfn-no-longer-open ErrorOr WebDriverConnection::ensure_open_top_level_browsing_context() { @@ -310,4 +392,36 @@ Gfx::IntRect WebDriverConnection::iconify_the_window() return rect; } +// https://w3c.github.io/webdriver/#dfn-find +ErrorOr WebDriverConnection::find(Web::DOM::ParentNode& start_node, Web::WebDriver::LocationStrategy using_, StringView value) +{ + // FIXME: 1. Let end time be the current time plus the session implicit wait timeout. + + // 2. Let location strategy be equal to using. + auto location_strategy = using_; + + // 3. Let selector be equal to value. + auto selector = value; + + // 4. Let elements returned be the result of trying to call the relevant element location strategy with arguments start node, and selector. + auto elements = Web::WebDriver::invoke_location_strategy(location_strategy, start_node, selector); + + // 5. If a DOMException, SyntaxError, XPathException, or other error occurs during the execution of the element location strategy, return error invalid selector. + if (elements.is_error()) + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidSelector, String::formatted("The location strategy could not finish: {}", elements.error().message)); + + // FIXME: 6. If elements returned is empty and the current time is less than end time return to step 4. Otherwise, continue to the next step. + + // 7. Let result be an empty JSON List. + JsonArray result; + result.ensure_capacity(elements.value()->length()); + + // 8. For each element in elements returned, append the web element reference object for element, to result. + for (size_t i = 0; i < elements.value()->length(); ++i) + result.append(web_element_reference_object(*elements.value()->item(i))); + + // 9. Return success with data result. + return result; +} + } diff --git a/Userland/Services/WebContent/WebDriverConnection.h b/Userland/Services/WebContent/WebDriverConnection.h index 369ef20363..3475c73d42 100644 --- a/Userland/Services/WebContent/WebDriverConnection.h +++ b/Userland/Services/WebContent/WebDriverConnection.h @@ -9,6 +9,8 @@ #pragma once #include +#include +#include #include #include #include @@ -37,11 +39,13 @@ private: virtual Messages::WebDriverClient::SetWindowRectResponse set_window_rect(JsonValue const& payload) override; virtual Messages::WebDriverClient::MaximizeWindowResponse maximize_window() override; virtual Messages::WebDriverClient::MinimizeWindowResponse minimize_window() override; + virtual Messages::WebDriverClient::FindElementResponse find_element(JsonValue const& payload) override; ErrorOr ensure_open_top_level_browsing_context(); void restore_the_window(); Gfx::IntRect maximize_the_window(); Gfx::IntRect iconify_the_window(); + ErrorOr find(Web::DOM::ParentNode& start_node, Web::WebDriver::LocationStrategy using_, StringView value); ConnectionFromClient& m_web_content_client; PageHost& m_page_host; diff --git a/Userland/Services/WebDriver/Client.cpp b/Userland/Services/WebDriver/Client.cpp index ba782e8510..5843173b36 100644 --- a/Userland/Services/WebDriver/Client.cpp +++ b/Userland/Services/WebDriver/Client.cpp @@ -618,8 +618,7 @@ Web::WebDriver::Response Client::handle_find_element(Vector const& p { dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session//element"); auto* session = TRY(find_session_with_id(parameters[0])); - auto result = TRY(session->find_element(payload)); - return make_json_value(result); + return session->web_content_connection().find_element(payload); } // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements diff --git a/Userland/Services/WebDriver/Session.cpp b/Userland/Services/WebDriver/Session.cpp index ef70dcf297..64b5696bcc 100644 --- a/Userland/Services/WebDriver/Session.cpp +++ b/Userland/Services/WebDriver/Session.cpp @@ -447,61 +447,6 @@ ErrorOr, Web::WebDriver::Error> Session::locator_s return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Not implemented: locator strategy XPath"); } -// 12.3.2 Find Element, https://w3c.github.io/webdriver/#dfn-find-element -Web::WebDriver::Response Session::find_element(JsonValue const& payload) -{ - if (!payload.is_object()) - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Payload is not a JSON object"); - - auto const& properties = payload.as_object(); - // 1. Let location strategy be the result of getting a property called "using". - if (!properties.has("using"sv)) - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "No property called 'using' present"); - auto const& maybe_location_strategy = properties.get("using"sv); - if (!maybe_location_strategy.is_string()) - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Property 'using' is not a String"); - - auto location_strategy = maybe_location_strategy.to_string(); - - // 2. If location strategy is not present as a keyword in the table of location strategies, return error with error code invalid argument. - if (!s_locator_strategies.first_matching([&](LocatorStrategy const& match) { return match.name == location_strategy; }).has_value()) - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "No valid location strategy"); - - // 3. Let selector be the result of getting a property called "value". - // 4. If selector is undefined, return error with error code invalid argument. - if (!properties.has("value"sv)) - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "No property called 'value' present"); - auto const& maybe_selector = properties.get("value"sv); - if (!maybe_selector.is_string()) - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, "Property 'value' is not a String"); - - auto selector = maybe_selector.to_string(); - - // 5. If the current browsing context is no longer open, return error with error code no such window. - TRY(check_for_open_top_level_browsing_context_or_return_error()); - - // FIXME: 6. Handle any user prompts and return its value if it is an error. - - // 7. Let start node be the current browsing context’s document element. - auto maybe_start_node_id = m_browser_connection->get_document_element(); - - // 8. If start node is null, return error with error code no such element. - if (!maybe_start_node_id.has_value()) - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "document element does not exist"); - - auto start_node_id = maybe_start_node_id.release_value(); - LocalElement start_node = { start_node_id }; - - // 9. Let result be the result of trying to Find with start node, location strategy, and selector. - auto result = TRY(find(start_node, location_strategy, selector)); - - // 10. If result is empty, return error with error code no such element. Otherwise, return the first element of result. - if (result.is_empty()) - return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchElement, "The requested element does not exist"); - - return JsonValue(result.at(0)); -} - // 12.3.3 Find Elements, https://w3c.github.io/webdriver/#dfn-find-elements Web::WebDriver::Response Session::find_elements(JsonValue const& payload) { diff --git a/Userland/Services/WebDriver/Session.h b/Userland/Services/WebDriver/Session.h index 5acf1b6892..05377f5b7f 100644 --- a/Userland/Services/WebDriver/Session.h +++ b/Userland/Services/WebDriver/Session.h @@ -58,7 +58,6 @@ public: Web::WebDriver::Response get_window_handle(); ErrorOr> close_window(); Web::WebDriver::Response get_window_handles() const; - Web::WebDriver::Response find_element(JsonValue const& payload); Web::WebDriver::Response find_elements(JsonValue const& payload); Web::WebDriver::Response find_element_from_element(JsonValue const& payload, StringView parameter_element_id); Web::WebDriver::Response find_elements_from_element(JsonValue const& payload, StringView parameter_element_id);