From d1ba317e22d41fd948fd477d44735918a972c158 Mon Sep 17 00:00:00 2001 From: Han Date: Sat, 7 Sep 2024 12:41:54 -0700 Subject: [PATCH] WebDriver: Add Routes, IPC definitions, and boilerplates Added the following Routes, IPC definitions, and boilerplates for the missing endpoints: - Switch To Frame - Switch To Parent Frame - Element Clear - Element Send Keys --- .../Libraries/LibWeb/WebDriver/Client.cpp | 4 + Userland/Libraries/LibWeb/WebDriver/Client.h | 4 + .../Services/WebContent/WebDriverClient.ipc | 4 + .../WebContent/WebDriverConnection.cpp | 384 ++++++++++++++++++ Userland/Services/WebDriver/Client.cpp | 28 ++ Userland/Services/WebDriver/Client.h | 4 + 6 files changed, 428 insertions(+) diff --git a/Userland/Libraries/LibWeb/WebDriver/Client.cpp b/Userland/Libraries/LibWeb/WebDriver/Client.cpp index 0cb8459efa1..408a0f448f9 100644 --- a/Userland/Libraries/LibWeb/WebDriver/Client.cpp +++ b/Userland/Libraries/LibWeb/WebDriver/Client.cpp @@ -64,6 +64,8 @@ static constexpr auto s_webdriver_endpoints = Array { ROUTE(POST, "/session/:session_id/window"sv, switch_to_window), ROUTE(GET, "/session/:session_id/window/handles"sv, get_window_handles), ROUTE(POST, "/session/:session_id/window/new"sv, new_window), + ROUTE(POST, "/session/:session_id/frame"sv, switch_to_frame), + ROUTE(POST, "/session/:session_id/frame/parent"sv, switch_to_parent_frame), ROUTE(GET, "/session/:session_id/window/rect"sv, get_window_rect), ROUTE(POST, "/session/:session_id/window/rect"sv, set_window_rect), ROUTE(POST, "/session/:session_id/window/maximize"sv, maximize_window), @@ -89,6 +91,8 @@ static constexpr auto s_webdriver_endpoints = Array { ROUTE(GET, "/session/:session_id/element/:element_id/computedrole"sv, get_computed_role), ROUTE(GET, "/session/:session_id/element/:element_id/computedlabel"sv, get_computed_label), ROUTE(POST, "/session/:session_id/element/:element_id/click"sv, element_click), + ROUTE(POST, "/session/:session_id/element/:element_id/clear"sv, element_clear), + ROUTE(POST, "/session/:session_id/element/:element_id/value"sv, element_send_keys), ROUTE(GET, "/session/:session_id/source"sv, get_source), ROUTE(POST, "/session/:session_id/execute/sync"sv, execute_script), ROUTE(POST, "/session/:session_id/execute/async"sv, execute_async_script), diff --git a/Userland/Libraries/LibWeb/WebDriver/Client.h b/Userland/Libraries/LibWeb/WebDriver/Client.h index fe1497a251d..be74a424cee 100644 --- a/Userland/Libraries/LibWeb/WebDriver/Client.h +++ b/Userland/Libraries/LibWeb/WebDriver/Client.h @@ -58,6 +58,8 @@ public: virtual Response maximize_window(Parameters parameters, JsonValue payload) = 0; virtual Response minimize_window(Parameters parameters, JsonValue payload) = 0; virtual Response fullscreen_window(Parameters parameters, JsonValue payload) = 0; + virtual Response switch_to_frame(Parameters parameters, JsonValue payload) = 0; + virtual Response switch_to_parent_frame(Parameters parameters, JsonValue payload) = 0; // Extension: https://html.spec.whatwg.org/multipage/interaction.html#user-activation-user-agent-automation virtual Response consume_user_activation(Parameters parameters, JsonValue payload) = 0; @@ -82,6 +84,8 @@ public: virtual Response get_computed_role(Parameters parameters, JsonValue payload) = 0; virtual Response get_computed_label(Parameters parameters, JsonValue payload) = 0; virtual Response element_click(Parameters parameters, JsonValue payload) = 0; + virtual Response element_clear(Parameters parameters, JsonValue payload) = 0; + virtual Response element_send_keys(Parameters parameters, JsonValue payload) = 0; // 13. Document, https://w3c.github.io/webdriver/#document virtual Response get_source(Parameters parameters, JsonValue payload) = 0; diff --git a/Userland/Services/WebContent/WebDriverClient.ipc b/Userland/Services/WebContent/WebDriverClient.ipc index 8c988c15d57..08bcd965da1 100644 --- a/Userland/Services/WebContent/WebDriverClient.ipc +++ b/Userland/Services/WebContent/WebDriverClient.ipc @@ -19,6 +19,8 @@ endpoint WebDriverClient { close_window() => (Web::WebDriver::Response response) switch_to_window() => (Web::WebDriver::Response response) new_window(JsonValue payload) => (Web::WebDriver::Response response) + switch_to_frame(JsonValue payload) => (Web::WebDriver::Response response) + switch_to_parent_frame(JsonValue payload) => (Web::WebDriver::Response response) get_window_rect() => (Web::WebDriver::Response response) set_window_rect(JsonValue payload) => (Web::WebDriver::Response response) maximize_window() => (Web::WebDriver::Response response) @@ -44,6 +46,8 @@ endpoint WebDriverClient { get_computed_role(String element_id) => (Web::WebDriver::Response response) get_computed_label(String element_id) => (Web::WebDriver::Response response) element_click(String element_id) => (Web::WebDriver::Response response) + element_clear(String element_id) => (Web::WebDriver::Response response) + element_send_keys(String element_id) => (Web::WebDriver::Response response) get_source() => (Web::WebDriver::Response response) execute_script(JsonValue payload) => (Web::WebDriver::Response response) execute_async_script(JsonValue payload) => (Web::WebDriver::Response response) diff --git a/Userland/Services/WebContent/WebDriverConnection.cpp b/Userland/Services/WebContent/WebDriverConnection.cpp index d54fae51bb1..416bb366f75 100644 --- a/Userland/Services/WebContent/WebDriverConnection.cpp +++ b/Userland/Services/WebContent/WebDriverConnection.cpp @@ -595,6 +595,87 @@ Messages::WebDriverClient::NewWindowResponse WebDriverConnection::new_window(Jso return result; } +// 11.6 Switch To Frame, https://w3c.github.io/webdriver/#dfn-switch-to-frame +Messages::WebDriverClient::SwitchToFrameResponse WebDriverConnection::switch_to_frame() +{ + dbgln("FIXME: WebDriverConnection::switch_to_frame()"); + // FIXME: 1. Let id be the result of getting the property "id" from parameters. + + // FIXME: 2. If id is not null, a Number object, or an Object that represents a web element, return error with error code invalid argument. + + // FIXME: 3. Run the substeps of the first matching condition: + + // -> id is null + + { + // FIXME: 1. If session's current top-level browsing context is no longer open, return error with error code no such window. + + // FIXME: 2. Try to handle any user prompts with session. + + // FIXME: 3. Set the current browsing context with session and session's current top-level browsing context. + } + + // -> id is a Number object + + { + // FIXME: 1. If id is less than 0 or greater than 216 – 1, return error with error code invalid argument. + + // FIXME: 2. If session's current browsing context is no longer open, return error with error code no such window. + + // FIXME: 3. Try to handle any user prompts with session. + + // FIXME: 4. Let window be the associated window of session's current browsing context's active document. + + // FIXME: 5. If id is not a supported property index of window, return error with error code no such frame. + + // FIXME: 6. Let child window be the WindowProxy object obtained by calling window.[[GetOwnProperty]] (id). + + // FIXME: 7. Set the current browsing context with session and child window's browsing context. + } + + // -> id represents a web element + + { + // FIXME: 1. If session's current browsing context is no longer open, return error with error code no such window. + + // FIXME: 2. Try to handle any user prompts with session. + + // FIXME: 3. Let element be the result of trying to get a known element with session and id. + + // FIXME: 4. If element is not a frame or iframe element, return error with error code no such frame. + + // FIXME: 5. Set the current browsing context with session and element's content navigable's active browsing context. + } + + // FIXME: 4. Update any implementation-specific state that would result from the user selecting session's current browsing context for interaction, without altering OS-level focus. + + // FIXME: 5. Return success with data null +} + +// 11.7 Switch To Parent Frame, https://w3c.github.io/webdriver/#dfn-switch-to-parent-frame +Messages::WebDriverClient::SwitchToParentFrameResponse WebDriverConnection::switch_to_parent_frame() +{ + dbgln("FIXME: WebDriverConnection::switch_to_parent_frame()"); + + // FIXME: 1. If session's current browsing context is already the top-level browsing context: + + { + // FIXME: 1. If session's current browsing context is no longer open, return error with error code no such window. + + // FIXME: 2. Return success with data null. + } + + // FIXME: 2. If session's current parent browsing context is no longer open, return error with error code no such window. + + // FIXME: 3. Try to handle any user prompts with session. + + // FIXME: 4. If session's current parent browsing context is not null, set the current browsing context with session and current parent browsing context. + + // FIXME: 5. Update any implementation-specific state that would result from the user selecting session's current browsing context for interaction, without altering OS-level focus. + + // FIXME: 6. Return success with data null. +} + // 11.8.1 Get Window Rect, https://w3c.github.io/webdriver/#dfn-get-window-rect Messages::WebDriverClient::GetWindowRectResponse WebDriverConnection::get_window_rect() { @@ -1427,6 +1508,309 @@ Messages::WebDriverClient::ElementClickResponse WebDriverConnection::element_cli return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Click not implemented"sv); } +// 12.5.2 Element Clear, https://w3c.github.io/webdriver/#dfn-element-clear +Messages::WebDriverClient::ElementClearResponse WebDriverConnection::element_clear(String const& element_id) +{ + dbgln("FIXME: WebDriverConnection::element_clear()"); + // To clear a content editable element + + // FIXME: 1. If element's innerHTML IDL attribute is an empty string do nothing and return. + + // FIXME: 2. Run the focusing steps for element. + + // FIXME: 3. Set element's innerHTML IDL attribute to an empty string. + + // FIXME: 4. Run the unfocusing steps for the element. + + // To clear a resettable element: + + // FIXME: 1. Let empty be the result of the first matching condition: + + { + // -> element is an input element whose type attribute is in the File Upload state + // True if the list of selected files has a length of 0, and false otherwise. + // -> otherwise + // True if its value IDL attribute is an empty string, and false otherwise. + } + + // FIXME: 2. If element is a candidate for constraint validation it satisfies its constraints, and empty is true, abort these substeps. + + // FIXME: 3. Invoke the focusing steps for element. + + // FIXME: 4. Invoke the clear algorithm for element. + + // FIXME: 5. Invoke the unfocusing steps for the element. + + // The remote end steps, given session, URL variables and parameters are: + + // FIXME: 1. If session's current browsing context is no longer open, return error with error code no such window. + + // FIXME: 2. Try to handle any user prompts with session. + + // FIXME: 3. Let element be the result of trying to get a known element with session and element id. + + // FIXME: 4. If element is not editable, return an error with error code invalid element state. + + // FIXME: 5. Scroll into view the element. + + // FIXME: 6. Let timeout be session's session timeouts' implicit wait timeout. + + // FIXME: 7. Let timer be a new timer. + + // FIXME: 8. If timeout is not null: + + { + // FIXME: 1. Start the timer with timer and timeout. + } + + // FIXME: 9. Wait for element to become interactable, or timer's timeout fired flag to be set, whichever occurs first. + + // FIXME: 10. If element is not interactable, return error with error code element not interactable. + + // FIXME: 11. Run the substeps of the first matching statement: + + { + // -> element is a mutable form control element + + // Invoke the steps to clear a resettable element. + + // -> element is a mutable element + + // Invoke the steps to clear a content editable element. + + // -> otherwise + + // Return error with error code invalid element state. + } + + // FIXME: 12. Return success with data null. +} + +// 12.5.3 Element Send Keys, https://w3c.github.io/webdriver/#dfn-element-send-keys +Messages::WebDriverClient::ElementSendKeysResponse WebDriverConnection::element_send_keys(String const& element_id) +{ + dbgln("FIXME: WebDriverConnection::element_send_keys()"); + // To clear the modifier key state given input state, input id, source, undo actions, and browsing context: + + // FIXME: 1. If source is not a key input source return error with error code invalid argument. + + // FIXME: 2. Let actions options be a new actions options with the is element origin steps set to represents a web element, and the get element origin steps set to get a WebElement origin. + + // FIXME: 3. For each entry key in the lexically sorted keys of undo actions: + + { + // FIXME: 1. Let action be the value of undo actions equal to the key entry key. + + // FIXME: 2. If action is not an action object with type "key" and subtype "keyUp", return error with error code invalid argument. + + // FIXME: 3. Let actions be the list «action» + + // FIXME: 4. Dispatch a list of actions with input state, actions, browsing context, and actions options. + } + + // To dispatch the events for a typeable string given input state, input id, source, text, and browsing context: + + // FIXME: 1. Let actions options be a new actions options with the is element origin steps set to represents a web element, and the get element origin steps set to get a WebElement origin. + + // FIXME: 2. For each char of text: + + { + // FIXME: 1. Let global key state be the result of get the global key state with input state. + + // FIXME: 2. Let actions be the list «action». + + // FIXME: 3. Dispatch a list of actions with input state, actions, and browsing context. + } + + // FIXME: 3. If char is not a shifted character and the shifted state of source is true: + + { + // FIXME: 1. Let action be an action object constructed with input id, "key", and "keyUp", and set its value property to U+E008 ("left shift"). + + // FIXME: 2. Let tick actions be the list «action». + + // FIXME: 3. Dispatch a list of actions with input state, actions, browsing context, and actions options. + } + + // FIXME: 4. Let keydown action be an action object constructed with arguments input id, "key", and "keyDown". + + // FIXME: 5. Set the value property of keydown action to char. + + // FIXME: 6. Let keyup action be a copy of keydown action with the subtype property changed to "keyUp". + + // FIXME: 7. Let actions be the list «keydown action, keyup action». + + // FIXME: 8. Dispatch a list of actions with input state, actions, browsing context, and actions options. + + // To dispatch actions for a string given input state, input id, source, text, browsing context, and actions options: + + // FIXME: 1. Let clusters be an array created by breaking text into extended grapheme clusters. + + // FIXME: 2. Let undo actions be an empty map. + + // FIXME: 3. Let current typeable text be an empty list. + + // FIXME: 4. For each cluster corresponding to an indexed property in clusters run the substeps of the first matching statement: + + { + // -> cluster is the null key + + { + // FIXME: 1. Dispatch the events for a typeable string with input state, input id, source, current typeable text, and browsing context. Empty current typeable text. + + // FIXME: 2. Try to clear the modifier key state with input state, input id, source, undo actions and browsing context. + + // FIXME: 3. Clear undo actions. + } + + // -> cluster is a modifier key + + { + // FIXME: 1. Dispatch the events for a typeable string with input state, input id, source, current typeable text, and browsing context. + + // FIXME: 2. Emptycurrent typeable text. + + // FIXME: 3. Let keydown action be an action object constructed with arguments input id, "key", and "keyDown". + + // FIXME: 4. Set the value property of keydown action to cluster. + + // FIXME: 5. Let actions be the list «keydown action» + + // FIXME: 6. Dispatch a list of actions with input state, actions, browsing context, and actions options. + + // FIXME: 7. Add an entry to undo actions with key cluster and value being a copy of keydown action with the subtype property modified to "keyUp". + } + + // -> cluster is typeable + + { + // Append cluster to current typeable text. + } + + // -> otherwise + + { + // FIXME: 1. Dispatch the events for a typeable string with input state, input id, source, current typeable text, and browsing context. + + // FIXME: 2. Empty current typeable text. + + // FIXME: 3. Dispatch a composition event with arguments "compositionstart", undefined, and browsing context. + + // FIXME: 4. Dispatch a composition event with arguments "compositionupdate", cluster, and browsing context. + + // FIXME: 5. Dispatch a composition event with arguments "compositionend", cluster, and browsing context. + } + } + + // FIXME: 5. Dispatch the events for a typeable string with input state, input id and source, current typeable text, and browsing context. + + // FIXME: 6. Try to clear the modifier key state with input state, input id, source, undo actions, and browsing context. + + // The remote end steps, given session, URL variables and parameters are: + + // FIXME: 1. Let text be the result of getting a property named "text" from parameters. + + // FIXME: 2. If text is not a String, return an error with error code invalid argument. + + // FIXME: 3. If session's current browsing context is no longer open, return error with error code no such window. + + // FIXME: 4. Try to handle any user prompts with session. + + // FIXME: 5. Let element be the result of trying to get a known element with session and URL variables[element id]. + + // FIXME: 6. Let file be true if element is input element in the file upload state, or false otherwise. + + // FIXME: 7. If file is false or the session's strict file interactability, is true run the following substeps: + + { + // FIXME: 1. Scroll into view the element. + + // FIXME: 2. Let timeout be session's session timeouts' implicit wait timeout. + + // FIXME: 3. Let timer be a new timer. + + // FIXME: 4. If timeout is not null: + + { + // FIXME: 1. Start the timer with timer and timeout. + } + + // FIXME: 5. Wait for element to become keyboard-interactable, or timer's timeout fired flag to be set, whichever occurs first. + + // FIXME: 6. If element is not keyboard-interactable, return error with error code element not interactable. + + // FIXME: 7. If element is not the active element run the focusing steps for the element. + } + + // FIXME: 8. Run the substeps of the first matching condition: + + // -> file is true + + { + // FIXME: 1. Let files be the result of splitting text on the newline (\n) character. + + // FIXME: 2. If files is of 0 length, return an error with error code invalid argument. + + // FIXME: 3. Let multiple equal the result of calling hasAttribute() with "multiple" on element. + + // FIXME: 4. if multiple is false and the length of files is not equal to 1, return an error with error code invalid argument. + + // FIXME: 5. Verify that each file given by the user exists. If any do not, return error with error code invalid argument. + + // FIXME: 6. Complete implementation specific steps equivalent to setting the selected files on the input element. If multiple is true files are be appended to element's selected files. + + // FIXME: 7. Fire these events in order on element: + + // FIXME: 1. input + + // FIXME: 2. change + + // FIXME: 8. Return success with data null. + } + + // -> element is a non-typeable form control + + { + // FIXME: 1. If element does not have an own property named value return an error with error code element not interactable + + // FIXME: 2. If element is not mutable return an error with error code element not interactable. + + // FIXME: 3. Set a property value to text on element. + + // FIXME: 4. If element is suffering from bad input return an error with error code invalid argument. + + // FIXME: 5. Return success with data null. + } + + // -> elementis content editable + + { + // If element does not currently have focus, set the text insertion caret after any child content. + } + + // -> otherwise + + { + // FIXME: 1. If element does not currently have focus, let current text length be the length of element's API value. + + // FIXME: 2. Set the text insertion caret using set selection range using current text length for both the start and end parameters. + } + + // FIXME: 9. Let input state be the result of get the input state with session and session's current top-level browsing context. + + // FIXME: 10. Let input id be a the result of generating a UUID. + + // FIXME: 11. Let source be the result of create an input source with input state, and "key". + + // FIXME: 12. Add an input source with input state, input id and source. + + // FIXME: 13. Dispatch actions for a string with arguments input state, input id, and source, text, and session's current browsing context. + + // FIXME: 14. Remove an input source with input state and input id. + + // FIXME: 15. Return success with data null. +} + // 13.1 Get Page Source, https://w3c.github.io/webdriver/#dfn-get-page-source Messages::WebDriverClient::GetSourceResponse WebDriverConnection::get_source() { diff --git a/Userland/Services/WebDriver/Client.cpp b/Userland/Services/WebDriver/Client.cpp index c56bbbd05c8..c977d8e2001 100644 --- a/Userland/Services/WebDriver/Client.cpp +++ b/Userland/Services/WebDriver/Client.cpp @@ -344,6 +344,20 @@ Web::WebDriver::Response Client::new_window(Web::WebDriver::Parameters parameter return session->web_content_connection().new_window(payload); } +// 11.6 Switch To Frame, https://w3c.github.io/webdriver/#dfn-switch-to-frame +// POST /session/{session id}/frame +Web::WebDriver::Response Client::switch_to_frame(Web::WebDriver::Parameters parameters, JsonValue payload) +{ + // FIXME +} + +// 11.7 Switch To Parent Frame, https://w3c.github.io/webdriver/#dfn-switch-to-parent-frame +// POST /session/{session id}/frame/parent +Web::WebDriver::Response Client::switch_to_parent_frame(Web::WebDriver::Parameters parameters, JsonValue payload) +{ + // FIXME +} + // 11.8.1 Get Window Rect, https://w3c.github.io/webdriver/#dfn-get-window-rect // GET /session/{session id}/window/rect Web::WebDriver::Response Client::get_window_rect(Web::WebDriver::Parameters parameters, JsonValue) @@ -569,6 +583,20 @@ Web::WebDriver::Response Client::element_click(Web::WebDriver::Parameters parame return session->web_content_connection().element_click(move(parameters[1])); } +// 12.5.2 Element Clear, https://w3c.github.io/webdriver/#dfn-element-clear +// POST /session/{session id}/element/{element id}/clear +Web::WebDriver::Response Client::element_clear(Web::WebDriver::Parameters parameters, JsonValue) +{ + // FIXME +} + +// 12.5.3 Element Send Keys, https://w3c.github.io/webdriver/#dfn-element-send-keys +// POST /session/{session id}/element/{element id}/value +Web::WebDriver::Response Client::element_send_keys(Web::WebDriver::Parameters parameters, JsonValue) +{ + // FIXME +} + // 13.1 Get Page Source, https://w3c.github.io/webdriver/#dfn-get-page-source // GET /session/{session id}/source Web::WebDriver::Response Client::get_source(Web::WebDriver::Parameters parameters, JsonValue) diff --git a/Userland/Services/WebDriver/Client.h b/Userland/Services/WebDriver/Client.h index 4a713fcd67e..5f2810a022f 100644 --- a/Userland/Services/WebDriver/Client.h +++ b/Userland/Services/WebDriver/Client.h @@ -53,6 +53,8 @@ private: virtual Web::WebDriver::Response switch_to_window(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response get_window_handles(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response new_window(Web::WebDriver::Parameters parameters, JsonValue payload) override; + virtual Web::WebDriver::Response switch_to_frame(Web::WebDriver::Parameters parameters, JsonValue payload) override; + virtual Web::WebDriver::Response switch_to_parent_frame(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response get_window_rect(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response set_window_rect(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response maximize_window(Web::WebDriver::Parameters parameters, JsonValue payload) override; @@ -78,6 +80,8 @@ private: virtual Web::WebDriver::Response get_computed_role(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response get_computed_label(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response element_click(Web::WebDriver::Parameters parameters, JsonValue payload) override; + virtual Web::WebDriver::Response element_clear(Web::WebDriver::Parameters parameters, JsonValue payload) override; + virtual Web::WebDriver::Response element_send_keys(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response get_source(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response execute_script(Web::WebDriver::Parameters parameters, JsonValue payload) override; virtual Web::WebDriver::Response execute_async_script(Web::WebDriver::Parameters parameters, JsonValue payload) override;