using Selenium.Core; using Selenium.Internal; using Selenium.Serializer; using System; using System.Collections; using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; namespace Selenium { /// /// Object through which the user controls the browser. /// /// /// VBScript: /// /// Class Script /// Dim driver /// /// Sub Class_Initialize /// Set driver = CreateObject("Selenium.WebDriver") /// driver.Start "firefox", "http://www.google.com" /// driver.Get "/" /// End Sub /// /// Sub Class_Terminate /// driver.Quit /// End Sub /// End Class /// /// Set s = New Script /// /// /// VBA: /// /// Public Sub Script() /// Dim driver As New WebDriver /// driver.Start "firefox", "http://www.google.com" /// driver.Get "/" /// ... /// driver.Quit /// End Sub /// /// [ProgId("Selenium.WebDriver")] [Guid("0277FC34-FD1B-4616-BB19-E3CCFFAB4234")] [ComVisible(true), ClassInterface(ClassInterfaceType.None)] [Description("Defines the interface through which the user controls the browser using WebDriver")] public class WebDriver : SearchContext, ComInterfaces._WebDriver, IDisposable { const string RUNNING_OBJECT_NAME = "Selenium.WebDriver"; internal Capabilities Capabilities = new Capabilities(); internal Dictionary Preferences = new Dictionary(); internal List Extensions = new List(); internal List Arguments = new List(); internal string Profile = null; internal string Binary = null; internal bool Persistant = false; private Timeouts timeouts = new Timeouts(); private IDriverService _service = null; private RemoteSession _session = null; private string _baseUrl = null; private Proxy _proxy = null; private COMRunningObject _comRunningObj = null; /// /// Creates a new WebDriver object. /// public WebDriver() { UnhandledException.Initialize(); RegisterRunningObject(); COMDisposable.Subscribe(this, typeof(ComInterfaces._WebDriver)); } public WebDriver(string browser) : this() { this.Capabilities.BrowserName = browser; } ~WebDriver() { this.Dispose(); } /// /// Release the resources. /// public void Dispose() { if (_comRunningObj != null) { _comRunningObj.Dispose(); _comRunningObj = null; } if (_service != null) { _service.Dispose(); _service = null; } if (_session != null) { _session = null; } } private void RegisterRunningObject(){ if (_comRunningObj == null) _comRunningObj = new COMRunningObject(this, RUNNING_OBJECT_NAME); } #region Setup /// /// /// public Proxy Proxy { get { return _proxy ?? (_proxy = new Proxy(Capabilities)); } } /// /// Set a specific profile for the firefox webdriver /// /// Profil name (Firefox only) or directory (Firefox and Chrome) /// If true, the browser will be launched without a copy the profile (Firefox only) /// The profile directory can be copied from the user temp folder (run %temp%) before the WebDriver is stopped. It's also possible to create a new Firefox profile by launching firefox with the "-p" switch (firefox.exe -p). /// /// /// Dim driver As New Selenium.FirefoxDriver /// driver.SetProfile "Selenium" 'Firefox only. Profile created by running "..\firefox.exe -p" /// driver.Get "http://www.google.com" /// ... /// /// /// Dim driver As New Selenium.FirefoxDriver /// driver.SetProfile "C:\MyProfil" 'For Chrome and Firefox only /// driver.Get "http://www.google.com" /// ... /// /// public void SetProfile(string nameOrDirectory, bool persistant = false) { Profile = nameOrDirectory; Persistant = persistant; } /// /// Set a specific preference for the firefox webdriver /// /// Preference key /// Preference value public void SetPreference(string key, object value) { Preferences[key] = JSON.Parse(value); } /// /// Set a specific capability for the webdriver /// /// Capability key /// Capability value public void SetCapability(string key, object value) { Capabilities[key] = JSON.Parse(value); } /// /// Add an extension to the browser (For Firefox and Chrome only) /// /// Path to the extension public void AddExtension(string extensionPath) { Extensions.Add(new DriverExtension(extensionPath)); } /// /// Add an argument to be appended to the command line to launch the browser. /// /// Argument public void AddArgument(string argument) { Arguments.Add(argument); } /// /// Set the path to the browser executable to use /// /// Full path public void SetBinary(string path) { if (!File.Exists(path)) throw new Errors.FileNotFoundError(path); this.Binary = path; } #endregion #region Session internal override RemoteSession session { get { if (_session == null) throw new Errors.BrowserNotStartedError(); return _session; } } internal override string uri { get { return string.Empty; } } /// /// Starts a new Selenium testing session /// /// Name of the browser : firefox, ie, chrome, phantomjs /// The base URL /// /// /// Dim driver As New WebDriver() /// driver.Start "firefox", "http://www.google.com" /// driver.Get "/" /// /// public void Start(string browser = null, string baseUrl = null) { try { browser = ExpendBrowserName(browser); switch (browser) { case "firefox": _service = FirefoxDriver.StartService(this); break; case "chrome": _service = ChromeDriver.StartService(this); break; case "phantomjs": _service = PhantomJSDriver.StartService(this); break; case "internet explorer": _service = IEDriver.StartService(this); break; case "MicrosoftEdge": _service = EdgeDriver.StartService(this); break; case "opera": _service = OperaDriver.StartService(this); break; default: throw new Errors.ArgumentError("Invalid browser name: {0}", browser); } this.Capabilities.BrowserName = browser; RegisterRunningObject(); _session = new RemoteSession(_service.Uri, true, this.timeouts); _session.Start(this.Capabilities); if (!string.IsNullOrEmpty(baseUrl)) this.BaseUrl = baseUrl; } catch (SeleniumException) { throw; } catch (Exception ex) { throw new SeleniumException(ex); } } /// /// Starts remotely a new Selenium testing session /// /// Remote executor address (ex : "http://localhost:4444/wd/hub") /// Name of the browser : firefox, ie, chrome, phantomjs, htmlunit, htmlunitwithjavascript, android, ipad, opera /// Browser version /// Platform: WINDOWS, LINUX, MAC, ANDROID... /// /// /// Dim driver As New WebDriver() /// driver.StartRemotely "http://localhost:4444/wd/hub", "ie", 11 /// driver.Get "/" /// /// public void StartRemotely(string executorUri, string browser = null, string version = null, string platform = null) { try { browser = ExpendBrowserName(browser); switch (browser) { case "firefox": FirefoxDriver.ExtendCapabilities(this, true); break; case "chrome": ChromeDriver.ExtendCapabilities(this, true); break; case "phantomjs": PhantomJSDriver.ExtendCapabilities(this, true); break; case "internet explorer": IEDriver.ExtendCapabilities(this, true); break; case "MicrosoftEdge": EdgeDriver.ExtendCapabilities(this, true); break; case "opera": OperaDriver.ExtendCapabilities(this, true); break; } this.Capabilities.Platform = platform; this.Capabilities.BrowserName = browser; if (!string.IsNullOrEmpty(version)) this.Capabilities.BrowserVersion = version; _session = new RemoteSession(executorUri, false, this.timeouts); _session.Start(this.Capabilities); RegisterRunningObject(); } catch (SeleniumException) { throw; } catch (Exception ex) { throw new SeleniumException(ex); } } /// /// Close the Browser and Dispose of WebDriver /// public virtual void Quit() { if (_session != null) { try { if (_session.IsLocal) { if (!(_service is FirefoxService)) _session.Delete(); _service.Quit(_session.server); } else { _session.Delete(); } } catch { } } this.Dispose(); } private string ExpendBrowserName(string browser) { if (string.IsNullOrEmpty(browser)) { browser = this.Capabilities.BrowserName; if (string.IsNullOrEmpty(browser)) throw new Errors.ArgumentError("Browser not defined"); } string browserName = browser.ToLower(); switch (browserName) { case "firefox": case "ff": return "firefox"; case "chrome": case "cr": return "chrome"; case "phantomjs": case "pjs": return "phantomjs"; case "internet explorer": case "internetexplorer": case "iexplore": case "ie": return "internet explorer"; case "microsoft edge": case "microsoftedge": case "edge": return "MicrosoftEdge"; case "opera": case "op": return "opera"; default: return browserName; } } /// /// Sends a customized command /// /// POST, GET or DELETE /// Relative URI. Ex: "/screenshot" /// Optional /// /// Optional /// /// Optional /// /// Optional /// /// Result /// /// /// Set links = driver.Send("POST", "/elements", "using", "css selector", "value", "a") /// /// public object Send(string method, string relativeUri, string param1 = null, object value1 = null, string param2 = null, object value2 = null, string param3 = null, object value3 = null, string param4 = null, object value4 = null) { RequestMethod mth = 0; switch (method.ToUpper()) { case "POST": mth = RequestMethod.POST; break; case "GET": mth = RequestMethod.GET; break; case "DELETE": mth = RequestMethod.DELETE; break; default: throw new Errors.ArgumentError("Unhandled method: " + method); } Dictionary data = null; if (param1 != null) { data = new Dictionary(); data.Add(param1, value1); if (param2 != null) { data.Add(param2, value2); if (param3 != null) { data.Add(param3, value3); if (param4 != null) { data.Add(param4, value3); } } } } object result = session.Send(mth, relativeUri, data); return result; } #endregion #region Interfaces /// /// Manage the browser settings. Need to be defined before the browser is launched /// public Timeouts Timeouts { get { return this.timeouts; } } /// /// Instructs the driver to change its settings. /// public Manage Manage { get { return this.session.manage; } } /// /// Get the actions class /// /// /// /// WebDriver driver = New WebDriver() /// driver.start "firefox", "http://www.google.com" /// driver.get "/" /// driver.Actions.keyDown(Keys.Control).sendKeys("a").perform /// /// public Actions Actions { get { return new Actions(this.session); } } /// /// TouchActions /// public TouchActions TouchActions { get { RemoteSession session = this.session; return new TouchActions(session, this.TouchScreen); } } /// /// Keyboard /// public Keyboard Keyboard { get { return this.session.keyboard; } } /// /// Mouse /// public Mouse Mouse { get { return this.session.mouse; } } /// /// TouchScreen /// public TouchScreen TouchScreen { get { return this.session.touchscreen; } } /// /// Keys /// public Keys Keys { get { return this.session.keyboard.Keys; } } #endregion #region Navigation /// /// Base URL to use a relative URL with Get /// public string BaseUrl { get { return _baseUrl; } set { _baseUrl = value.TrimEnd('/'); } } /// /// Loads a web page in the current browser session. Same as Open method. /// /// URL /// Optional timeout in milliseconds. Infinite=-1 /// Optional - Raise an exception after the timeout when true /// Return true if the url was openned within the timeout, false otherwise public bool Get(string url, int timeout = -1, bool raise = true) { if (_session == null) this.Start(); RemoteSession session = _session; if (string.IsNullOrEmpty(url)) throw new Errors.ArgumentError("Argument 'url' cannot be null."); if (timeout > 0){ session.timeouts.PageLoad = timeout; session.Send(RequestMethod.POST, "/timeouts", "type", "page load", "ms", timeout); } int idx = url.IndexOf("/"); if (idx == 0) { //relative url if (_baseUrl == null) throw new Errors.ArgumentError("Base URL not defined. Define a base URL or use a full URL."); url = string.Concat(_baseUrl, url); } else { //absolute url idx = url.IndexOf('/', idx + 3); if (idx != -1) { _baseUrl = url.Substring(0, idx - 1); } else { _baseUrl = url; } } try { session.Send(RequestMethod.POST, "/url", "url", url); return true; } catch { if (raise) throw; return false; } } /// /// Get the URL the browser is currently displaying. /// /// Current URL public string Url { get { return (string)session.Send(RequestMethod.GET, "/url"); } } /// /// Gets the title of the current browser window. /// /// Title of the window public string Title { get { return session.windows.CurrentWindow.Title; } } /// /// Goes one step backward in the browser history. /// public void GoBack() { this.session.Send(RequestMethod.POST, "/back"); } /// /// Goes one step forward in the browser history. /// public void GoForward() { this.session.Send(RequestMethod.POST, "/forward"); } /// /// Refreshes the current page. /// public void Refresh() { this.session.Send(RequestMethod.POST, "/refresh"); } #endregion #region Windows /// /// Gets an object allowing the user to manipulate the currently-focused browser window. /// public Window Window { get { return session.windows.CurrentWindow; } } /// /// Gets the window handles of open browser windows. /// /// public List Windows { get { List windows, handles; session.windows.ListWindows(out windows, out handles); return windows; } } /// /// Closes the current window. /// public void Close() { this.Window.Close(); } #endregion #region Interaction /// /// Sends a sequence of keystrokes to the browser. /// /// Sequence of keys or a modifier key(Control, Shift or Alt) if the sequence is in keysToSendEx /// Optional - Sequence of keys if keysToSend contains modifier key(Control,Shift...) /// /// To Send mobile to the window : /// /// driver.SendKeys "mobile" /// /// To Send ctrl+a to the window : /// /// driver.SendKeys Keys.Control, "a" /// /// public void SendKeys(string keysOrModifier, string keys = null) { this.session.keyboard.SendKeys(keysOrModifier, keys); } #endregion #region Screenshot /// /// Takes the screenshot of the current window /// /// Time to wait before taking the screenshot in milliseconds /// public Image TakeScreenshot(int delay = 0) { if (delay != 0) SysWaiter.Wait(delay); Image image = (Image)session.Send(RequestMethod.GET, "/screenshot"); return image; } #endregion #region Javascript /// /// Execute a piece of JavaScript in the context of the currently selected frame or window /// /// The JavaScript code to execute. /// The arguments to the script. /// The value specified by the return statement. /// /// /// txt = driver.ExecuteScript("return 'xyz' + arguments[0];", "123") /// /// public object ExecuteScript(string script, object arguments = null) { object args_ex = FormatArguments(arguments); object result = session.javascript.Execute(script, args_ex, true); return result; } /// /// Execute an asynchronous piece of JavaScript in the context of the current frame or window. /// /// Piece of JavaScript code to execute. /// Optional arguments for the script. /// Optional timeout in milliseconds. /// The first argument of the callback function. /// /// /// txt = driver.ExecuteAsyncScript("callback('xyz')");" /// /// public object ExecuteAsyncScript(string script, object arguments = null, int timeout = -1) { object args_ex = FormatArguments(arguments); object result = session.javascript.ExecuteAsync(script, args_ex, true, timeout); return result; } /// /// Waits for a piece of JavaScript to return true or not null. /// /// Piece of JavaScript code to execute. /// Optional arguments for the script. /// Optional timeout in milliseconds. /// Value not null public object WaitForScript(string script, object arguments, int timeout = -1) { object args_ex = FormatArguments(arguments); object result = session.javascript.WaitFor(script, args_ex, timeout); return result; } private static object FormatArguments(object arguments) { if (arguments == null) { return new object[0]; } else if (arguments is IEnumerable && !(arguments is string || arguments is Dictionary)) { return arguments; } else { return new object[] { arguments }; } } #endregion #region Find Elements /// /// Returns the element with focus, or BODY if nothing has focus. /// /// public WebElement ActiveElement() { return WebElement.GetActiveWebElement(session); } #endregion #region PageSource /// /// Gets the source of the page last loaded by the browser. /// public string PageSource() { var result = session.Send(RequestMethod.GET, "/source"); return (string)result; } /// /// Returns the first occurence matching the regular expression. /// /// The regular expression pattern to match. /// Optional - Group number (Zero based) /// String public string PageSourceMatch(string pattern, short group = 0) { const string JS = "return document.body.innerHTML.match(/{0}/)[{1}]"; string code = string.Format(JS, pattern, group); object result = session.javascript.Execute(code, null, false); return (string)result; } /// /// Returns all the occurences matching the regular expression. /// /// The regular expression pattern to match. /// Optional - Group number (Zero based) /// Array of strings or null public List PageSourceMatches(string pattern, short group = 0) { const string JS = "var r=/{0}/g,s=document.body.innerHTML,a=[],m;" + "while(m=r.exec(s))a.push(m[{1}]);return a;"; string code = string.Format(JS, pattern, group); object result = session.javascript.Execute(code, null, false); return (List)result; } #endregion #region Context /// /// Select either the first frame on the page or the main document when a page contains iFrames. /// /// A WebDriver instance focused on the default frame. public void SwitchToDefaultContent() { this.session.frame.SwitchToDefaultContent(); } /// /// Switch focus to the specified window by name. /// /// The name of the window to activate /// Optional timeout in milliseconds /// Optional - Raise an exception after the timeout when true /// Current web driver public Window SwitchToWindowByName(string name, int timeout = -1, bool raise = true) { try { return session.windows.SwitchToWindowByName(name, timeout); } catch (Errors.NoSuchWindowError) { if (raise) throw new Errors.NoSuchWindowError(name); return null; } } /// /// Switch focus to the specified window by title. /// /// The title of the window to activate /// Optional timeout in milliseconds /// Optional - Raise an exception after the timeout when true /// Current web driver public Window SwitchToWindowByTitle(string title, int timeout = -1, bool raise = true) { try { return session.windows.SwitchToWindowByTitle(title, timeout); } catch (Errors.NoSuchWindowError) { if (raise) throw new Errors.NoSuchWindowError(title); return null; } } /// /// Switch the focus to the next window. /// /// Optional timeout in milliseconds /// Optional - Raise an exception after the timeout when true. Default is true. /// Window public Window SwitchToNextWindow(int timeout = -1, bool raise = true) { try { return session.windows.SwitchToNextWindow(timeout); } catch (Errors.NoSuchWindowError) { if (raise) throw; return null; } } /// /// Switch the focus to the previous window /// /// Window public Window SwitchToPreviousWindow() { return session.windows.SwitchToPreviousWindow(); } /// /// Switch focus to the specified frame, by index(zero based), name or WebElement. /// /// The name, index(zero based) or WebElement /// Optional timeout in milliseconds /// Optional - Raise an exception after the timeout when true /// Current web driver public bool SwitchToFrame(object identifier, int timeout = -1, bool raise = true) { try { this.session.frame.SwitchToFrame(identifier, timeout); } catch (Errors.NoSuchFrameError) { if (raise) throw new Errors.NoSuchFrameError(identifier); return false; } return true; } /// /// Select the parent frame of the currently selected frame. /// /// The WebDriver instance focused on the specified frame. public void SwitchToParentFrame() { this.session.frame.SwitchToParentFrame(); } /// /// Switch focus to an alert on the page. /// /// Optional timeout in milliseconds /// Optional - Raise an exception after the timeout when true /// Focused alert public Alert SwitchToAlert(int timeout = -1, bool raise = true) { try { return Alert.SwitchToAlert(session, timeout); } catch (Errors.NoAlertPresentError) { if (raise) throw; return null; } } #endregion #region Wait methods /// /// Wait the specified time in millisecond before executing the next command /// /// Time to wait in millisecond public void Wait(int timems) { SysWaiter.Wait(timems); } /// /// Waits for the delegate function to return not null or true /// /// Returned object or boolean /// Delegate taking the web driver instance as argument /// Timeout in milliseconds /// Variant or boolean public T Until(Func func, int timeout = -1) { if (timeout == -1) timeout = session.timeouts.timeout_implicitwait; return Waiter.WaitUntil(func, this, timeout, 80); } /// /// Waits for a function to return true. VBScript: Function WaitEx(webdriver), VBA: Function WaitEx(webdriver As WebDriver) As Boolean /// /// Function reference. VBScript: wd.WaitFor GetRef(\"WaitEx\") VBA: wd.WaitFor AddressOf WaitEx) /// Optional - Argument to send to the function /// Optional - timeout in milliseconds /// Current WebDriver /// VBA example: /// /// Sub WaitForTitle(driver, argument, result) /// result = driver.Title = argument /// End Sub /// /// Sub testSimple() /// Dim driver As New FirefoxDriver /// driver.Get "http://www.google.com" /// driver.Until AddressOf WaitForTitle, "Google" /// ... /// End Sub /// /// /// /// VBScript example: /// /// Function WaitForTitle(driver, argument) /// WaitForTitle = driver.Title = argument /// End Function /// /// Sub testSimple() /// Dim driver As New FirefoxDriver /// driver.Get "http://www.google.com" /// driver.Until GetRef("WaitForTitle"), "Google", 1000 /// ... /// End Sub /// /// object ComInterfaces._WebDriver.Until(object procedure, object argument, int timeout) { if (timeout == -1) timeout = session.timeouts.timeout_implicitwait; return COMExt.WaitUntilProc(procedure, this, argument, timeout); } #endregion #region Cache /// /// Get the status of the html5 application cache. /// /// {number} Status code for application cache: {UNCACHED = 0, IDLE = 1, CHECKING = 2, DOWNLOADING = 3, UPDATE_READY = 4, OBSOLETE = 5} public CacheState CacheStatus() { return (CacheState)session.Send(RequestMethod.GET, "/application_cache/status"); } #endregion #region Clipboard /// /// Sets the text in the Clipboard /// /// Text public void SetClipBoard(string text) { ClipboardExt.SetText(text); } /// /// Returns the text from the Clipboard /// public string GetClipBoard() { return ClipboardExt.GetText(); } #endregion } }