mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			918 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			918 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| #![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,
 | |
|     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<Event<'a>>),
 | |
|     EitherOr(Assert<'a>, Vec<Event<'a>>, Vec<Event<'a>>),
 | |
|     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>),
 | |
|     IsPresent(&'a str),
 | |
|     IsElePresent(By),
 | |
|     IsPresentNow(&'a str),
 | |
| }
 | |
| 
 | |
| pub static CHEKOUT_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<Event<'_>>,
 | |
|     ) -> 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::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::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::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<String, String> =
 | |
|                             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<Event<'_>>,
 | |
|     ) -> 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<Event<'_>>,
 | |
|     ) -> Result<(), WebDriverError> {
 | |
|         let config = self.get_configs().automation_configs.unwrap();
 | |
|         if config.run_minimum_steps.unwrap() {
 | |
|             self.complete_actions(&web_driver, actions[..3].to_vec())
 | |
|                 .await
 | |
|         } else {
 | |
|             self.complete_actions(&web_driver, actions).await
 | |
|         }
 | |
|     }
 | |
|     async fn make_gpay_payment(
 | |
|         &self,
 | |
|         web_driver: WebDriver,
 | |
|         url: &str,
 | |
|         actions: Vec<Event<'_>>,
 | |
|     ) -> Result<(), WebDriverError> {
 | |
|         self.execute_gpay_steps(web_driver, url, actions).await
 | |
|     }
 | |
|     async fn execute_gpay_steps(
 | |
|         &self,
 | |
|         web_driver: WebDriver,
 | |
|         url: &str,
 | |
|         actions: Vec<Event<'_>>,
 | |
|     ) -> 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::IsPresentNow("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<Event<'_>>,
 | |
|     ) -> 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<Event<'_>>,
 | |
|         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!(
 | |
|                 "{CHEKOUT_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[data.data.len().saturating_sub(3)..]; // 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<Event<'_>>,
 | |
|     ) -> 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<Event<'_>>,
 | |
|     ) -> 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<Event<'_>>,
 | |
|     ) -> 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<bool> {
 | |
|     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<bool> {
 | |
|     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<bool> {
 | |
|     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>(_: T) -> &'static str {
 | |
|             std::any::type_name::<T>()
 | |
|         }
 | |
|         let name = type_name_of(f);
 | |
|         &name[..name.len() - 3]
 | |
|     }};
 | |
| }
 | |
| 
 | |
| #[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<String> =
 | |
|         serde_json::from_value(conf).unwrap_or_else(|_| HashSet::new());
 | |
|     let modules: Vec<_> = name.split("::").collect();
 | |
|     let file_match = format!("{}::*", <&str>::clone(&modules[1]));
 | |
|     let module_name = modules[1..3].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 {
 | |
|                 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<String, WebDriverError> {
 | |
|     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::<Vec<_>>();
 | |
|             fp.truncate(3);
 | |
|             fp.join(&MAIN_SEPARATOR.to_string())
 | |
|         })
 | |
|         .unwrap();
 | |
|     base_path.push_str(r"/Library/Application\ Support/Google/Chrome/Default"); //Issue: 1573
 | |
|     Ok(base_path)
 | |
| }
 | |
| 
 | |
| fn get_firefox_profile_path() -> Result<String, WebDriverError> {
 | |
|     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::<Vec<_>>();
 | |
|             fp.truncate(3);
 | |
|             fp.join(&MAIN_SEPARATOR.to_string())
 | |
|         })
 | |
|         .unwrap();
 | |
|     base_path.push_str(r#"/Library/Application Support/Firefox/Profiles/hs-test"#); //Issue: 1573
 | |
|     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<Result<(), WebDriverError>, Box<dyn std::any::Any + Send>>,
 | |
| ) -> 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::<WebDriverError>() {
 | |
|                 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<WebhookResponseData>,
 | |
| }
 | |
| 
 | |
| #[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,
 | |
| }
 | 
