use std::collections::BTreeMap;
use std::fmt::{format, Debug};
use std::fs::File;
use std::future::Future;
use std::io::Write;
use std::num::{ParseIntError, TryFromIntError};
use std::ops::Deref;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use parking_lot::lock_api::MutexGuard;
use parking_lot::{Mutex, RawMutex};
use product_os_configuration::NetworkProxy;
use product_os_random::RandomGenerator;
use product_os_request::Method;
use regex::Regex;
use product_os_server::{ProductOSRouter, ProductOSServer};
use product_os_utilities::ProductOSError;
use serde_json::{Error, Value};
use url::{ParseError, Url};
use uuid::Uuid;
use crate::{BrowserHelper, BrowserStatus};
use crate::actor::{Behaviour, BehaviourKind};
use crate::cache::CachedContent;
use crate::response::{ResponseContent, SearchResultFormat};
use crate::socket_connection_server::{socket_server_handler, SocketConnectionServer, SocketPayload};
use crate::request::Request;
use crate::client::{LastRequest, ResultFormat, SearchKind, WebClient, WebClientStatus, WindowSelect};
use crate::element::{Element, ElementSearchQuery};
use crate::engine::{BrowserEngine, Engine};
use crate::engine::BrowserKind::Gecko;
pub struct BoundingBoxPosition {
top: f64,
bottom: f64,
left: f64,
right: f64,
width: f64,
height: f64,
x: f64,
y: f64
}
#[derive(Clone)]
pub struct SocketClient {
identifier: String,
socket_server: Option<Arc<SocketConnectionServer>>,
windows: Arc<Mutex<Vec<String>>>,
active_window: Arc<Mutex<Option<String>>>,
config: product_os_configuration::Browser,
proxy_config: Option<product_os_configuration::NetworkProxy>,
status: Arc<Mutex<BTreeMap<String, WebClientStatus>>>,
last_request: Arc<Mutex<BTreeMap<String, LastRequest>>>,
cached_content: Arc<Mutex<BTreeMap<String, CachedContent>>>,
engine: Option<Arc<Mutex<BrowserEngine>>>,
helpers: Arc<Mutex<Vec<Arc<dyn crate::helper::BrowserHelper>>>>
}
impl Debug for SocketClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?} - {:?}", self.status, self.last_request)
}
}
impl SocketClient {
pub fn new(config: product_os_configuration::Browser, proxy_config: Option<product_os_configuration::NetworkProxy>, helpers: Arc<Mutex<Vec<Arc<dyn BrowserHelper>>>>) -> Self {
Self {
identifier: Uuid::new_v4().to_string(),
socket_server: None,
windows: Arc::new(Mutex::new(vec![])),
active_window: Arc::new(Mutex::new(None)),
config: config.to_owned(),
proxy_config: proxy_config.to_owned(),
status: Arc::new(Mutex::new(BTreeMap::new())),
last_request: Arc::new(Mutex::new(BTreeMap::new())),
cached_content: Arc::new(Mutex::new(BTreeMap::new())),
engine: None,
helpers: helpers.to_owned()
}
}
async fn generate_socket_connector(&mut self) -> Result<(Arc<SocketConnectionServer>, Arc<Mutex<BrowserEngine>>), ProductOSError> {
let socket_server = match self.generate_browser().await {
Ok(_) => {
tokio::time::sleep(Duration::new(1, 0)).await;
let socket_server = Arc::new(SocketConnectionServer::new(self.config.socket_identifier.to_owned()));
self.socket_server = Some(socket_server.clone());
socket_server
}
Err(e) => return Err(ProductOSError::GenericError(format!("Failed to start server: {:?}", e.to_string())))
};
Ok((socket_server, self.engine.to_owned().unwrap()))
}
async fn search(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, result_format: SearchResultFormat) -> Result<ResultFormat, ProductOSError> {
match self.search_all(window, search.to_owned(), search_kind, result_format).await {
Ok(results) => {
tracing::info!("Search results for {:?} - #{:?}", search, results.len());
match results.get(which) {
Some(result) => Ok(result.clone()),
None => {
tracing::error!("Failed to search element {:?} - Index is invalid", search);
Err(ProductOSError::GenericError(String::from("Index is invalid")))
}
}
}
Err(e) => Err(e)
}
}
async fn search_all(&self, window: &WindowSelect, search: String, search_kind: SearchKind, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
match search_kind {
SearchKind::XPath => self.search_xpath(window, search, result_format).await,
SearchKind::CSS => self.search_selector(window, search, result_format).await,
SearchKind::ID => self.search_id(window, search, result_format).await,
SearchKind::Name => self.search_name(window, search, result_format).await,
SearchKind::Variable => self.search_variable(window, search, result_format).await,
SearchKind::Header => self.search_header(window, search, result_format).await,
SearchKind::Location => self.search_location_as_string(window, search, result_format).await,
SearchKind::None => Ok(Vec::new())
}
}
async fn search_xpath(&self, window: &WindowSelect, query: String, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
self.underlying_search(window, query, SearchKind::XPath, result_format).await
}
async fn search_selector(&self, window: &WindowSelect, selector: String, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
self.underlying_search(window, selector, SearchKind::CSS, result_format).await
}
async fn search_id(&self, window: &WindowSelect, id: String, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
self.underlying_search(window, id, SearchKind::ID, result_format).await
}
async fn search_name(&self, window: &WindowSelect, name: String, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
self.underlying_search(window, name, SearchKind::Name, result_format).await
}
async fn search_variable(&self, window: &WindowSelect, variable_name: String, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
self.underlying_search(window, variable_name, SearchKind::Variable, result_format).await
}
async fn search_header(&self, window: &WindowSelect, header_name: String, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
self.underlying_search(window, header_name, SearchKind::Header, result_format).await
}
async fn search_location(&self, window: &WindowSelect, x: usize, y: usize, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
let mut position = String::new();
position.push_str(x.to_string().as_str());
position.push_str(",");
position.push_str(y.to_string().as_str());
self.underlying_search(window, position, SearchKind::Location, result_format).await
}
async fn search_location_as_string(&self, window: &WindowSelect, position: String, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
self.underlying_search(window, position, SearchKind::Location, result_format).await
}
async fn underlying_search(&self, window: &WindowSelect, query: String, kind: SearchKind, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
let scope_present = query.split("%%").collect::<Vec<&str>>();
let search_query = ElementSearchQuery {
search_query: if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { query.to_owned() },
target: if scope_present.len() > 1 { Some(scope_present.get(0).unwrap().to_string()) } else { None },
search_kind: kind,
which: 0,
result_format: result_format.to_owned()
};
match serde_json::value::to_value(search_query.to_owned()) {
Ok(search_query_json) => {
match self.send_message_to_window_socket_and_get_result(window, "search", "server_action", search_query_json, "application/json", None, None, "search_result").await {
Ok(result) => {
let current_content = match self.get_cached_content(window) {
Ok(p) => p,
Err(e) => return Err(e)
};
let mut result_list = vec!();
match result.data {
Value::Array(elements) => {
for el in elements {
match result_format {
SearchResultFormat::Element => {
match el {
Value::String(element_string) => { result_list.push(ResultFormat::Element(Element::new(element_string, search_query.to_owned(), Arc::new(Mutex::new(current_content.to_owned()))))); },
_ => return Err(ProductOSError::GenericError(String::from("Invalid element result")))
}
}
SearchResultFormat::Location => {
match el {
Value::Object(map) => {
let x = match map.get("x") {
None => return Err(ProductOSError::GenericError(String::from("Missing x position"))),
Some(x) => {
match x {
Value::String(s) => match f64::from_str(s.as_str()) {
Ok(u) => u,
Err(e) => return Err(ProductOSError::GenericError(format!("Convert from string failure for x position: {:?}", e))),
},
Value::Number(n) => {
match n.as_f64() {
None => return Err(ProductOSError::GenericError(String::from("Convert from number failure for x position"))),
Some(f) => f
}
}
_ => return Err(ProductOSError::GenericError(String::from("Invalid type for x position"))),
}
}
};
let y = match map.get("y") {
None => return Err(ProductOSError::GenericError(String::from("Missing x position"))),
Some(x) => {
match x {
Value::String(s) => match f64::from_str(s.as_str()) {
Ok(u) => u,
Err(e) => return Err(ProductOSError::GenericError(format!("Convert from string failure for x position: {:?}", e))),
},
Value::Number(n) => {
match n.as_f64() {
None => return Err(ProductOSError::GenericError(String::from("Convert from number failure for x position"))),
Some(f) => f
}
}
_ => return Err(ProductOSError::GenericError(String::from("Invalid type for x position"))),
}
}
};
result_list.push(ResultFormat::Location(x, y));
}
_ => return Err(ProductOSError::GenericError(String::from("Invalid element result")))
}
}
SearchResultFormat::Object | SearchResultFormat::Raw => {
match &el {
Value::Object(_) => { result_list.push(ResultFormat::Object(el)); },
_ => return Err(ProductOSError::GenericError(String::from("Invalid element result")))
}
}
SearchResultFormat::Text | SearchResultFormat::Value => {
match el {
Value::String(element_string) => { result_list.push(ResultFormat::Element(Element::new(element_string, search_query.to_owned(), Arc::new(Mutex::new(current_content.to_owned()))))); },
_ => return Err(ProductOSError::GenericError(String::from("Invalid element result")))
}
}
_ => return Err(ProductOSError::GenericError(String::from("Invalid element kind")))
}
}
},
Value::String(element_string) => {
match result_format {
SearchResultFormat::Element | SearchResultFormat::Text | SearchResultFormat::Value => {
result_list.push(ResultFormat::Element(Element::new(element_string, search_query.to_owned(), Arc::new(Mutex::new(current_content.to_owned())))));
}
_ => return Err(ProductOSError::GenericError(String::from("Invalid element kind")))
}
},
Value::Object(map) => {
match result_format {
SearchResultFormat::Location => {
let x = match map.get("x") {
None => return Err(ProductOSError::GenericError(String::from("Missing x position"))),
Some(x) => {
match x {
Value::String(s) => match f64::from_str(s.as_str()) {
Ok(u) => u,
Err(e) => return Err(ProductOSError::GenericError(format!("Convert from string failure for x position: {:?}", e))),
},
Value::Number(n) => {
match n.as_f64() {
None => return Err(ProductOSError::GenericError(String::from("Convert from number failure for x position"))),
Some(f) => f
}
}
_ => return Err(ProductOSError::GenericError(String::from("Invalid type for x position"))),
}
}
};
let y = match map.get("y") {
None => return Err(ProductOSError::GenericError(String::from("Missing x position"))),
Some(x) => {
match x {
Value::String(s) => match f64::from_str(s.as_str()) {
Ok(u) => u,
Err(e) => return Err(ProductOSError::GenericError(format!("Convert from string failure for x position: {:?}", e))),
},
Value::Number(n) => {
match n.as_f64() {
None => return Err(ProductOSError::GenericError(String::from("Convert from number failure for x position"))),
Some(f) => f
}
}
_ => return Err(ProductOSError::GenericError(String::from("Invalid type for x position"))),
}
}
};
result_list.push(ResultFormat::Location(x, y));
}
SearchResultFormat::Object => {
result_list.push(ResultFormat::Object(Value::Object(map)));
}
SearchResultFormat::Raw => {
result_list.push(ResultFormat::Raw(Value::Object(map)));
}
_ => return Err(ProductOSError::GenericError(String::from("Invalid element kind")))
}
}
value => {
match result_format {
SearchResultFormat::Object => {
result_list.push(ResultFormat::Object(value));
}
SearchResultFormat::Raw => {
result_list.push(ResultFormat::Raw(value));
}
_ => return Err(ProductOSError::GenericError(String::from("Invalid search list result")))
}
}
};
Ok(result_list)
}
Err(e) => Err(e)
}
}
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
}
}
fn set_current_cached_content(&self, window: &WindowSelect, current_content: ResponseContent) -> Result<(), ProductOSError> {
match self.get_window_id(window) {
Err(e) => Err(e),
Ok(w) => {
match self.cached_content.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to get lock for cached current_contents: {:?}", w))),
Some(mut cached_contents) => {
cached_contents.insert(w.to_owned(), CachedContent::new(current_content));
Ok(())
}
}
}
}
}
fn get_position_data(result: serde_json::Map<String, Value>) -> BoundingBoxPosition {
let top = match result.get("top") {
None => 0.0,
Some(v) => {
match v {
Value::Number(n) => {
match n.as_f64() {
None => 0.0,
Some(f) => f
}
}
_ => 0.0
}
}
};
let bottom = match result.get("bottom") {
None => 0.0,
Some(v) => {
match v {
Value::Number(n) => {
match n.as_f64() {
None => 0.0,
Some(f) => f
}
}
_ => 0.0
}
}
};
let left = match result.get("left") {
None => 0.0,
Some(v) => {
match v {
Value::Number(n) => {
match n.as_f64() {
None => 0.0,
Some(f) => f
}
}
_ => 0.0
}
}
};
let right = match result.get("right") {
None => 0.0,
Some(v) => {
match v {
Value::Number(n) => {
match n.as_f64() {
None => 0.0,
Some(f) => f
}
}
_ => 0.0
}
}
};
let width = match result.get("width") {
None => 0.0,
Some(v) => {
match v {
Value::Number(n) => {
match n.as_f64() {
None => 0.0,
Some(f) => f
}
}
_ => 0.0
}
}
};
let height = match result.get("height") {
None => 0.0,
Some(v) => {
match v {
Value::Number(n) => {
match n.as_f64() {
None => 0.0,
Some(f) => f
}
}
_ => 0.0
}
}
};
let x = match result.get("x") {
None => 0.0,
Some(v) => {
match v {
Value::Number(n) => {
match n.as_f64() {
None => 0.0,
Some(f) => f
}
}
_ => 0.0
}
}
};
let y = match result.get("y") {
None => 0.0,
Some(v) => {
match v {
Value::Number(n) => {
match n.as_f64() {
None => 0.0,
Some(f) => f
}
}
_ => 0.0
}
}
};
BoundingBoxPosition {
top,
bottom,
left,
right,
width,
height,
x,
y
}
}
async fn websocket_status() -> String { String::from("Websocket service running") }
async fn send_message_to_window_socket_and_get_result(&self, window: &WindowSelect, instruction_type: &str, instruction_kind: &str, data: Value, content_type: &str, related_identifier: Option<String>, next_identifier: Option<String>, result_type: &str) -> Result<SocketPayload, ProductOSError> {
let timeout = 100;
let counter_limit = 30 * 1000 / timeout;
match self.get_window_id(window) {
Err(e) => Err(e),
Ok(window_string) => {
match &self.socket_server {
None => {
tracing::error!("Failed to get socket server");
Err(ProductOSError::GenericError(String::from("Failed to get socket server")))
}
Some(socket_server) => {
match socket_server.send_message_to_window_socket(window_string.to_owned(), instruction_type, instruction_kind, data, content_type, related_identifier, next_identifier).await {
Ok(identifier) => {
let mut counter = 0;
while counter < counter_limit {
tracing::debug!("Getting messages: {:?}", counter);
match socket_server.get_message(identifier.as_str()).await {
Ok(result) => {
match result.instruction_type.as_str() {
"search" => {
let data: ElementSearchQuery = match serde_json::from_value(result.data.to_owned()) {
Ok(s) => s,
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
};
match data.search_kind {
SearchKind::Header => {
let cached_content = match self.get_cached_content(window) {
Ok(cp) => cp,
Err(e) => return Err(e)
};
let uri = cached_content.get_request_url();
let search_result = socket_server.get_header(uri.as_str(), data.search_query.as_str());
let mut result_list = vec![];
for value in search_result {
result_list.push(Value::String(value));
}
let result_json = Value::Array(result_list);
match socket_server.send_message_to_window_socket(window_string, "search_result", "server_result", result_json, "application/json", Some(result.instruction_identifier.to_owned()), result.next_identifier.to_owned()).await {
Ok(new_identifier) => {
let mut counter = 0;
while counter < counter_limit {
tracing::debug!("Getting messages: {:?}", counter);
match socket_server.get_message(identifier.as_str()).await {
Ok(result) => {
return Ok(result)
}
Err(_) => {}
}
counter += 1;
tokio::time::sleep(Duration::from_millis(u64::try_from(timeout).unwrap())).await;
}
}
Err(_) => {}
}
return Err(ProductOSError::GenericError(String::from("No message returned within timeout - search scenario")))
}
_ => {
return Err(ProductOSError::GenericError(String::from("Problem with query - danger of infinite recursion")))
}
}
}
"error" => {
return Err(ProductOSError::GenericError(format!("Error encountered on client side: {:?}", result)))
}
_ => {
return Ok(result)
}
}
}
Err(e) => {}
}
counter += 1;
tokio::time::sleep(Duration::from_millis(u64::try_from(timeout).unwrap())).await;
}
Err(ProductOSError::GenericError(String::from("No message returned within timeout")))
}
Err(e) => Err(e)
}
}
}
}
}
}
async fn send_message_to_window_socket(&self, window: &WindowSelect, instruction_type: &str, instruction_kind: &str, data: Value, content_type: &str, related_identifier: Option<String>, next_identifier: Option<String>) -> Result<(), ProductOSError> {
match self.get_window_id(window) {
Err(e) => Err(e),
Ok(window_string) => {
match &self.socket_server {
None => {
tracing::error!("Failed to get socket server");
Err(ProductOSError::GenericError(String::from("Failed to get socket server")))
}
Some(socket_server) => {
match socket_server.send_message_to_window_socket(window_string.to_owned(), instruction_type, instruction_kind, data, content_type, related_identifier, next_identifier).await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
}
}
}
fn remove_status(&self, window: &WindowSelect) -> Result<(), ProductOSError> {
match self.get_window_id(window) {
Ok(window) => {
match self.status.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to get lock for status: {:?}", window))),
Some(mut statuses) => {
statuses.remove(window.as_str());
Ok(())
}
}
}
Err(e) => Err(e)
}
}
}
#[async_trait]
impl WebClient for SocketClient {
fn get_windows(&self) -> Result<Vec<String>, ProductOSError> {
match self.windows.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(String::from("Unable to get windows"))),
Some(w) => Ok(w.to_owned())
}
}
fn has_window(&self, window: &WindowSelect) -> Result<bool, ProductOSError> {
match self.windows.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(String::from("Unable to get windows"))),
Some(w) => {
match window {
WindowSelect::Visible => {
match self.active_window.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to lock and get active window: {:?}", window))),
Some(w) => {
match &*w {
None => Err(ProductOSError::GenericError("No active window defined when checking for has window".to_string())),
Some(w) => Ok(true)
}
}
}
}
WindowSelect::Id(id) => {
if w.contains(id) { Ok(true) }
else { Ok(false) }
}
WindowSelect::Count(n) => {
if w.len() <= n.to_owned() as usize { Ok(true ) }
else { Ok(false) }
}
}
}
}
}
fn get_client_id(&self) -> &str {
self.identifier.as_str()
}
fn get_window_id(&self, window: &WindowSelect) -> Result<String, ProductOSError> {
match window {
WindowSelect::Visible => match self.active_window.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to lock and get active window: {:?}", window))),
Some(w) => {
match &*w {
None => Err(ProductOSError::GenericError("No active window defined".to_string())),
Some(w) => Ok(w.to_owned())
}
}
},
WindowSelect::Id(w) => Ok(w.to_owned()),
WindowSelect::Count(n) => {
match self.windows.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to lock and get window list: {:?}", window))),
Some(ws) => {
match ws.get(*n as usize) {
None => Err(ProductOSError::GenericError(format!("No window defined with name: {:?}", window))),
Some(w) => Ok(w.to_owned())
}
}
}
}
}
}
async fn generate_browser(&mut self) -> Result<(), ProductOSError> {
match self.engine {
None => {
let agent = match self.helpers.try_lock_for(Duration::from_secs(10)) {
None => return Err(ProductOSError::GenericError(String::from("Unable to get helpers"))),
Some(helpers) => {
let mut agent = String::new();
for helper in helpers.iter() {
match helper.set_user_agent(self.config.browser_name.as_str()) {
Some(ag) => agent = ag,
None => agent = self.config.browser_name.to_owned()
}
}
agent
}
};
let engine = BrowserEngine::new(agent, &self.config, &self.proxy_config);
self.engine = Some(Arc::new(Mutex::new(engine)));
}
Some(_) => {}
}
Ok(())
}
fn add_helper(&self, helper: Arc<dyn BrowserHelper>) {
match self.helpers.try_lock_for(Duration::from_secs(10)) {
None => tracing::error!("Failed to add helper - could not get lock on helpers"),
Some(mut helpers) => {
helpers.push(helper.clone())
}
}
}
fn get_status(&self, window: &WindowSelect) -> Result<BrowserStatus, ProductOSError> {
match self.get_window_id(window) {
Ok(window) => {
match self.status.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to get lock for status: {:?}", window))),
Some(status) => {
match status.get(window.as_str()) {
None => Err(ProductOSError::GenericError(format!("Unable to get status for window - does not exist: {:?}", window))),
Some(s) => Ok(s.status.to_owned())
}
}
}
}
Err(e) => Err(e)
}
}
fn set_status(&self, window: &WindowSelect, status: BrowserStatus) -> Result<(), ProductOSError> {
match self.get_window_id(window) {
Ok(window) => {
match self.status.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to get lock for status: {:?}", window))),
Some(mut statuses) => {
statuses.insert(window, WebClientStatus::new(status));
Ok(())
}
}
}
Err(e) => Err(e)
}
}
fn get_last_request(&self, window: &WindowSelect) -> Result<Option<Request>, ProductOSError> {
match self.get_window_id(window) {
Ok(window) => {
match self.last_request.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to get lock for status: {:?}", window))),
Some(requests) => {
match requests.get(window.as_str()) {
None => Err(ProductOSError::GenericError(format!("Unable to get status for window - does not exist: {:?}", window))),
Some(r) => Ok(r.last_request.to_owned())
}
}
}
}
Err(e) => Err(e)
}
}
fn set_last_request(&self, window: &WindowSelect, last_request: Request) -> Result<(), ProductOSError> {
match self.get_window_id(window) {
Ok(window) => {
match self.last_request.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to get lock for status: {:?}", window))),
Some(mut requests) => {
requests.insert(window, LastRequest::new(last_request));
Ok(())
}
}
}
Err(e) => Err(e)
}
}
async fn init_browser(&mut self) {
tracing::info!("Browser initialized - start");
match self.generate_socket_connector().await {
Ok((mut s, p)) => {
tokio::spawn(async move {
p
});
match self.helpers.try_lock_for(Duration::from_secs(10)) {
None => {}
Some(mut helpers) => {
helpers.push(s.clone())
}
}
let mut socket_server = ProductOSServer::new_with_state(s.clone());
let mut config = socket_server.get_config();
config.network.host = match &self.config.socket_endpoint {
None => String::from("localhost"),
Some(p) => p.to_owned()
};
let socket_endpoint = match &self.config.socket_endpoint {
None => String::from("/socket_endpoint"),
Some(p) => p.to_owned()
};
config.network.port = match self.config.socket_port {
None => u16::try_from(product_os_random::RandomGenerator::get_random_usize_one_time(1025, 65535, &mut None::<product_os_random::RNG>)).unwrap(),
Some(p) => p
};
config.network.secure = true;
let listen_all_interfaces = config.network.listen_all_interfaces;
let port = config.network.port;
socket_server.update_config(config);
socket_server.add_ws_handler(socket_endpoint.as_str(), socket_server_handler);
socket_server.add_handler("/", product_os_server::Method::GET, SocketClient::websocket_status);
match socket_server.create_https_server(false, listen_all_interfaces, Some(port), None, true).await {
Ok(_) => {
tracing::info!("Socket server started successfully on port {:?} and endpoint {:?}", port, socket_endpoint.as_str());
tracing::info!("Browser initialized - done: {:?}", self.get_client_id());
self.init_windows().await;
self.wait_for_actions_for_time(&WindowSelect::Visible, Some(2000)).await;
self.gather_windows().await;
}
Err(e) => { panic!("Problem starting socket server: {:?}", e); }
}
}
Err(e) => panic!("{:?}", e)
}
}
async fn init_windows(&self) {
let initial_window = product_os_random::RandomGenerator::get_random_string_one_time(10, &mut None::<product_os_random::RNG>);
match self.windows.try_lock_for(Duration::new(10, 0)) {
None => panic!("Problem getting windows"),
Some(mut windows) => {
windows.push(initial_window.to_owned());
}
}
match self.set_status(&WindowSelect::Id(initial_window.to_owned()), BrowserStatus::Idle) {
Ok(_) => {}
Err(e) => panic!("Unable to set window status: {:?}", e)
}
match &self.socket_server {
None => {}
Some(socket_server) => {
let socket_instance = RandomGenerator::get_random_string_one_time(10, &mut None::<product_os_random::RNG>);
match socket_server.add_window_socket(initial_window.as_str(), socket_instance.as_str(), None) {
Ok(_) => {
match socket_server.set_visible_window_socket(initial_window.as_str()) {
Ok(_) => {}
Err(e) => {
tracing::error!("Problem setting visible window socket: {:?}", e);
}
}
}
Err(e) => {
tracing::error!("Problem adding window socket: {:?}", e);
}
}
}
}
let active_window_locked = self.active_window.try_lock_for(Duration::new(10, 0));
match active_window_locked {
None => panic!("Problem getting status"),
Some(mut active_window) => { active_window.replace(initial_window); }
}
}
async fn gather_windows(&self) -> Result<String, ProductOSError> {
match &self.socket_server {
None => {
tracing::error!("Failed to get socket server");
Err(ProductOSError::GenericError("Failed to get socket server".to_string()))
}
Some(socket_server) => {
match self.send_message_to_window_socket_and_get_result(&WindowSelect::Visible, "get_windows", "server_action", Value::Null, "application/json", None, None, "confirm_windows").await {
Ok(result) => {
let socket_windows = match result.data {
Value::Array(wins) => {
let mut windows = vec!();
for win in wins {
match win {
Value::String(w) => { windows.push(w); }
_ => {}
}
}
windows
}
_ => {
tracing::error!("Failed to get windows");
vec!()
}
};
tracing::info!("Removing windows already closed");
match self.windows.try_lock_for(Duration::new(10, 0)) {
None => panic!("Problem getting status"),
Some(mut windows) => {
for window_counter in 0..windows.len() {
let window = windows.get(window_counter).unwrap();
if !socket_windows.contains(window) {
let _ = self.remove_status(&WindowSelect::Id(window.to_owned()));
windows.remove(window_counter);
}
}
tracing::info!("Closing redundant windows opened but not needed");
tracing::info!("Current windows: {:?}", socket_windows);
for socket_window in socket_windows {
if !windows.contains(&socket_window) {
match self.send_message_to_window_socket(&WindowSelect::Id(socket_window.to_owned()), "close_window", "server_action", serde_json::Value::String(socket_window), "text/plain", None, None).await {
Ok(_) => {}
Err(e) => {
tracing::error!("Error attempt to close window via socket: {:?}", e);
return Err(e)
}
}
}
}
let window_count = if self.config.multi_window { self.config.windows - windows.len() } else { 1 - windows.len() };
tracing::info!("Opening new windows as needed: {}", window_count + 1);
for _ in 0..window_count {
let window_id = RandomGenerator::get_random_string_one_time(10, &mut None::<product_os_random::RNG>);
match socket_server.get_socket_instance_identifier(window_id.as_str()) {
Ok(socket_instance) => {
match socket_server.add_window_socket(window_id.as_str(), socket_instance.as_str(), None) {
Ok(_) => {
match self.send_message_to_window_socket_and_get_result(&WindowSelect::Visible, "open_window", "server_action", serde_json::Value::String(window_id.to_owned()), "text/plain", None, None, "confirm_window_opened").await {
Ok(_) => {
match socket_server.set_visible_window_socket(window_id.as_str()) {
Ok(_) => {
windows.push(window_id.to_owned());
match self.set_status(&WindowSelect::Id(window_id.to_owned()), BrowserStatus::Idle) {
Ok(_) => {}
Err(e) => {
tracing::error!("Problem setting new window {:?} status: {:?}", window_id, e);
return Err(e)
}
}
}
Err(e) => {
tracing::error!("Failed to open window: {:?}", e);
return Err(e)
}
}
}
Err(e) => {
tracing::error!("Failed to open window: {:?}", e);
return Err(e)
}
}
}
Err(e) => {
tracing::error!("Failed to open window: {:?}", e);
return Err(e)
}
}
}
Err(e) => {
tracing::error!("Failed to open window: {:?}", e);
return Err(e)
}
}
}
match self.send_message_to_window_socket(&WindowSelect::Visible, "maximize_window", "server_action", serde_json::Value::Null, "text/plain", None, None).await {
Ok(_) => {}
Err(_) => {}
}
match windows.get(0) {
None => Err(ProductOSError::GenericError("No windows current available".to_string())),
Some(first) => {
let active_window_locked = self.active_window.try_lock_for(Duration::new(10, 0));
match active_window_locked {
None => panic!("Problem getting status"),
Some(mut active_window) => { active_window.replace(first.to_owned()); }
}
Ok(format!("{:?}", first))
}
}
}
}
}
Err(e) => {
tracing::error!("Failed to get socket windows: {:?}", e);
Err(e)
}
}
}
}
}
fn get_active_window_id(&self) -> Result<String, ProductOSError> {
let active_window_locked = self.active_window.try_lock_for(Duration::new(10, 0));
match active_window_locked {
None => Err(ProductOSError::GenericError(String::from("Unable to get windows"))),
Some(active_window) => {
match active_window.deref() {
Some(window) => Ok(window.to_owned()),
None => Err(ProductOSError::GenericError(String::from("Problem with window ID")))
}
}
}
}
async fn download_content(&self, window: &WindowSelect, request: Request) -> Result<ResponseContent, ProductOSError> {
let browser_window_id = self.get_window_id(window).unwrap_or_else(|_| String::new());
match self.set_status(window, BrowserStatus::Downloading) {
Ok(_) => {}
Err(e) => return Err(e)
}
match self.set_last_request(window, request.to_owned()) {
Ok(_) => {}
Err(e) => return Err(e)
}
let active_window = self.gather_windows().await;
tracing::info!("Windows gathered: {:?}", active_window);
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
match self.set_status(window, BrowserStatus::Error(error.to_owned())) {
Ok(_) => {}
Err(e) => return Err(e)
}
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
tracing::info!("Starting to download: {:?}", request);
match request.method {
Method::GET => {
let mut request_url = request.url.to_owned();
let mut query = match request_url.query() {
None => String::new(),
Some(q) => q.to_string()
};
if query.len() > 0 { query.push_str("&"); }
query.push_str("WindowID");
query.push_str("=");
query.push_str(browser_window_id.as_str());
request_url.set_query(Some(query.as_str()));
let request_url_string = request_url.to_string();
if request.headers.len() > 0 {
match socket_server.add_headers_on_url_match(request.url.as_str(), request.headers) {
Ok(_) => {}
Err(e) => return Err(e)
}
}
tracing::info!("Performing get request from driver: {request_url_string}");
match self.send_message_to_window_socket_and_get_result(window, "goto", "server_action", serde_json::Value::String(request_url_string.to_owned()), "application/json", None, None, "confirm_gone").await {
Ok(_) => {
tracing::info!("Downloaded current content from browser window {browser_window_id} with url {request_url_string}");
match self.update_content(window).await {
Ok(_) => {
match self.set_status(window, BrowserStatus::Idle) {
Ok(_) => self.get_current_content(window).await,
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
Err(e) => {
match self.set_status(window, BrowserStatus::Idle) {
Ok(_) => Err(ProductOSError::GenericError(e.to_string())),
Err(e) => Err(e)
}
}
}
}
_ => {
match self.set_status(window, BrowserStatus::Idle) {
Ok(_) => Err(ProductOSError::GenericError("Method not supported".to_string())),
Err(e) => Err(e)
}
}
}
}
}
}
async fn get_current_content(&self, window: &WindowSelect) -> Result<ResponseContent, ProductOSError> {
match self.send_message_to_window_socket_and_get_result(window, "get_url", "server_action", serde_json::Value::Null, "text/plain", None, None, "confirm_url").await {
Ok(result) => {
let response_url = match result.data {
Value::String(url) => {
match Url::parse(url.as_str()) {
Ok(url) => url,
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
}
}
_ => return Err(ProductOSError::GenericError(String::from("Response url invalid")))
};
match self.send_message_to_window_socket_and_get_result(window, "get_current_content", "server_action", serde_json::Value::Null, "text/plain", None, None, "confirm_current_content").await {
Ok(result) => {
let config = self.config.clone();
let request = match &self.get_last_request(window) {
Ok(request) => match request.to_owned() {
None => return Err(ProductOSError::GenericError(String::from("No request saved correctly"))),
Some(r) => r
},
Err(e) => return Err(e.to_owned()),
};
let html_string = match result.data {
Value::String(current_content) => current_content,
_ => return Err(ProductOSError::GenericError(String::from("Response url invalid")))
};
let client = self.clone();
let window_id = match self.get_window_id(window) {
Ok(w) => w,
Err(e) => return Err(e)
};
let current_content = ResponseContent::new(request.to_owned(), chrono::Utc::now(), response_url, html_string.as_str(), Arc::new(client), window_id, config);
match self.set_current_cached_content(window, current_content.clone()) {
Ok(_) => {
tracing::info!("Successfully obtained current content: {:?}", request.url);
Ok(current_content)
}
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
fn get_cached_content(&self, window: &WindowSelect) -> Result<ResponseContent, ProductOSError> {
match self.get_window_id(window) {
Err(e) => Err(e),
Ok(w) => {
match self.cached_content.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to get cached current_content lock: {:?}", w))),
Some(cached_contents) => {
match cached_contents.get(w.as_str()) {
None => Err(ProductOSError::GenericError(format!("No cached current_content exists for window: {:?}", w))),
Some(c) => {
match c.get_content() {
None => Err(ProductOSError::GenericError(format!("No current_content cached for window: {:?}", w))),
Some(current_content) => Ok(current_content)
}
}
}
}
}
}
}
}
async fn update_content(&self, window: &WindowSelect) -> Result<ResponseContent, ProductOSError> {
match self.send_message_to_window_socket_and_get_result(window, "get_url", "server_action", serde_json::Value::Null, "text/plain", None, None, "confirm_url").await {
Ok(result) => {
let current_url = match result.data {
Value::String(url) => {
match Url::parse(url.as_str()) {
Ok(url) => url,
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
}
}
_ => return Err(ProductOSError::GenericError(String::from("Response url invalid")))
};
match self.send_message_to_window_socket_and_get_result(window, "get_current_content", "server_action", serde_json::Value::Null, "text/plain", None, None, "confirm_current_content").await {
Ok(result) => {
let config = self.config.clone();
let request = match &self.get_last_request(window) {
Ok(request) => match request.to_owned() {
None => return Err(ProductOSError::GenericError(String::from("No request saved correctly"))),
Some(r) => r
},
Err(e) => return Err(e.to_owned()),
};
let html_string = match result.data {
Value::String(current_content) => current_content,
_ => return Err(ProductOSError::GenericError(String::from("Response url invalid")))
};
let window_id = match self.get_window_id(window) {
Ok(w) => w,
Err(e) => return Err(e)
};
let current_content = ResponseContent::new(request.to_owned(), chrono::Utc::now(), current_url.to_owned(), html_string.as_str(), Arc::new(self.clone()), window_id, config);
match self.set_current_cached_content(window, current_content.clone()) {
Ok(_) => {
tracing::info!("Successfully updated DOM: {:?}", request.url);
Ok(current_content)
}
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
async fn wait_for_actions(&self, window: &WindowSelect) {
if self.config.wait_for_actions {
tracing::info!("Attempt to wait for actions");
tokio::time::sleep(core::time::Duration::from_millis(1000)).await;
}
}
async fn wait_for_actions_for_time(&self, window: &WindowSelect, millis: Option<usize>) {
if self.config.wait_for_actions {
let millis: usize = match millis {
None => 0,
Some(m) => m
};
tracing::info!("Attempt to wait for actions for time {}", millis);
tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(millis).unwrap())).await;
}
}
async fn wait_for_time(&self, window: &WindowSelect, millis: Option<usize>) {
let millis: usize = match millis {
None => 1000,
Some(m) => m
};
tracing::info!("Attempt to wait for time {}", millis);
tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(millis).unwrap())).await;
}
async fn wait_until_visible(&self, window: &WindowSelect, search: String, search_kind: SearchKind, wait: Option<usize>, timeout: Option<usize>, which: Option<usize>) -> Result<(), ProductOSError> {
let wait: usize = match wait {
None => 1000,
Some(w) => w
};
let timeout: usize = match timeout {
None => 60000,
Some(t) => t
};
let which: usize = match which {
None => 0,
Some(c) => c - 1
};
tracing::info!("Attempt to wait until # {} visible {}", which, search);
let mut total_time = 0;
loop {
match self.search_all(window, search.to_owned(), search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
let mut visible_counter = 0;
tracing::debug!("Elements found {:?}", results.len());
for res in &results {
match res {
ResultFormat::Element(el) => {
if visible_counter == which {
match el.is_visible(Behaviour::default()).await {
Ok(result) => {
tracing::debug!("Element visible check {}", result);
if result {
return Ok(())
}
else {
total_time = total_time + wait;
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
}
Err(e) => {
tracing::error!("Failed to search for elements {}", e);
return Err(ProductOSError::GenericError(e.to_string()))
}
}
}
visible_counter += 1;
}
_ => { todo!("Not yet implemented") }
}
}
}
Err(e) => {
tracing::error!("Failed to search for elements {}", e);
return Err(e)
}
}
}
}
async fn wait_until_invisible(&self, window: &WindowSelect, search: String, search_kind: SearchKind, wait: Option<usize>, timeout: Option<usize>, which: Option<usize>) -> Result<(), ProductOSError> {
let wait: usize = match wait {
None => 1000,
Some(w) => w
};
let timeout: usize = match timeout {
None => 60000,
Some(t) => t
};
let which: usize = match which {
None => 0,
Some(c) => c - 1
};
tracing::info!("Attempt to wait until # {} invisible {}", which, search);
let mut total_time = 0;
loop {
match self.search_all(window, search.to_owned(), search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
let mut visible_counter = 0;
tracing::debug!("Elements found {:?}", results.len());
for res in &results {
match res {
ResultFormat::Element(el) => {
if visible_counter == which {
match el.is_visible(Behaviour::default()).await {
Ok(result) => {
tracing::debug!("Element invisible check {}", result);
if !result {
return Ok(())
}
else {
total_time = total_time + wait;
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to disappear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
}
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
}
}
}
_ => { todo!("Not yet implemented") }
}
}
}
Err(e) => return Err(e)
}
}
}
async fn wait_until_more_visible(&self, window: &WindowSelect, search: String, search_kind: SearchKind, wait: Option<usize>, timeout: Option<usize>, count: Option<usize>) -> Result<(), ProductOSError> {
let wait: usize = match wait {
None => 1000,
Some(w) => w
};
let timeout: usize = match timeout {
None => 60000,
Some(t) => t
};
let count: usize = match count {
None => 1,
Some(c) => c
};
tracing::info!("Attempt to wait until at least {} visible {}", count, search);
let mut total_time = 0;
loop {
match self.search_all(window, search.to_owned(), search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
let mut is_visible = true;
tracing::debug!("Elements found {:?}", results.len());
for res in &results {
match res {
ResultFormat::Element(el) => {
match el.is_visible(Behaviour::default()).await {
Ok(result) => {
tracing::debug!("Element visible check {}", result);
if !result { is_visible = false; }
}
Err(e) => {
tracing::error!("Failed to search for elements {}", e);
return Err(ProductOSError::GenericError(e.to_string()))
}
}
}
_ => { todo!("Not yet implemented") }
}
}
tracing::debug!("Elements count {} and visible check {}", results.len(), is_visible);
if results.len() < count || !is_visible {
total_time = total_time + wait;
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
else {
tracing::info!("Met criteria - More elements found");
return Ok(())
}
}
Err(e) => {
tracing::error!("Failed to search for elements {}", e);
return Err(e)
}
}
}
}
async fn wait_until_less_visible(&self, window: &WindowSelect, search: String, search_kind: SearchKind, wait: Option<usize>, timeout: Option<usize>, count: Option<usize>) -> Result<(), ProductOSError> {
let wait: usize = match wait {
None => 1000,
Some(w) => w
};
let timeout: usize = match timeout {
None => 60000,
Some(t) => t
};
let count: usize = match count {
None => 0,
Some(c) => c
};
tracing::info!("Attempt to wait until at most {} visible {}", count, search);
let mut total_time = 0;
loop {
match self.search_all(window, search.to_owned(), search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
let mut is_visible = false;
let check_counter = 1;
tracing::debug!("Elements found {:?}", results.len());
for res in &results {
match res {
ResultFormat::Element(el) => {
match el.is_visible(Behaviour::default()).await {
Ok(result) => {
tracing::debug!("Element visible check {}", result);
if result && check_counter >= count { is_visible = true; }
}
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
}
}
_ => { todo!("Not yet implemented") }
}
}
tracing::debug!("Elements count {} and visible check {}", results.len(), is_visible);
if results.len() > count && is_visible {
total_time = total_time + wait;
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to disappear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
else {
tracing::info!("Met criteria - Less elements found");
return Ok(())
}
}
Err(e) => return Err(e)
}
}
}
async fn wait_until_on_screen(&self, window: &WindowSelect, search: String, search_kind: SearchKind, wait: Option<usize>, timeout: Option<usize>, count: Option<usize>) -> Result<(), ProductOSError> {
let wait: usize = match wait {
None => 1000,
Some(w) => w
};
let timeout: usize = match timeout {
None => 60000,
Some(t) => t
};
let count: usize = match count {
None => 1,
Some(c) => c
};
tracing::info!("Attempt to wait until at least {} on screen {}", count, search);
let mut total_time = 0;
loop {
match self.search_all(window, search.to_owned(), search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
let mut is_on_screen = true;
tracing::debug!("Elements found {:?}", results.len());
for res in &results {
match res {
ResultFormat::Element(el) => {
match el.is_on_screen(Behaviour::default()).await {
Ok(result) => {
tracing::debug!("Element on screen check {}", result);
if !result { is_on_screen = false; }
}
Err(e) => {
tracing::error!("Failed to search for elements {}", e);
return Err(ProductOSError::GenericError(e.to_string()))
}
}
}
_ => { todo!("Not yet implemented") }
}
}
tracing::debug!("Elements count {} and on screen check {}", results.len(), is_on_screen);
if results.len() < count || !is_on_screen {
total_time = total_time + wait;
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
else {
tracing::info!("Met criteria - More elements found");
return Ok(())
}
}
Err(e) => {
tracing::error!("Failed to search for elements {}", e);
return Err(e)
}
}
}
}
async fn wait_until_match(&self, window: &WindowSelect, search: String, search_kind: SearchKind, wait: Option<usize>, timeout: Option<usize>, regex: Option<String>, which: usize, look_in: String) -> Result<(), ProductOSError> {
let wait: usize = match wait {
None => 1000,
Some(w) => w
};
let timeout: usize = match timeout {
None => 60000,
Some(t) => t
};
let regex: Regex = match regex {
None => return Err(ProductOSError::GenericError(String::from(format!("Regex not provided - cannot do match")))),
Some(rx) => {
match Regex::new(rx.as_str()) {
Ok(r) => r,
Err(e) => return Err(ProductOSError::GenericError(String::from(format!("Regex not valid {} - {}", rx, e))))
}
}
};
tracing::info!("Attempt to wait until match {} visible {} in {}", regex, search, look_in);
let mut total_time = 0;
loop {
match self.search_all(window, search.to_owned(), search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
total_time = total_time + wait;
if results.len() <= which {
if total_time > timeout {
return Err(ProductOSError::GenericError(format!("Timed out waiting for element to appear: {}", search)));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
else {
match results.get(which) {
None => return Err(ProductOSError::GenericError(format!("Unable to find element at position: {}", which))),
Some(result) => {
match result {
ResultFormat::Element(element) => {
match look_in.as_str() {
"text" => {
let val = element.raw_html();
if regex.is_match(val.as_str()) {
tracing::info!("Met criteria - match");
return Ok(())
}
else {
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
}
"value" | _ => {
let val = element.get_attribute(String::from("value"));
match val {
None => {
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
Some(v) => {
if regex.is_match(v.as_str()) {
tracing::info!("Met criteria - match");
return Ok(())
}
else {
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
}
}
}
}
}
_ => { todo!("Not yet implemented") }
}
}
}
}
}
Err(e) => return Err(e)
}
}
}
async fn wait_until_not_match(&self, window: &WindowSelect, search: String, search_kind: SearchKind, wait: Option<usize>, timeout: Option<usize>, regex: Option<String>, which: usize, look_in: String) -> Result<(), ProductOSError> {
let wait: usize = match wait {
None => 1000,
Some(w) => w
};
let timeout: usize = match timeout {
None => 60000,
Some(t) => t
};
let regex: Regex = match regex {
None => return Err(ProductOSError::GenericError(String::from(format!("Regex not provided - cannot do match")))),
Some(rx) => {
match Regex::new(rx.as_str()) {
Ok(r) => r,
Err(e) => return Err(ProductOSError::GenericError(String::from(format!("Regex not valid {:?} - {}", rx, e))))
}
}
};
tracing::info!("Attempt to wait until not match {} visible {} in {}", regex, search, look_in);
let mut total_time = 0;
loop {
match self.search_all(window, search.to_owned(), search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
total_time = total_time + wait;
if results.len() <= which {
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
else {
match results.get(which) {
None => return Err(ProductOSError::GenericError(format!("Unable to find element at position: {}", which))),
Some(result) => {
match result {
ResultFormat::Element(element) => {
match look_in.as_str() {
"text" => {
let val = element.raw_html();
if !regex.is_match(val.as_str()) {
tracing::info!("Met criteria - no match");
return Ok(())
}
else {
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
}
"value" | _ => {
let val = element.get_attribute(String::from("value"));
match val {
None => {
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
Some(v) => {
if !regex.is_match(v.as_str()) {
tracing::info!("Met criteria - no match");
return Ok(())
}
else {
if total_time > timeout {
return Err(ProductOSError::GenericError(String::from(format!("Timed out waiting for element to appear: {}", search))));
} else { tokio::time::sleep(core::time::Duration::from_millis(u64::try_from(wait).unwrap())).await; }
}
}
}
}
}
}
_ => { todo!("Not yet implemented") }
}
}
}
}
}
Err(e) => return Err(e)
}
}
}
async fn execute_script_for_result(&self, window: &WindowSelect, script: String, args: Vec<serde_json::Value>) -> Result<serde_json::Value, ProductOSError> {
todo!()
}
async fn execute_script_for_current_content(&self, window: &WindowSelect, script: String, args: Vec<serde_json::Value>) -> Result<(), ProductOSError> {
match self.send_message_to_window_socket_and_get_result(window, "action", "server_action", serde_json::Value::String(script), "text/plain", None, None, "action_done").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
async fn alert(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize) -> Result<(), ProductOSError> {
tracing::info!("Attempt to alert content based on search {:?}", search);
let scope_present = search.split("%%").collect::<Vec<&str>>();
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
match self.send_message_to_window_socket_and_get_result(window, "alert", "server_action", action, "application/json", None, None, "alert_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
async fn focus(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<(), ProductOSError> {
tracing::info!("Attempt to focus on element {} at position {}", search, which);
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search_query = ElementSearchQuery {
search_query: if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() },
target: if scope_present.len() > 1 { Some(scope_present.get(0).unwrap().to_string()) } else { None },
search_kind,
which,
result_format: SearchResultFormat::Element
};
match self.scroll_to(window, search_query.search_query.to_owned(), search_query.search_kind.to_owned(), search_query.which.to_owned(), behaviour.to_owned()).await {
Ok(_) => {
let search_query_json = match serde_json::value::to_value(search_query.to_owned()) {
Ok(result) => result,
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
};
let behaviour_kind_json = match serde_json::value::to_value(behaviour.kind.to_owned()) {
Ok(result) => result,
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
};
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.waiting_range.start, behaviour.waiting_range.end, &mut None::<product_os_random::RNG>);
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search"), search_query_json);
action_map.insert(String::from("behaviour_kind"), behaviour_kind_json);
action_map.insert(String::from("wait_time"), serde_json::Value::Number(serde_json::Number::from(wait_time)));
let action = serde_json::Value::Object(action_map);
match self.send_message_to_window_socket_and_get_result(window, "focus", "server_action", action, "application/json", None, None, "focus_done").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
async fn scroll_to(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<(), ProductOSError> {
tracing::info!("Attempt to scroll to element {} at position {}", search, which);
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search_query = ElementSearchQuery {
search_query: if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() },
target: if scope_present.len() > 1 { Some(scope_present.get(0).unwrap().to_string()) } else { None },
search_kind,
which,
result_format: SearchResultFormat::Element
};
let search_query_json = match serde_json::value::to_value(search_query.to_owned()) {
Ok(result) => result,
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
};
let behaviour_kind_json = match serde_json::value::to_value(behaviour.kind.to_owned()) {
Ok(result) => result,
Err(e) => return Err(ProductOSError::GenericError(e.to_string()))
};
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.waiting_range.start, behaviour.waiting_range.end, &mut None::<product_os_random::RNG>);
match self.send_message_to_window_socket_and_get_result(window, "position", "server_action", search_query_json.to_owned(), "application/json", None, None, "position_result").await {
Ok(result) => {
let position = match result.data {
Value::Object(map) => SocketClient::get_position_data(map),
_ => return Err(ProductOSError::GenericError(String::from("Invalid response for is visible check")))
};
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("x"), serde_json::Value::Number(serde_json::Number::from_f64(position.x).unwrap()));
action_map.insert(String::from("y"), serde_json::Value::Number(serde_json::Number::from_f64(position.y).unwrap()));
action_map.insert(String::from("behaviour_kind"), behaviour_kind_json);
action_map.insert(String::from("wait_time"), serde_json::Value::Number(serde_json::Number::from(wait_time)));
let action = serde_json::Value::Object(action_map);
match self.send_message_to_window_socket_and_get_result(window, "scroll_to", "server_action", action, "application/json", None, None, "scroll_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
async fn focus_on_iframe(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<(), ProductOSError> {
todo!()
}
async fn take_screenshot(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<Vec<u8>, ProductOSError> {
todo!()
}
async fn find(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, result_format: SearchResultFormat) -> Result<ResultFormat, ProductOSError> {
self.search(window, search, search_kind, which, result_format).await
}
async fn find_all(&self, window: &WindowSelect, search: String, search_kind: SearchKind, result_format: SearchResultFormat) -> Result<Vec<ResultFormat>, ProductOSError> {
self.search_all(window, search, search_kind, result_format).await
}
async fn click(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<(), ProductOSError> {
tracing::info!("Attempt to click on element {} at position {}", search, which);
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.waiting_range.start, behaviour.waiting_range.end, &mut None::<product_os_random::RNG>);
self.wait_for_time(window, Some(wait_time)).await;
let scope_present = search.split("%%").collect::<Vec<&str>>();
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
match self.send_message_to_window_socket_and_get_result(window, "click", "server_action", action, "application/json", None, None, "click_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
async fn double_click(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<(), ProductOSError> {
tracing::info!("Attempt to double click on element {} at position {}", search, which);
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.waiting_range.start, behaviour.waiting_range.end, &mut None::<product_os_random::RNG>);
self.wait_for_time(window, Some(wait_time)).await;
let scope_present = search.split("%%").collect::<Vec<&str>>();
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
match self.send_message_to_window_socket_and_get_result(window, "double_click", "server_action", action, "application/json", None, None, "double_click_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
async fn right_click(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<(), ProductOSError> {
tracing::info!("Attempt to right click on element {} at position {}", search, which);
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.waiting_range.start, behaviour.waiting_range.end, &mut None::<product_os_random::RNG>);
self.wait_for_time(window, Some(wait_time)).await;
let scope_present = search.split("%%").collect::<Vec<&str>>();
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
match self.send_message_to_window_socket_and_get_result(window,"right_click", "server_action", action, "application/json", None, None, "right_click_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
async fn click_and_hold(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<(), ProductOSError> {
todo!()
}
async fn release(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<(), ProductOSError> {
todo!()
}
async fn type_text(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, text: String, behaviour: Behaviour) -> Result<(), ProductOSError> {
tracing::info!("Attempt to type text {} into element {} at position {}", text, search, which);
match &behaviour.kind {
BehaviourKind::Robot => {
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.waiting_range.start, behaviour.waiting_range.end, &mut None::<product_os_random::RNG>);
self.wait_for_time(window, Some(wait_time)).await;
let scope_present = search.split("%%").collect::<Vec<&str>>();
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
action_map.insert(String::from("text"), serde_json::Value::String(text));
let action = serde_json::Value::Object(action_map);
match self.send_message_to_window_socket_and_get_result(window, "type_text", "server_action", action, "application/json", None, None, "type_text_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
},
BehaviourKind::Human => {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
match self.click(window, search.to_owned(), search_kind.to_owned(), which, behaviour.to_owned()).await {
Ok(_) => {
for character in text.chars() {
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.typing_range.start, behaviour.typing_range.end, &mut None::<product_os_random::RNG>);
self.wait_for_time(window, Some(wait_time)).await;
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search.to_owned()));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target.to_owned()));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
action_map.insert(String::from("text"), serde_json::Value::String(character.to_string()));
let action = serde_json::Value::Object(action_map);
match self.send_message_to_window_socket_and_get_result(window, "type_text", "server_action", action, "application/json", None, None, "type_text_result").await {
Ok(_) => {},
Err(e) => return Err(e)
}
}
Ok(())
}
Err(e) => Err(e)
}
}
}
}
async fn select_by_visible_text(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, text: String, behaviour: Behaviour) -> Result<(), ProductOSError> {
match &behaviour.kind {
BehaviourKind::Robot => {
tracing::info!("Attempt to set selection option by visible text {} for element {} at position {}", text, search, which);
match self.search(window, search.to_owned(), search_kind.to_owned(), which, SearchResultFormat::Element).await {
Ok(select_result) => {
match select_result {
ResultFormat::Element(select_result) => {
match select_result.search_all(search.to_owned() + " " + "option", search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
let child_counter = 1;
for result in results {
match result {
ResultFormat::Element(option) => {
match option.text() {
None => {}
Some(option_text) => {
if option_text == text {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search_option = search.to_owned() + " option:nth-child(" + child_counter.to_string().as_str() + ")";
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search_option.to_owned()));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target.to_owned()));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
return match self.send_message_to_window_socket_and_get_result(window, "select_set", "server_action", action, "application/json", None, None, "select_set_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
}
}
_ => return Err(ProductOSError::GenericError(String::from("Different result format for option received and unexpected")))
}
}
Err(ProductOSError::GenericError(String::from("Select option not found")))
}
Err(e) => Err(e)
}
}
_ => Err(ProductOSError::GenericError(String::from("Different result format received and unexpected")))
}
},
Err(e) => Err(e)
}
},
BehaviourKind::Human => {
tracing::info!("Attempt to focus on element {} at position {}", search, which);
match self.click(window, search.to_owned(), search_kind.to_owned(), which, behaviour.to_owned()).await {
Ok(_) => {
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.typing_range.start, behaviour.typing_range.end, &mut None::<product_os_random::RNG>);
self.wait_for_time(window, Some(wait_time)).await;
tracing::info!("Attempt to set selection option by visible text {} for element {} at position {}", text, search, which);
match self.search(window, search.to_owned(), search_kind.to_owned(), which, SearchResultFormat::Element).await {
Ok(select_result) => {
match select_result {
ResultFormat::Element(select_result) => {
match select_result.search_all(search.to_owned() + " " + "option", search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
let child_counter = 1;
for result in results {
match result {
ResultFormat::Element(option) => {
match option.text() {
None => {}
Some(option_text) => {
if option_text == text {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search_option = search.to_owned() + " option:nth-child(" + child_counter.to_string().as_str() + ")";
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search_option.to_owned()));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target.to_owned()));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
return match self.send_message_to_window_socket_and_get_result(window, "select_click", "server_action", action, "application/json", None, None, "select_click_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
}
}
_ => return Err(ProductOSError::GenericError(String::from("Different result format for option received and unexpected")))
}
}
Err(ProductOSError::GenericError(String::from("Select option not found")))
}
Err(e) => Err(e)
}
}
_ => Err(ProductOSError::GenericError(String::from("Different result format received and unexpected")))
}
},
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
}
}
async fn select_by_value(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, value: String, behaviour: Behaviour) -> Result<(), ProductOSError> {
match &behaviour.kind {
BehaviourKind::Robot => {
tracing::info!("Attempt to set selection option by value {} for element {} at position {}", value, search, which);
match self.search(window, search.to_owned(), search_kind.to_owned(), which, SearchResultFormat::Element).await {
Ok(select_result) => {
match select_result {
ResultFormat::Element(select_result) => {
match select_result.search_all(search.to_owned() + " " + "option", search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
let child_counter = 1;
for result in results {
match result {
ResultFormat::Element(option) => {
match option.value() {
None => {}
Some(option_value) => {
if option_value == value {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search_option = search.to_owned() + " option:nth-child(" + child_counter.to_string().as_str() + ")";
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search_option.to_owned()));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target.to_owned()));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
return match self.send_message_to_window_socket_and_get_result(window, "select_set", "server_action", action, "application/json", None, None, "select_set_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
}
}
_ => return Err(ProductOSError::GenericError(String::from("Different result format for option received and unexpected")))
}
}
Err(ProductOSError::GenericError(String::from("Select option not found")))
}
Err(e) => Err(e)
}
}
_ => Err(ProductOSError::GenericError(String::from("Different result format received and unexpected")))
}
},
Err(e) => Err(e)
}
},
BehaviourKind::Human => {
tracing::info!("Attempt to focus on element {} at position {}", search, which);
match self.click(window, search.to_owned(), search_kind.to_owned(), which, behaviour.to_owned()).await {
Ok(_) => {
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.typing_range.start, behaviour.typing_range.end, &mut None::<product_os_random::RNG>);
self.wait_for_time(window, Some(wait_time)).await;
tracing::info!("Attempt to set selection option by value {} for element {} at position {}", value, search, which);
match self.search(window, search.to_owned(), search_kind.to_owned(), which, SearchResultFormat::Element).await {
Ok(select_result) => {
match select_result {
ResultFormat::Element(select_result) => {
match select_result.search_all(search.to_owned() + " " + "option", search_kind.to_owned(), SearchResultFormat::Element).await {
Ok(results) => {
let child_counter = 1;
for result in results {
match result {
ResultFormat::Element(option) => {
match option.value() {
None => {}
Some(option_value) => {
if option_value == value {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search_option = search.to_owned() + " option:nth-child(" + child_counter.to_string().as_str() + ")";
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search_option.to_owned()));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target.to_owned()));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
return match self.send_message_to_window_socket_and_get_result(window, "select_click", "server_action", action, "application/json", None, None, "select_click_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
}
}
_ => return Err(ProductOSError::GenericError(String::from("Different result format for option received and unexpected")))
}
}
Err(ProductOSError::GenericError(String::from("Select option not found")))
}
Err(e) => Err(e)
}
}
_ => Err(ProductOSError::GenericError(String::from("Different result format received and unexpected")))
}
},
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
}
}
async fn select_by_position(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, position: i64, behaviour: Behaviour) -> Result<(), ProductOSError> {
match &behaviour.kind {
BehaviourKind::Robot => {
tracing::info!("Attempt to set selection option by position {} for element {} at position {}", position, search, which);
match self.search(window, search.to_owned(), search_kind.to_owned(), which, SearchResultFormat::Element).await {
Ok(select_result) => {
match select_result {
ResultFormat::Element(select_result) => {
match usize::try_from(position) {
Ok(pos) => {
match select_result.search(search.to_owned() + " " + "option", search_kind.to_owned(), pos.to_owned(), SearchResultFormat::Element).await {
Ok(_) => {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search_option = search.to_owned() + " option:nth-child(" + pos.to_string().as_str() + ")";
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search_option.to_owned()));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target.to_owned()));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
return match self.send_message_to_window_socket_and_get_result(window, "select_set", "server_action", action, "application/json", None, None, "select_set_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
Err(e) => Err(ProductOSError::GenericError(e.to_string()))
}
}
_ => Err(ProductOSError::GenericError(String::from("Different result format received and unexpected")))
}
},
Err(e) => Err(e)
}
},
BehaviourKind::Human => {
tracing::info!("Attempt to focus on element {} at position {}", search, which);
match self.click(window, search.to_owned(), search_kind.to_owned(), which, behaviour.to_owned()).await {
Ok(_) => {
let wait_time = product_os_random::RandomGenerator::get_random_usize_one_time(behaviour.typing_range.start, behaviour.typing_range.end, &mut None::<product_os_random::RNG>);
self.wait_for_time(window, Some(wait_time)).await;
tracing::info!("Attempt to set selection option by position {} for element {} at position {}", position, search, which);
match self.search(window, search.to_owned(), search_kind.to_owned(), which, SearchResultFormat::Element).await {
Ok(select_result) => {
match select_result {
ResultFormat::Element(select_result) => {
match usize::try_from(position) {
Ok(pos) => {
match select_result.search(search.to_owned() + " " + "option", search_kind.to_owned(), pos.to_owned(), SearchResultFormat::Element).await {
Ok(_) => {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search = if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() };
let target = if scope_present.len() > 1 { scope_present.get(0).unwrap().to_string() } else { String::new() };
let search_option = search.to_owned() + " option:nth-child(" + pos.to_string().as_str() + ")";
let mut action_map = serde_json::Map::new();
action_map.insert(String::from("search_query"), serde_json::Value::String(search_option.to_owned()));
action_map.insert(String::from("search_kind"), serde_json::Value::String(search_kind.to_string()));
action_map.insert(String::from("target"), serde_json::Value::String(target.to_owned()));
action_map.insert(String::from("which"), serde_json::Value::Number(serde_json::Number::from(which)));
let action = serde_json::Value::Object(action_map);
return match self.send_message_to_window_socket_and_get_result(window, "select_click", "server_action", action, "application/json", None, None, "select_click_result").await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
Err(e) => Err(ProductOSError::GenericError(e.to_string()))
}
}
_ => Err(ProductOSError::GenericError(String::from("Different result format received and unexpected")))
}
},
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
}
}
}
async fn select_by_xpath(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, xpath_bracket_query: String, behaviour: Behaviour) -> Result<(), ProductOSError> {
todo!()
}
async fn forward(&self, window: &WindowSelect) -> Result<(), ProductOSError> {
tracing::info!("Attempt to go forward on current content");
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
let window_id = match self.get_window_id(window) {
Ok(w) => w,
Err(e) => return Err(e)
};
match socket_server.send_message_to_window_socket(window_id, "forward", "server_action", serde_json::Value::Null, "text/plain", None, None).await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
}
async fn back(&self, window: &WindowSelect) -> Result<(), ProductOSError> {
tracing::info!("Attempt to go backward on current content");
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
let window_id = match self.get_window_id(window) {
Ok(w) => w,
Err(e) => return Err(e)
};
match socket_server.send_message_to_window_socket(window_id, "back", "server_action", serde_json::Value::Null, "text/plain", None, None).await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
}
async fn refresh(&self, window: &WindowSelect) -> Result<(), ProductOSError> {
tracing::info!("Attempt to refresh current content");
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
let window_id = match self.get_window_id(window) {
Ok(w) => w,
Err(e) => return Err(e)
};
match socket_server.send_message_to_window_socket(window_id, "refresh", "server_action", serde_json::Value::Null, "text/plain", None, None).await {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
}
async fn open(&self, window: &WindowSelect, url: String) -> Result<(), ProductOSError> {
tracing::info!("Attempt to open new tab with url");
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
match self.send_message_to_window_socket_and_get_result(window, "open_url", "server_action", serde_json::Value::String(url), "text/plain", None, None, "open_url_result").await {
Ok(result) => {
match result.data {
Value::String(new_window) => {
match self.active_window.try_lock_for(Duration::from_secs(10)) {
None => Err(ProductOSError::GenericError(String::from("Problem locking active window"))),
Some(mut active_window) => {
active_window.replace(new_window.to_owned());
socket_server.set_visible_window_socket(new_window.as_str())
}
}
},
_ => Err(ProductOSError::GenericError(String::from("Not a valid window option")))
}
},
Err(e) => Err(e)
}
}
}
}
async fn close(&self, window: &WindowSelect) -> Result<(), ProductOSError> {
tracing::info!("Attempt to close current window or tab and return to the parent window / tab");
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
match self.send_message_to_window_socket_and_get_result(window, "close_window", "server_action", serde_json::Value::Null, "text/plain", None, None, "confirm_closed_window").await {
Ok(result) => {
match self.active_window.try_lock_for(Duration::new(10, 0)) {
None => return Err(ProductOSError::GenericError(String::from("Unable to get lock on active window"))),
Some(active_window) => {
let window = match self.get_window_id(window) {
Ok(w) => w,
Err(_) => String::from("no_window_id")
};
let active_window = match &*active_window {
None => String::from("no_active_window_id"),
Some(w) => w.to_owned(),
};
if window == *active_window {
match result.data {
Value::String(parent_window) => {
match self.active_window.try_lock_for(Duration::from_secs(10)) {
None => Err(ProductOSError::GenericError(String::from("Problem locking active window"))),
Some(mut active_window) => {
active_window.replace(parent_window.to_owned());
match socket_server.set_visible_window_socket(parent_window.as_str()) {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
},
_ => Err(ProductOSError::GenericError(String::from("Not a valid window option")))
}
}
else {
Ok(())
}
}
}
},
Err(e) => Err(e)
}
}
}
}
async fn switch(&self, window: &WindowSelect) -> Result<(), ProductOSError> {
match self.has_window(window) {
Ok(has_window) => {
if has_window {
match window {
WindowSelect::Visible => Ok(()),
WindowSelect::Id(window) => {
match self.active_window.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to lock and get active window: {:?}", window))),
Some(mut active_window) => {
active_window.replace(window.to_owned());
Ok(())
}
}
}
WindowSelect::Count(position) => {
match self.get_windows() {
Ok(windows) => {
match windows.get(position.to_owned() as usize) {
None => {
Err(ProductOSError::GenericError(format!("Window for switch not found from position: {:?}", window)))
}
Some(window) => {
match self.active_window.try_lock_for(Duration::new(10, 0)) {
None => Err(ProductOSError::GenericError(format!("Unable to lock and get active window: {:?}", window))),
Some(mut active_window) => {
active_window.replace(window.to_owned());
Ok(())
}
}
}
}
}
Err(e) => Err(e)
}
}
}
}
else {
Err(ProductOSError::GenericError(format!("Window for switch not found: {:?}", window)))
}
}
Err(e) => Err(e)
}
}
async fn is_visible(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<bool, ProductOSError> {
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search_query = ElementSearchQuery {
search_query: if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() },
target: if scope_present.len() > 1 { Some(scope_present.get(0).unwrap().to_string()) } else { None },
search_kind,
which,
result_format: SearchResultFormat::Element
};
match serde_json::value::to_value(search_query.to_owned()) {
Ok(search_query_json) => {
match self.send_message_to_window_socket_and_get_result(window, "is_visible", "server_action", search_query_json, "application/json", None, None, "visible_result").await {
Ok(result) => {
match result.data {
Value::Bool(b) => Ok(b),
_ => Err(ProductOSError::GenericError(String::from("Invalid response for is visible check")))
}
}
Err(e) => Err(e)
}
}
Err(e) => Err(ProductOSError::GenericError(e.to_string()))
}
}
}
}
async fn is_on_screen(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<bool, ProductOSError> {
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search_query = ElementSearchQuery {
search_query: if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() },
target: if scope_present.len() > 1 { Some(scope_present.get(0).unwrap().to_string()) } else { None },
search_kind,
which,
result_format: SearchResultFormat::Element
};
match serde_json::value::to_value(search_query.to_owned()) {
Ok(search_query_json) => {
match self.send_message_to_window_socket_and_get_result(window, "is_on_screen", "server_action", search_query_json, "application/json", None, None, "on_screen_result").await {
Ok(result) => {
match result.data {
Value::Bool(b) => Ok(b),
_ => Err(ProductOSError::GenericError(String::from("Invalid response for is visible check")))
}
}
Err(e) => Err(e)
}
}
Err(e) => Err(ProductOSError::GenericError(e.to_string()))
}
}
}
}
async fn is_selected(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<bool, ProductOSError> {
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search_query = ElementSearchQuery {
search_query: if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() },
target: if scope_present.len() > 1 { Some(scope_present.get(0).unwrap().to_string()) } else { None },
search_kind,
which,
result_format: SearchResultFormat::Element
};
match serde_json::value::to_value(search_query.to_owned()) {
Ok(search_query_json) => {
match self.send_message_to_window_socket_and_get_result(window, "is_selected", "server_action", search_query_json, "application/json", None, None, "selected_result").await {
Ok(result) => {
match result.data {
Value::Bool(b) => Ok(b),
_ => Err(ProductOSError::GenericError(String::from("Invalid response for is visible check")))
}
}
Err(e) => Err(e)
}
}
Err(e) => Err(ProductOSError::GenericError(e.to_string()))
}
}
}
}
async fn is_clickable(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<bool, ProductOSError> {
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search_query = ElementSearchQuery {
search_query: if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() },
target: if scope_present.len() > 1 { Some(scope_present.get(0).unwrap().to_string()) } else { None },
search_kind,
which,
result_format: SearchResultFormat::Element
};
match serde_json::value::to_value(search_query.to_owned()) {
Ok(search_query_json) => {
match self.send_message_to_window_socket_and_get_result(window, "is_clickable", "server_action", search_query_json, "application/json", None, None, "clickable_result").await {
Ok(result) => {
match result.data {
Value::Bool(b) => Ok(b),
_ => Err(ProductOSError::GenericError(String::from("Invalid response for is visible check")))
}
}
Err(e) => Err(e)
}
}
Err(e) => Err(ProductOSError::GenericError(e.to_string()))
}
}
}
}
async fn is_enabled(&self, window: &WindowSelect, search: String, search_kind: SearchKind, which: usize, behaviour: Behaviour) -> Result<bool, ProductOSError> {
match &self.socket_server {
None => {
let error = format!("No browser ready or available");
tracing::error!(error);
return Err(ProductOSError::GenericError(error))
}
Some(socket_server) => {
let scope_present = search.split("%%").collect::<Vec<&str>>();
let search_query = ElementSearchQuery {
search_query: if scope_present.len() > 1 { scope_present.get(1).unwrap().to_string() } else { search.to_owned() },
target: if scope_present.len() > 1 { Some(scope_present.get(0).unwrap().to_string()) } else { None },
search_kind,
which,
result_format: SearchResultFormat::Element
};
match serde_json::value::to_value(search_query.to_owned()) {
Ok(search_query_json) => {
match self.send_message_to_window_socket_and_get_result(window, "is_enabled", "server_action", search_query_json, "application/json", None, None, "enabled_result").await {
Ok(result) => {
match result.data {
Value::Bool(b) => Ok(b),
_ => Err(ProductOSError::GenericError(String::from("Invalid response for is visible check")))
}
}
Err(e) => Err(e)
}
}
Err(e) => Err(ProductOSError::GenericError(e.to_string()))
}
}
}
}
}