#![allow( clippy::expect_used, clippy::panic, clippy::unwrap_in_result, clippy::missing_panics_doc, clippy::unwrap_used )] use std::{ collections::{HashMap, HashSet}, env, io::Read, path::{MAIN_SEPARATOR, MAIN_SEPARATOR_STR}, time::Duration, }; use async_trait::async_trait; use base64::Engine; use serde::{Deserialize, Serialize}; use serde_json::json; use test_utils::connector_auth; use thirtyfour::{components::SelectElement, prelude::*, WebDriver}; #[derive(Clone)] pub enum Event<'a> { RunIf(Assert<'a>, Vec>), EitherOr(Assert<'a>, Vec>, Vec>), Assert(Assert<'a>), Trigger(Trigger<'a>), } #[derive(Clone)] #[allow(dead_code)] pub enum Trigger<'a> { Goto(&'a str), Click(By), ClickNth(By, usize), SelectOption(By, &'a str), ChangeQueryParam(&'a str, &'a str), SwitchTab(Position), SwitchFrame(By), Find(By), Query(By), SendKeys(By, &'a str), Sleep(u64), } #[derive(Clone)] pub enum Position { Prev, Next, } #[derive(Clone)] pub enum Selector { Title, QueryParamStr, } #[derive(Clone)] pub enum Assert<'a> { Eq(Selector, &'a str), Contains(Selector, &'a str), ContainsAny(Selector, Vec<&'a str>), EitherOfThemExist(&'a str, &'a str), IsPresent(&'a str), IsElePresent(By), IsPresentNow(&'a str), } pub static CHECKOUT_BASE_URL: &str = "https://hs-payments-test.netlify.app"; #[async_trait] pub trait SeleniumTest { fn get_saved_testcases(&self) -> serde_json::Value { get_saved_testcases() } fn get_configs(&self) -> connector_auth::ConnectorAuthentication { get_configs() } async fn retry_click( &self, times: i32, interval: u64, driver: &WebDriver, by: By, ) -> Result<(), WebDriverError> { let mut res = Ok(()); for _i in 0..times { res = self.click_element(driver, by.clone()).await; if res.is_err() { tokio::time::sleep(Duration::from_secs(interval)).await; } else { break; } } return res; } fn get_connector_name(&self) -> String; async fn complete_actions( &self, driver: &WebDriver, actions: Vec>, ) -> Result<(), WebDriverError> { for action in actions { match action { Event::Assert(assert) => match assert { Assert::Contains(selector, text) => match selector { Selector::QueryParamStr => { let url = driver.current_url().await?; assert!(url.query().unwrap().contains(text)) } _ => assert!(driver.title().await?.contains(text)), }, Assert::ContainsAny(selector, search_keys) => match selector { Selector::QueryParamStr => { let url = driver.current_url().await?; assert!(search_keys .iter() .any(|key| url.query().unwrap().contains(key))) } _ => assert!(driver.title().await?.contains(search_keys.first().unwrap())), }, Assert::EitherOfThemExist(text_1, text_2) => assert!( is_text_present_now(driver, text_1).await? || is_text_present_now(driver, text_2).await? ), Assert::Eq(_selector, text) => assert_eq!(driver.title().await?, text), Assert::IsPresent(text) => { assert!(is_text_present(driver, text).await?) } Assert::IsElePresent(selector) => { assert!(is_element_present(driver, selector).await?) } Assert::IsPresentNow(text) => { assert!(is_text_present_now(driver, text).await?) } }, Event::RunIf(con_event, events) => match con_event { Assert::Contains(selector, text) => match selector { Selector::QueryParamStr => { let url = driver.current_url().await?; if url.query().unwrap().contains(text) { self.complete_actions(driver, events).await?; } } _ => assert!(driver.title().await?.contains(text)), }, Assert::ContainsAny(selector, keys) => match selector { Selector::QueryParamStr => { let url = driver.current_url().await?; if keys.iter().any(|key| url.query().unwrap().contains(key)) { self.complete_actions(driver, events).await?; } } _ => assert!(driver.title().await?.contains(keys.first().unwrap())), }, Assert::Eq(_selector, text) => { if text == driver.title().await? { self.complete_actions(driver, events).await?; } } Assert::EitherOfThemExist(text_1, text_2) => { if is_text_present_now(driver, text_1).await.is_ok() || is_text_present_now(driver, text_2).await.is_ok() { self.complete_actions(driver, events).await?; } } Assert::IsPresent(text) => { if is_text_present(driver, text).await.is_ok() { self.complete_actions(driver, events).await?; } } Assert::IsElePresent(text) => { if is_element_present(driver, text).await.is_ok() { self.complete_actions(driver, events).await?; } } Assert::IsPresentNow(text) => { if is_text_present_now(driver, text).await.is_ok() { self.complete_actions(driver, events).await?; } } }, Event::EitherOr(con_event, success, failure) => match con_event { Assert::Contains(selector, text) => match selector { Selector::QueryParamStr => { let url = driver.current_url().await?; self.complete_actions( driver, if url.query().unwrap().contains(text) { success } else { failure }, ) .await?; } _ => assert!(driver.title().await?.contains(text)), }, Assert::ContainsAny(selector, keys) => match selector { Selector::QueryParamStr => { let url = driver.current_url().await?; self.complete_actions( driver, if keys.iter().any(|key| url.query().unwrap().contains(key)) { success } else { failure }, ) .await?; } _ => assert!(driver.title().await?.contains(keys.first().unwrap())), }, Assert::Eq(_selector, text) => { self.complete_actions( driver, if text == driver.title().await? { success } else { failure }, ) .await?; } Assert::EitherOfThemExist(text_1, text_2) => { self.complete_actions( driver, if is_text_present_now(driver, text_1).await.is_ok() || is_text_present_now(driver, text_2).await.is_ok() { success } else { failure }, ) .await?; } Assert::IsPresent(text) => { self.complete_actions( driver, if is_text_present(driver, text).await.is_ok() { success } else { failure }, ) .await?; } Assert::IsElePresent(by) => { self.complete_actions( driver, if is_element_present(driver, by).await.is_ok() { success } else { failure }, ) .await?; } Assert::IsPresentNow(text) => { self.complete_actions( driver, if is_text_present_now(driver, text).await.is_ok() { success } else { failure }, ) .await?; } }, Event::Trigger(trigger) => match trigger { Trigger::Goto(url) => { let saved_tests = serde_json::to_string(&self.get_saved_testcases()).unwrap(); let conf = serde_json::to_string(&self.get_configs()).unwrap(); let configs = self.get_configs().automation_configs.unwrap(); let hs_base_url = configs .hs_base_url .unwrap_or_else(|| "http://localhost:8080".to_string()); let configs_url = configs.configs_url.unwrap(); let hs_api_keys = configs.hs_api_keys.unwrap(); let test_env = configs.hs_test_env.unwrap(); let script = &[ format!("localStorage.configs='{configs_url}'").as_str(), format!("localStorage.current_env='{test_env}'").as_str(), "localStorage.hs_api_key=''", format!("localStorage.hs_api_keys='{hs_api_keys}'").as_str(), format!("localStorage.base_url='{hs_base_url}'").as_str(), format!("localStorage.hs_api_configs='{conf}'").as_str(), format!("localStorage.saved_payments=JSON.stringify({saved_tests})") .as_str(), "localStorage.force_sync='true'", format!( "localStorage.current_connector=\"{}\";", self.get_connector_name().clone() ) .as_str(), ] .join(";"); driver.goto(url).await?; driver.execute(script, Vec::new()).await?; } Trigger::Click(by) => { self.retry_click(3, 5, driver, by.clone()).await?; } Trigger::ClickNth(by, n) => { let ele = driver.query(by).all().await?.into_iter().nth(n).unwrap(); ele.wait_until().enabled().await?; ele.wait_until().displayed().await?; ele.wait_until().clickable().await?; ele.scroll_into_view().await?; ele.click().await?; } Trigger::Find(by) => { driver.find(by).await?; } Trigger::Query(by) => { driver.query(by).first().await?; } Trigger::SendKeys(by, input) => { let ele = driver.query(by).first().await?; ele.wait_until().displayed().await?; ele.send_keys(&input).await?; } Trigger::SelectOption(by, input) => { let ele = driver.query(by).first().await?; let select_element = SelectElement::new(&ele).await?; select_element.select_by_partial_text(input).await?; } Trigger::ChangeQueryParam(param, value) => { let mut url = driver.current_url().await?; let mut hash_query: HashMap = url.query_pairs().into_owned().collect(); hash_query.insert(param.to_string(), value.to_string()); let url_str = serde_urlencoded::to_string(hash_query) .expect("Query Param update failed"); url.set_query(Some(&url_str)); driver.goto(url.as_str()).await?; } Trigger::Sleep(seconds) => { tokio::time::sleep(Duration::from_secs(seconds)).await; } Trigger::SwitchTab(position) => match position { Position::Next => { let windows = driver.windows().await?; if let Some(window) = windows.iter().next_back() { driver.switch_to_window(window.to_owned()).await?; } } Position::Prev => { let windows = driver.windows().await?; if let Some(window) = windows.into_iter().next() { driver.switch_to_window(window.to_owned()).await?; } } }, Trigger::SwitchFrame(by) => { let iframe = driver.query(by).first().await?; iframe.wait_until().displayed().await?; iframe.clone().enter_frame().await?; } }, } } Ok(()) } async fn click_element(&self, driver: &WebDriver, by: By) -> Result<(), WebDriverError> { let ele = driver.query(by).first().await?; ele.wait_until().enabled().await?; ele.wait_until().displayed().await?; ele.wait_until().clickable().await?; ele.scroll_into_view().await?; ele.click().await } async fn make_redirection_payment( &self, web_driver: WebDriver, actions: Vec>, ) -> Result<(), WebDriverError> { // To support failure retries let result = self .execute_steps(web_driver.clone(), actions.clone()) .await; if result.is_err() { self.execute_steps(web_driver, actions).await } else { result } } async fn execute_steps( &self, web_driver: WebDriver, actions: Vec>, ) -> Result<(), WebDriverError> { let config = self.get_configs().automation_configs.unwrap(); if config.run_minimum_steps.unwrap() { self.complete_actions(&web_driver, actions.get(..3).unwrap().to_vec()) .await } else { self.complete_actions(&web_driver, actions).await } } async fn make_gpay_payment( &self, web_driver: WebDriver, url: &str, actions: Vec>, ) -> Result<(), WebDriverError> { self.execute_gpay_steps(web_driver, url, actions).await } async fn execute_gpay_steps( &self, web_driver: WebDriver, url: &str, actions: Vec>, ) -> Result<(), WebDriverError> { let config = self.get_configs().automation_configs.unwrap(); let (email, pass) = (&config.gmail_email.unwrap(), &config.gmail_pass.unwrap()); let default_actions = vec![ Event::Trigger(Trigger::Goto(url)), Event::Trigger(Trigger::Click(By::Css(".gpay-button"))), Event::Trigger(Trigger::SwitchTab(Position::Next)), Event::Trigger(Trigger::Sleep(5)), Event::RunIf( Assert::EitherOfThemExist("Use your Google Account", "Sign in"), vec![ Event::Trigger(Trigger::SendKeys(By::Id("identifierId"), email)), Event::Trigger(Trigger::ClickNth(By::Tag("button"), 2)), Event::EitherOr( Assert::IsPresent("Welcome"), vec![ Event::Trigger(Trigger::SendKeys(By::Name("Passwd"), pass)), Event::Trigger(Trigger::Sleep(2)), Event::Trigger(Trigger::Click(By::Id("passwordNext"))), Event::Trigger(Trigger::Sleep(10)), ], vec![ Event::Trigger(Trigger::SendKeys(By::Id("identifierId"), email)), Event::Trigger(Trigger::ClickNth(By::Tag("button"), 2)), Event::Trigger(Trigger::SendKeys(By::Name("Passwd"), pass)), Event::Trigger(Trigger::Sleep(2)), Event::Trigger(Trigger::Click(By::Id("passwordNext"))), Event::Trigger(Trigger::Sleep(10)), ], ), ], ), Event::Trigger(Trigger::SwitchFrame(By::Css( ".bootstrapperIframeContainerElement iframe", ))), Event::Assert(Assert::IsPresent("Gpay Tester")), Event::Trigger(Trigger::Click(By::ClassName("jfk-button-action"))), Event::Trigger(Trigger::SwitchTab(Position::Prev)), ]; self.complete_actions(&web_driver, default_actions).await?; self.complete_actions(&web_driver, actions).await } async fn make_affirm_payment( &self, driver: WebDriver, url: &str, actions: Vec>, ) -> Result<(), WebDriverError> { self.complete_actions( &driver, vec![ Event::Trigger(Trigger::Goto(url)), Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), ], ) .await?; let mut affirm_actions = vec![ Event::RunIf( Assert::IsPresent("Big purchase? No problem."), vec![ Event::Trigger(Trigger::SendKeys( By::Css("input[data-testid='phone-number-field']"), "(833) 549-5574", // any test phone number accepted by affirm )), Event::Trigger(Trigger::Click(By::Css( "button[data-testid='submit-button']", ))), Event::Trigger(Trigger::SendKeys( By::Css("input[data-testid='phone-pin-field']"), "1234", )), ], ), Event::Trigger(Trigger::Click(By::Css( "button[data-testid='skip-payment-button']", ))), Event::Trigger(Trigger::Click(By::Css("div[data-testid='indicator']"))), Event::Trigger(Trigger::Click(By::Css( "button[data-testid='submit-button']", ))), Event::Trigger(Trigger::Click(By::Css("div[data-testid='indicator']"))), Event::Trigger(Trigger::Click(By::Css( "div[data-testid='disclosure-checkbox-indicator']", ))), Event::Trigger(Trigger::Click(By::Css( "button[data-testid='submit-button']", ))), ]; affirm_actions.extend(actions); self.complete_actions(&driver, affirm_actions).await } async fn make_webhook_test( &self, web_driver: WebDriver, payment_url: &str, actions: Vec>, webhook_retry_time: u64, webhook_status: &str, ) -> Result<(), WebDriverError> { self.complete_actions( &web_driver, vec![Event::Trigger(Trigger::Goto(payment_url))], ) .await?; self.complete_actions(&web_driver, actions).await?; //additional actions needs to make a payment self.complete_actions( &web_driver, vec![Event::Trigger(Trigger::Goto(&format!( "{CHECKOUT_BASE_URL}/events" )))], ) .await?; let element = web_driver.query(By::Css("h2.last-payment")).first().await?; let payment_id = element.text().await?; let retries = 3; // no of retry times for _i in 0..retries { let configs = self.get_configs().automation_configs.unwrap(); let outgoing_webhook_url = configs.hs_webhook_url.unwrap().to_string(); let client = reqwest::Client::new(); let response = client.get(outgoing_webhook_url).send().await.unwrap(); // get events from outgoing webhook endpoint let body_text = response.text().await.unwrap(); let data: WebhookResponse = serde_json::from_str(&body_text).unwrap(); let last_three_events = data.data.get(data.data.len().saturating_sub(3)..).unwrap(); // Get the last three elements if available for last_event in last_three_events { let last_event_body = &last_event.step.request.body; let decoded_bytes = base64::engine::general_purpose::STANDARD //decode the encoded outgoing webhook event .decode(last_event_body) .unwrap(); let decoded_str = String::from_utf8(decoded_bytes).unwrap(); let webhook_response: HsWebhookResponse = serde_json::from_str(&decoded_str).unwrap(); if payment_id == webhook_response.content.object.payment_id && webhook_status == webhook_response.content.object.status { return Ok(()); } } self.complete_actions( &web_driver, vec![Event::Trigger(Trigger::Sleep(webhook_retry_time))], ) .await?; } Err(WebDriverError::CustomError("Webhook Not Found".to_string())) } async fn make_paypal_payment( &self, web_driver: WebDriver, url: &str, actions: Vec>, ) -> Result<(), WebDriverError> { let pypl_url = url.to_string(); // To support failure retries let result = self .execute_paypal_steps(web_driver.clone(), &pypl_url, actions.clone()) .await; if result.is_err() { self.execute_paypal_steps(web_driver.clone(), &pypl_url, actions.clone()) .await } else { result } } async fn execute_paypal_steps( &self, web_driver: WebDriver, url: &str, actions: Vec>, ) -> Result<(), WebDriverError> { self.complete_actions( &web_driver, vec![ Event::Trigger(Trigger::Goto(url)), Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), ], ) .await?; let (email, pass) = ( &self .get_configs() .automation_configs .unwrap() .pypl_email .unwrap(), &self .get_configs() .automation_configs .unwrap() .pypl_pass .unwrap(), ); let mut pypl_actions = vec![ Event::Trigger(Trigger::Sleep(8)), Event::RunIf( Assert::IsPresentNow("Enter your email address to get started"), vec![ Event::Trigger(Trigger::SendKeys(By::Id("email"), email)), Event::Trigger(Trigger::Click(By::Id("btnNext"))), ], ), Event::RunIf( Assert::IsPresentNow("Password"), vec![ Event::Trigger(Trigger::SendKeys(By::Id("password"), pass)), Event::Trigger(Trigger::Click(By::Id("btnLogin"))), ], ), ]; pypl_actions.extend(actions); self.complete_actions(&web_driver, pypl_actions).await } async fn make_clearpay_payment( &self, driver: WebDriver, url: &str, actions: Vec>, ) -> Result<(), WebDriverError> { self.complete_actions( &driver, vec![ Event::Trigger(Trigger::Goto(url)), Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), Event::Trigger(Trigger::Sleep(5)), Event::RunIf( Assert::IsPresentNow("Manage Cookies"), vec![ Event::Trigger(Trigger::Click(By::Css("button.cookie-setting-link"))), Event::Trigger(Trigger::Click(By::Id("accept-recommended-btn-handler"))), ], ), ], ) .await?; let (email, pass) = ( &self .get_configs() .automation_configs .unwrap() .clearpay_email .unwrap(), &self .get_configs() .automation_configs .unwrap() .clearpay_pass .unwrap(), ); let mut clearpay_actions = vec![ Event::Trigger(Trigger::Sleep(3)), Event::EitherOr( Assert::IsPresent("Please enter your password"), vec![ Event::Trigger(Trigger::SendKeys(By::Css("input[name='password']"), pass)), Event::Trigger(Trigger::Click(By::Css("button[type='submit']"))), ], vec![ Event::Trigger(Trigger::SendKeys( By::Css("input[name='identifier']"), email, )), Event::Trigger(Trigger::Click(By::Css("button[type='submit']"))), Event::Trigger(Trigger::Sleep(3)), Event::Trigger(Trigger::SendKeys(By::Css("input[name='password']"), pass)), Event::Trigger(Trigger::Click(By::Css("button[type='submit']"))), ], ), Event::Trigger(Trigger::Click(By::Css( "button[data-testid='summary-button']", ))), ]; clearpay_actions.extend(actions); self.complete_actions(&driver, clearpay_actions).await } } async fn is_text_present_now(driver: &WebDriver, key: &str) -> WebDriverResult { let mut xpath = "//*[contains(text(),'".to_owned(); xpath.push_str(key); xpath.push_str("')]"); let result = driver.find(By::XPath(&xpath)).await?; let display: &str = &result.css_value("display").await?; if display.is_empty() || display == "none" { return Err(WebDriverError::CustomError("Element is hidden".to_string())); } result.is_present().await } async fn is_text_present(driver: &WebDriver, key: &str) -> WebDriverResult { let mut xpath = "//*[contains(text(),'".to_owned(); xpath.push_str(key); xpath.push_str("')]"); let result = driver.query(By::XPath(&xpath)).first().await?; result.is_present().await } async fn is_element_present(driver: &WebDriver, by: By) -> WebDriverResult { let element = driver.query(by).first().await?; element.is_present().await } #[macro_export] macro_rules! tester_inner { ($execute:ident, $webdriver:expr) => {{ use std::{ sync::{Arc, Mutex}, thread, }; let driver = $webdriver; // we'll need the session_id from the thread // NOTE: even if it panics, so can't just return it let session_id = Arc::new(Mutex::new(None)); // run test in its own thread to catch panics let sid = session_id.clone(); let res = thread::spawn(move || { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap(); let driver = runtime .block_on(driver) .expect("failed to construct test WebDriver"); *sid.lock().unwrap() = runtime.block_on(driver.session_id()).ok(); // make sure we close, even if an assertion fails let client = driver.clone(); let x = runtime.block_on(async move { let run = tokio::spawn($execute(driver)).await; let _ = client.quit().await; run }); drop(runtime); x.expect("test panicked") }) .join(); let success = handle_test_error(res); assert!(success); }}; } #[macro_export] macro_rules! function { () => {{ fn f() {} fn type_name_of(_: T) -> &'static str { std::any::type_name::() } let name = type_name_of(f); &name.get(..name.len() - 3).unwrap() }}; } #[macro_export] macro_rules! tester { ($f:ident) => {{ use $crate::{function, tester_inner}; let test_name = format!("{:?}", function!()); if (should_ignore_test(&test_name)) { return; } let browser = get_browser(); let url = make_url(&browser); let caps = make_capabilities(&browser); tester_inner!($f, WebDriver::new(url, caps)); }}; } fn get_saved_testcases() -> serde_json::Value { let env_value = env::var("CONNECTOR_TESTS_FILE_PATH").ok(); if env_value.is_none() { return serde_json::json!(""); } let path = env_value.unwrap(); let mut file = &std::fs::File::open(path).expect("Failed to open file"); let mut contents = String::new(); file.read_to_string(&mut contents) .expect("Failed to read file"); // Parse the JSON data serde_json::from_str(&contents).expect("Failed to parse JSON") } fn get_configs() -> connector_auth::ConnectorAuthentication { let path = env::var("CONNECTOR_AUTH_FILE_PATH").expect("connector authentication file path not set"); toml::from_str( &std::fs::read_to_string(path).expect("connector authentication config file not found"), ) .expect("Failed to read connector authentication config file") } pub fn should_ignore_test(name: &str) -> bool { let conf = get_saved_testcases() .get("tests_to_ignore") .unwrap_or(&json!([])) .clone(); let tests_to_ignore: HashSet = serde_json::from_value(conf).unwrap_or_else(|_| HashSet::new()); let modules: Vec<_> = name.split("::").collect(); let file_match = format!( "{}::*", <&str>::clone(modules.get(1).expect("Error obtaining module path segment")) ); let module_name = modules .get(1..3) .expect("Error obtaining module path segment") .join("::"); // Ignore if it matches patterns like nuvei_ui::*, nuvei_ui::should_make_nuvei_eps_payment_test tests_to_ignore.contains(&file_match) || tests_to_ignore.contains(&module_name) } pub fn get_browser() -> String { "firefox".to_string() } // based on the browser settings build profile info pub fn make_capabilities(browser: &str) -> Capabilities { match browser { "firefox" => { let mut caps = DesiredCapabilities::firefox(); let ignore_profile = env::var("IGNORE_BROWSER_PROFILE").ok(); if ignore_profile.is_none() || ignore_profile.unwrap() == "false" { let profile_path = &format!("-profile={}", get_firefox_profile_path().unwrap()); caps.add_firefox_arg(profile_path).unwrap(); } else { let profile_path = &format!("-profile={}", get_firefox_profile_path().unwrap()); caps.add_firefox_arg(profile_path).unwrap(); caps.add_firefox_arg("--headless").ok(); } caps.into() } "chrome" => { let mut caps = DesiredCapabilities::chrome(); let profile_path = &format!("user-data-dir={}", get_chrome_profile_path().unwrap()); caps.add_chrome_arg(profile_path).unwrap(); caps.into() } &_ => DesiredCapabilities::safari().into(), } } fn get_chrome_profile_path() -> Result { let exe = env::current_exe()?; let dir = exe.parent().expect("Executable must be in some directory"); let mut base_path = dir .to_str() .map(|str| { let mut fp = str.split(MAIN_SEPARATOR).collect::>(); fp.truncate(3); fp.join(MAIN_SEPARATOR_STR) }) .unwrap(); if env::consts::OS == "macos" { base_path.push_str(r"/Library/Application\ Support/Google/Chrome/Default"); //Issue: 1573 } // We're only using Firefox on Ubuntu runner Ok(base_path) } fn get_firefox_profile_path() -> Result { let exe = env::current_exe()?; let dir = exe.parent().expect("Executable must be in some directory"); let mut base_path = dir .to_str() .map(|str| { let mut fp = str.split(MAIN_SEPARATOR).collect::>(); fp.truncate(3); fp.join(MAIN_SEPARATOR_STR) }) .unwrap(); if env::consts::OS == "macos" { base_path.push_str(r#"/Library/Application Support/Firefox/Profiles/hs-test"#); //Issue: 1573 } else if env::consts::OS == "linux" { if let Some(home_dir) = env::var_os("HOME") { if let Some(home_path) = home_dir.to_str() { let profile_path = format!("{home_path}/.mozilla/firefox/hs-test"); return Ok(profile_path); } } } Ok(base_path) } pub fn make_url(browser: &str) -> &'static str { match browser { "firefox" => "http://localhost:4444", "chrome" => "http://localhost:9515", &_ => "", } } pub fn handle_test_error( res: Result, Box>, ) -> bool { match res { Ok(Ok(_)) => true, Ok(Err(web_driver_error)) => { eprintln!("test future failed to resolve: {web_driver_error:?}"); false } Err(e) => { if let Some(web_driver_error) = e.downcast_ref::() { eprintln!("test future panicked: {web_driver_error:?}"); } else { eprintln!("test future panicked; an assertion probably failed"); } false } } } #[derive(Debug, Serialize, Deserialize)] pub struct WebhookResponse { data: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct WebhookResponseData { step: WebhookRequestData, } #[derive(Debug, Serialize, Deserialize)] pub struct WebhookRequestData { request: WebhookRequest, } #[derive(Debug, Serialize, Deserialize)] pub struct WebhookRequest { body: String, } #[derive(Debug, Serialize, Deserialize)] pub struct HsWebhookResponse { content: HsWebhookContent, } #[derive(Debug, Serialize, Deserialize)] pub struct HsWebhookContent { object: HsWebhookObject, } #[derive(Debug, Serialize, Deserialize)] pub struct HsWebhookObject { payment_id: String, status: String, connector: String, }