saresend / selenium-rs

A Rust Client for the Selenium webdriver (WIP)
MIT License
175 stars 31 forks source link

[CHROME] find_element() -> Error(Json (Error ("invalid type: null, expected a string", line: 3, column: 19)) #35

Open l1444 opened 3 years ago

l1444 commented 3 years ago

hello, I'm using the code that is provided in the documentation so this one:

let mut driver = WebDriver::new(Browser::Chrome);

driver.start_session();
driver.navigate("http://google.com");
let search_bar = driver.find_element(Selector::CSS, "input[maxlength=\"2048\"]").unwrap();

search_bar.type_text("selenium-rs github");
let search_button = driver.find_element(Selector::CSS, "input[name=\"btnK\"]").unwrap();
search_button.click();

and it shows me this error:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error(Json(Error("invalid type: null, expected a string", line: 3, column: 19)))', src\main.rs:20:71
blesson3 commented 3 years ago

I'm seeing that the selenium-rs is not communicating well with the the local Selenium API. It's returning sessionId: null to selenium-rs when trying to find an element, which selenium-rs requires in the response (ElementResponse). I have a fix incoming, but it's relatively large and changes some semantics.

As an aside: does anyone have a reference to the standalone Selenium server REST API (for selenium-server-standalone-3.141.59.jar)? It would be incredibly useful for this fix. I seem to be unable to find it in ~10m of web searching.

nixpulvis commented 3 years ago

Here's a hacky workaround I whipped up (sorry for the whitespace changes). I only updated find_elements but the same hack can be applied to other functions I'm sure.

```diff diff --git a/src/element.rs b/src/element.rs index 743064b..8ff48ce 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,13 +1,13 @@ -/*! +/*! Element enables most of the site interaction, and wraps user interactions such as typing text and clicking on things. Note that each element is tied to the specific session (currently, we can't hold on to the same element across sessions). - # Example - Inspecting attributes of an element + # Example - Inspecting attributes of an element - ```rust + ```rust use selenium_rs::webdriver::{Selector, Browser, WebDriver}; use selenium_rs::element::Element; @@ -43,6 +43,11 @@ impl<'a> Element<'a> { client, } } + + pub fn set_session_id(&mut self, session_id: String) { + self.session_id = session_id; + } } // Contains implementation for attribute interaction for the element diff --git a/src/element_structs.rs b/src/element_structs.rs index ff8cf36..e22a47e 100644 --- a/src/element_structs.rs +++ b/src/element_structs.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use element::Element; use reqwest; @@ -11,29 +12,24 @@ pub struct SelectedResponse { #[derive(Deserialize)] pub struct ElementResponse { #[serde(rename = "sessionId")] - session_id: String, + session_id: Option, status: i32, - value: ElemValue, + value: HashMap, } #[allow(dead_code)] -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct ElementsResponse { #[serde(rename = "sessionId")] - session_id: String, + session_id: Option, status: i32, - value: Vec, -} - -#[derive(Deserialize)] -struct ElemValue { - #[serde(rename = "ELEMENT")] - element_id: String, + value: Vec>, } impl<'a> ElementResponse { pub fn parse_into_element(self, client: &'a reqwest::Client) -> Element<'a> { - Element::new(self.value.element_id, self.session_id, client) + let element_id = self.value.values().next().expect("one element"); + Element::new(element_id.clone(), self.session_id.as_ref().map_or("null".to_string(), |id| id.clone()), client) } } @@ -42,7 +38,10 @@ impl<'a> ElementsResponse { let session_id = self.session_id; self.value .into_iter() - .map(|value| Element::new(value.element_id, session_id.clone(), client)) + .map(|value| { + let element_id = value.values().next().expect("one element"); + Element::new(element_id.clone(), session_id.as_ref().map_or("null".to_string(), |id| id.clone()), client) + }) .collect() } } @@ -51,7 +50,7 @@ impl<'a> ElementsResponse { #[derive(Deserialize)] pub struct AttributeResponse { #[serde(rename = "sessionId")] - session_id: String, + session_id: Option, pub value: String, } diff --git a/src/webdriver.rs b/src/webdriver.rs index ddc270e..47a0196 100644 --- a/src/webdriver.rs +++ b/src/webdriver.rs @@ -199,13 +199,17 @@ impl WebDriver { let sess_id = self.session_id.clone().unwrap(); let url = construct_url(vec!["session/", &(sess_id + "/"), "elements"]); let payload = ElementRequest::new(str_for_selector(selector), query.to_string()); let response: ElementsResponse = self.client .post(url) .json(&payload) .send()? .error_for_status()? .json()?; - let elements = response.parse_into_elements(&self.client); + let mut elements = response.parse_into_elements(&self.client); + for element in &mut elements { + element.set_session_id(self.session_id.clone().unwrap()); + } Ok(elements) } } ```

As an aside: does anyone have a reference to the standalone Selenium server REST API [...]?

I'm struggling without this too! The fundamental issue I don't really understand is why the sessionId field is null, or why an element needs it's own session id at all. But I'm not really that knowledgeable of Selenium, I just use it occasionally.