lw_webdriver/
session.rs

1//! Sessions allow you to control tabs
2
3use json::*;
4use std::time::Duration;
5use std::result::Result;
6use crate::enums::*;
7use crate::timeouts::*;
8use crate::tab::*;
9use crate::error::*;
10use std::process::{Command, Stdio};
11use std::thread;
12use log::{debug, info, warn, error};
13use std::rc::Rc;
14use crate::http_requests::*;
15
16/// This is the more important object.
17/// Tabs can be accessed within the session.
18/// 
19/// # Example
20/// 
21/// ```rust
22/// use lw_webdriver::{session::Session, enums::Browser};
23/// 
24/// let mut session = Session::new(Browser::Firefox, false).unwrap();
25/// 
26/// // accessing default tab
27/// session.tabs[0].navigate("http://example.com/").unwrap();
28/// 
29/// // creating a new tab and access it
30/// session.open_tab().unwrap();
31/// session.tabs[1].navigate("https://mubelotix.dev/").unwrap();
32/// ```
33pub struct Session {
34    id: Rc<String>,
35    /// Contains every manually created tabs and default tab.
36    /// Do not contains tabs created by web pages with javascript unless you call [update_tabs()](https://to.do/).
37    pub tabs: Vec<Tab>,
38    webdriver_process: Option<std::process::Child>,
39}
40
41impl Session {
42    /// Create a session of a specific [browser](https://to.do/).
43    /// Headless mean that the browser will be opened but not displayed (useful for servers).
44    /// The crate will request a webdriver server at http://localhost:4444.
45    /// If no webdriver is listening, one will be launched, but the program ([geckodriver](https://to.do/) or [chromedriver](https://to.do/))
46    /// must be located at the same place than the running program.
47    /// 
48    /// # Example
49    /// 
50    /// ```rust
51    /// # use lw_webdriver::{session::Session, enums::Browser};
52    /// let mut session = Session::new(Browser::Firefox, false).unwrap();
53    /// ```
54    pub fn new(browser: Browser, headless: bool) -> Result<Self, WebdriverError> {
55        info!{"Creating a session..."};
56        let result = Session::new_session(browser, headless);
57
58        if let Err(WebdriverError::FailedRequest) = result {
59            warn!{"No webdriver launched."}
60            if cfg!(unix) {
61                if browser == Browser::Firefox {
62                    info!{"Launching geckodriver..."}
63                    let p = Command::new("./geckodriver")
64                        .stdout(Stdio::null())
65                        .stderr(Stdio::null())
66                        .spawn()
67                        .expect("Failed to start process.");
68                    thread::sleep(Duration::from_millis(2000));
69                    let result = Session::new_session(browser, headless);
70                    if let Ok(mut result) = result {
71                        info!{"Session created successfully."}
72                        result.webdriver_process = Some(p);
73                        return Ok(result);
74                    } else if let Err(e) = result {
75                        error!("Failed to create session. error : {:?}.", e);
76                        return Err(e);
77                    }
78                } else {
79                    info!{"Launching chromedriver..."}
80                    let p = Command::new("./chromedriver")
81                        .arg("--port=4444")
82                        .stdout(Stdio::null())
83                        .stderr(Stdio::null())
84                        .spawn()
85                        .expect("Failed to start process");
86                    thread::sleep(Duration::from_millis(2000));
87                    let result = Session::new_session(browser, headless);
88                    if let Ok(mut result) = result {
89                        info!{"Session created successfully."}
90                        result.webdriver_process = Some(p);
91                        return Ok(result);
92                    } else if let Err(e) = result{
93                        error!("Failed to create session. error : {:?}.", e);
94                        return Err(e);
95                    }
96                }
97            } else {
98                panic!("Please launch the webdriver manually.")
99            }
100        } else {
101            return result;
102        }
103        
104        result
105    }
106
107    fn new_session(browser: Browser, headless: bool)  -> Result<Self, WebdriverError> {
108        // Detect platform
109        let platform = Platform::current();
110        if let Platform::Unknow = platform {
111            return Err(WebdriverError::UnsupportedPlatform);
112        }
113
114        // Generate capabilities
115        let post_data = match browser {
116            Browser::Firefox => {
117                if headless {
118                    object!{
119                        "capabilities" => object!{
120                            "alwaysMatch" => object!{
121                                "platformName" => platform.to_string(),
122                                "browserName" => browser.to_string(),
123                                "moz:firefoxOptions" => object! {
124                                    "args" => array!{"-headless"}
125                                },
126                            }
127                        }
128                    }
129                } else {
130                    object!{
131                        "capabilities" => object!{
132                            "alwaysMatch" => object!{
133                                "platformName" => platform.to_string(),
134                                "browserName" => browser.to_string()
135                            }
136                        }
137                    }
138                }
139            },
140            Browser::Chrome => {
141                if headless {
142                    object!{
143                        "capabilities" => object!{
144                            "alwaysMatch" => object!{
145                                "platformName" => platform.to_string(),
146                                "browserName" => browser.to_string(),
147                                "goog:chromeOptions" => object! {
148                                    "args" => array!{"-headless"}
149                                }
150                            }
151                        }
152                    }
153                } else {
154                    object!{
155                        "capabilities" => object!{
156                            "alwaysMatch" => object!{
157                                "platformName" => platform.to_string(),
158                                "browserName" => browser.to_string()
159                            }
160                        }
161                    }
162                }
163            }
164        };
165        
166        // Send request
167        let session_id = new_session(&post_data.to_string())?;
168        let mut session = Session {
169            id: Rc::new(session_id),
170            tabs: Vec::new(),
171            webdriver_process: None
172        };
173
174        session.update_tabs()?;
175
176        Ok(session)
177    }
178
179    /// Create a new tab in the session.
180    /// The tab will be directly accessible from the session (no call to [update_tabs()](https://to.do/) needed).
181    /// 
182    /// # Example
183    /// 
184    /// ```rust
185    /// # use lw_webdriver::{session::Session, enums::Browser};
186    /// let mut session = Session::new(Browser::Firefox, false).unwrap();
187    /// 
188    /// assert_eq!(session.tabs.len(), 1); // default tab is already opened
189    /// session.open_tab().unwrap();
190    /// assert_eq!(session.tabs.len(), 2); // new tab is accessible
191    /// ```
192    pub fn open_tab(&mut self) -> Result<usize, WebdriverError> {
193        let tab_id = new_tab(&self.id)?;
194        let new_tab = Tab::new_from(tab_id, Rc::clone(&self.id));
195        self.tabs.push(new_tab);
196
197        Ok(self.tabs.len() - 1)
198    }
199
200    /// When a tab is created with [open_tab()](https://to.do/) method, it is accessible directly.
201    /// But sometimes a tab is created by someone else (from a web page with javascript) and you don't want to care about it!
202    /// This tab will not be accessible by your program because you never asked it.
203    /// However if you want to access every open tab, call this function.
204    /// 
205    /// # Example
206    /// 
207    /// ```rust
208    /// # use lw_webdriver::{session::Session, enums::Browser};
209    /// # use std::thread::sleep;
210    /// # use std::time::Duration;
211    /// let mut session = Session::new(Browser::Firefox, false).unwrap();
212    /// 
213    /// // only the default tab is open
214    /// assert_eq!(session.tabs.len(), 1);
215    /// 
216    /// // load a website
217    /// session.tabs[0].navigate("https://mubelotix.dev/webdriver_tests/open_tab.html").unwrap();
218    /// 
219    /// // observe what is happening
220    /// sleep(Duration::from_secs(5));
221    /// 
222    /// // a tab has been opened by another tab but you never asked for it
223    /// // you can see two tabs displayed
224    /// // but this crate don't show the useless one
225    /// assert_eq!(session.tabs.len(), 1);
226    /// 
227    /// // if you want to access it, call this function
228    /// session.update_tabs().unwrap();
229    /// 
230    /// // now you can access two tabs!
231    /// assert_eq!(session.tabs.len(), 2);
232    /// ```
233    pub fn update_tabs(&mut self) -> Result<(), WebdriverError> {
234        let tabs_id = get_open_tabs(&self.id)?;
235        for tab_id in tabs_id {
236            if self.tabs.iter().position(|element| *element.id == tab_id).is_none() {
237                self.tabs.push(Tab::new_from(tab_id, Rc::clone(&self.id)));
238            }
239        }
240
241        Ok(())
242    }
243
244    /// This is a simple method getting [timeouts](https://to.do/) of the session.
245    pub fn get_timeouts(&self) -> Result<Timeouts, WebdriverError> {
246        Ok(get_timeouts(&self.id)?)
247    }
248
249    /// This is a simple method setting [timeouts](https://to.do/) of the session.
250    pub fn set_timeouts(&mut self, timeouts: Timeouts) -> Result<(), WebdriverError> {
251        Ok(set_timeouts(&self.id, timeouts)?)
252    }
253}
254
255impl PartialEq for Session {
256    fn eq(&self, other: &Self) -> bool {
257        self.get_id() == other.get_id()
258    }
259}
260
261impl WebdriverObject for Session {
262    fn get_id(&self) -> &String {
263        &self.id
264    }
265}
266
267impl Drop for Session {
268    #[allow(unused_must_use)]
269    fn drop(&mut self) {
270        self.tabs.clear();
271        if self.webdriver_process.is_some() {
272            warn!("Killing webdriver process (may fail silently)");
273            self.webdriver_process.take().unwrap().kill();
274        }
275    }
276}