├── Build.ps1
├── Coverage.ps1
├── Version.ps1
├── src
└── Nito.BrowserBoss
│ ├── linqpad-samples
│ └── Basic Usage.linq
│ ├── Loggers
│ ├── NullLogger.cs
│ ├── ConsoleLogger.cs
│ └── ILogger.cs
│ ├── Finders
│ ├── FindByXPath.cs
│ ├── FindByValue.cs
│ ├── FindByText.cs
│ ├── FindByNormalizeSpaceText.cs
│ ├── IFind.cs
│ ├── FindByLabel.cs
│ ├── FindByJQueryCss.cs
│ └── FindExtensions.cs
│ ├── WebDrivers
│ ├── IWebDriverSetup.cs
│ ├── LocalDirectory.cs
│ ├── FirefoxWebDriverSetup.cs
│ ├── BrowserUtility.cs
│ ├── InternetExplorerWebDriverSetup.cs
│ ├── ChromeWebDriverSetup.cs
│ └── WebDriverSetupBase.cs
│ ├── EnumerableExtensions.cs
│ ├── Nito.BrowserBoss.csproj
│ ├── Utility.cs
│ ├── Browser.cs
│ ├── Element.cs
│ ├── Boss.cs
│ └── Session.cs
├── appveyor.yml
├── examples
└── Example
│ ├── App.config
│ ├── Program.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ └── Example.csproj
├── test
└── UnitTests
│ ├── UtilityUnitTests.cs
│ └── UnitTests.csproj
├── LICENSE
├── Nito.BrowserBoss.sln
├── .gitattributes
├── .gitignore
└── README.md
/Build.ps1:
--------------------------------------------------------------------------------
1 | iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/StephenCleary/BuildTools/6efa9f040c0622d60dc167947b7d35b55d071f25/Build.ps1'))
2 |
--------------------------------------------------------------------------------
/Coverage.ps1:
--------------------------------------------------------------------------------
1 | iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/StephenCleary/BuildTools/6efa9f040c0622d60dc167947b7d35b55d071f25/Coverage.ps1'))
2 |
--------------------------------------------------------------------------------
/Version.ps1:
--------------------------------------------------------------------------------
1 | param([String]$oldVersion="", [String]$newVersion="")
2 | iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/StephenCleary/BuildTools/f27313f986ce2b26b091b87649771b0bcee0c30c/Version.ps1'))
3 |
4 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/linqpad-samples/Basic Usage.linq:
--------------------------------------------------------------------------------
1 |
2 | Nito.BrowserBoss
3 | static Nito.BrowserBoss.Boss
4 |
5 |
6 | Url = "https://github.com/StephenCleary/BrowserBoss";
7 |
8 | Click("README.md");
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Loggers/NullLogger.cs:
--------------------------------------------------------------------------------
1 | namespace Nito.BrowserBoss.Loggers
2 | {
3 | ///
4 | /// A logger that ignores everything.
5 | ///
6 | public sealed class NullLogger : ILogger
7 | {
8 | void ILogger.WriteLine(string text)
9 | {
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Loggers/ConsoleLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Nito.BrowserBoss.Loggers
4 | {
5 | ///
6 | /// A logger that writes all messages to the console window.
7 | ///
8 | public sealed class ConsoleLogger : ILogger
9 | {
10 | void ILogger.WriteLine(string text)
11 | {
12 | Console.WriteLine(text);
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Loggers/ILogger.cs:
--------------------------------------------------------------------------------
1 | namespace Nito.BrowserBoss.Loggers
2 | {
3 | ///
4 | /// A simple tracing logger.
5 | ///
6 | public interface ILogger
7 | {
8 | ///
9 | /// Writes a line of text to the logger.
10 | ///
11 | /// The text to write.
12 | void WriteLine(string text);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Finders/FindByXPath.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using OpenQA.Selenium;
3 |
4 | namespace Nito.BrowserBoss.Finders
5 | {
6 | ///
7 | /// Finds elements by XPath strings.
8 | ///
9 | public sealed class FindByXPath : IFind
10 | {
11 | IReadOnlyCollection IFind.Find(ISearchContext context, string searchText)
12 | {
13 | return context.FindElements(By.XPath(searchText));
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Finders/FindByValue.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using OpenQA.Selenium;
3 |
4 | namespace Nito.BrowserBoss.Finders
5 | {
6 | ///
7 | /// Finds elements by their value.
8 | ///
9 | public sealed class FindByValue : IFind
10 | {
11 | IReadOnlyCollection IFind.Find(ISearchContext context, string searchText)
12 | {
13 | return context.FindElements(By.CssSelector("*[value=" + Utility.CssString(searchText) + "]"));
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Finders/FindByText.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using OpenQA.Selenium;
3 |
4 | namespace Nito.BrowserBoss.Finders
5 | {
6 | ///
7 | /// Finds elements by their text value.
8 | ///
9 | public sealed class FindByText : IFind
10 | {
11 | IReadOnlyCollection IFind.Find(ISearchContext context, string searchText)
12 | {
13 | return context.FindElements(By.XPath(".//*[text() = " + Utility.XPathString(searchText) + "]"));
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | os: Visual Studio 2017
3 | configuration: Debug
4 | environment:
5 | COVERALLS_REPO_TOKEN: KYm0YEBE2t3vOQTiWu9fTZGQndd3WgsMt
6 | skip_branch_with_pr: true
7 | build_script:
8 | - ps: ./Build.ps1
9 | test_script:
10 | - ps: ./Coverage.ps1
11 | artifacts:
12 | - path: 'src\**\*.nupkg'
13 | name: NuGet Packages
14 | - path: 'src\**\*.snupkg'
15 | name: NuGet Symbol Packages
16 | deploy:
17 | provider: NuGet
18 | api_key:
19 | secure: QeC34B7ohkvqbwCOKmavQWhitZNYLX/EFdgK8CfL5jEujWw2L85qrzuME8CQRBEb
20 | on:
21 | APPVEYOR_REPO_TAG: true
22 |
--------------------------------------------------------------------------------
/examples/Example/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Finders/FindByNormalizeSpaceText.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using OpenQA.Selenium;
3 |
4 | namespace Nito.BrowserBoss.Finders
5 | {
6 | ///
7 | /// Finds elements by their text value.
8 | ///
9 | public sealed class FindByNormalizeSpaceText : IFind
10 | {
11 | IReadOnlyCollection IFind.Find(ISearchContext context, string searchText)
12 | {
13 | return context.FindElements(By.XPath(".//*[normalize-space(text()) = normalize-space(" + Utility.XPathString(searchText) + ")]"));
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/WebDrivers/IWebDriverSetup.cs:
--------------------------------------------------------------------------------
1 | using OpenQA.Selenium;
2 |
3 | namespace Nito.BrowserBoss.WebDrivers
4 | {
5 | ///
6 | /// Handles automatic WebDriver installation/updating.
7 | ///
8 | public interface IWebDriverSetup
9 | {
10 | ///
11 | /// Starts a new instance of the web driver, installing or updating it as necessary.
12 | ///
13 | /// Whether to hide the Selenium command window.
14 | IWebDriver Start(bool hideCommandWindow = true);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/EnumerableExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Nito.BrowserBoss
5 | {
6 | ///
7 | /// Utility methods for sequences.
8 | ///
9 | public static class EnumerableExtensions
10 | {
11 | ///
12 | /// Applies an action to all elements.
13 | ///
14 | /// The source sequence of elements.
15 | /// The action to perform on each element.
16 | public static void Apply(this IEnumerable elements, Action action)
17 | {
18 | foreach (var e in elements)
19 | action(e);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Finders/IFind.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using OpenQA.Selenium;
3 |
4 | namespace Nito.BrowserBoss.Finders
5 | {
6 | ///
7 | /// A "finder" is an object that knows how to search for matching elements given a search string.
8 | ///
9 | public interface IFind
10 | {
11 | ///
12 | /// Finds all matching elements. May throw exceptions on error.
13 | ///
14 | /// The context of the search. All results should be within this context.
15 | /// The search string used to match the elements.
16 | IReadOnlyCollection Find(ISearchContext context, string searchText);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/WebDrivers/LocalDirectory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Nito.BrowserBoss.WebDrivers
5 | {
6 | ///
7 | /// The local directories used by BrowserBoss.
8 | ///
9 | public static class LocalDirectories
10 | {
11 | ///
12 | /// Gets the private local directory that web drivers should use for their installations.
13 | ///
14 | /// The name of the web driver.
15 | public static string WebDriverPath(string webDriverName)
16 | {
17 | return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Nito", "BrowserBoss", "WebDrivers", webDriverName);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/WebDrivers/FirefoxWebDriverSetup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using OpenQA.Selenium;
7 | using OpenQA.Selenium.Firefox;
8 |
9 | namespace Nito.BrowserBoss.WebDrivers
10 | {
11 | ///
12 | /// Installs/updates the Chrome web driver.
13 | ///
14 | public sealed class FirefoxWebDriverSetup : IWebDriverSetup
15 | {
16 | ///
17 | /// Starts a new instance of the web driver, installing or updating it as necessary.
18 | ///
19 | /// Whether to hide the Selenium command window.
20 | public IWebDriver Start(bool hideCommandWindow = true)
21 | {
22 | return new FirefoxDriver();
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/test/UnitTests/UtilityUnitTests.cs:
--------------------------------------------------------------------------------
1 | using Nito.BrowserBoss;
2 | using System;
3 | using Xunit;
4 |
5 | namespace UnitTests
6 | {
7 | public class UtilityUnitTests
8 | {
9 | [Theory]
10 | [InlineData("a", "'a'")]
11 | [InlineData("a'", "\"a'\"")]
12 | [InlineData("a\"", "\'a\"'")]
13 | [InlineData("a\"'", "\"a\\\"'\"")]
14 | [InlineData("a\"'\\", "\"a\\\"'\\\\\"")]
15 | public void CssString_QuotesAndEscapes(string input, string expected)
16 | {
17 | Assert.Equal(expected, Utility.CssString(input));
18 | }
19 |
20 | [Theory]
21 | [InlineData("a", "'a'")]
22 | [InlineData("a'", "\"a'\"")]
23 | [InlineData("a\"", "'a\"'")]
24 | public void XPathString_QuotesAndEscapes(string input, string expected)
25 | {
26 | Assert.Equal(expected, Utility.XPathString(input));
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/test/UnitTests/UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.1
5 |
6 |
7 |
8 |
9 | all
10 | runtime; build; native; contentfiles; analyzers
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Stephen Cleary
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/examples/Example/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Nito.BrowserBoss;
7 |
8 | namespace Example
9 | {
10 | class Program
11 | {
12 | static void Main(string[] args)
13 | {
14 | try
15 | {
16 | // This is a sample site designed to be difficult to script.
17 | Boss.Url = "http://newtours.demoaut.com/";
18 |
19 | Boss.Click("REGISTER"); // You can click links by text
20 | Boss.Write("#email", "StephenCleary"); // CSS selectors are commonly used
21 | Boss.Write("//input[@type='password']", "password"); // XPath also works fine
22 | // Note that the last line matched both password inputs, and filled them both in.
23 | Boss.Click("input[name='register']");
24 |
25 | Console.WriteLine("Done");
26 | }
27 | catch (Exception ex)
28 | {
29 | Console.WriteLine(ex);
30 | }
31 | Console.ReadKey();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/examples/Example/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Example")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Example")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("3a719aed-f090-4ecb-b7a4-5c95e6537ce1")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Finders/FindByLabel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using OpenQA.Selenium;
4 |
5 | namespace Nito.BrowserBoss.Finders
6 | {
7 | ///
8 | /// Finds elements by a matching label. The search string is the label text, and the returned element is the form element that label is for.
9 | ///
10 | public sealed class FindByLabel : IFind
11 | {
12 | private static IEnumerable DoFind(ISearchContext context, string searchText)
13 | {
14 | foreach (var label in context.FindElements(By.XPath(".//label[text() = " + Utility.XPathString(searchText) + "]")))
15 | {
16 | var forAttribute = label.GetAttribute("for");
17 | if (forAttribute != null)
18 | {
19 | foreach (var e in context.FindElements(By.Id(forAttribute)))
20 | yield return e;
21 | }
22 | else
23 | {
24 | foreach (var e in label.FindElements(By.XPath("./following-sibling::*[1]")))
25 | {
26 | if (e.TagName == "select" || e.TagName == "textarea" || (e.TagName == "input" && e.GetAttribute("type") != "hidden"))
27 | yield return e;
28 | }
29 | }
30 | }
31 | }
32 |
33 | IReadOnlyCollection IFind.Find(ISearchContext context, string searchText)
34 | {
35 | return DoFind(context, searchText).ToArray();
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/WebDrivers/BrowserUtility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Win32;
3 |
4 | namespace Nito.BrowserBoss.WebDrivers
5 | {
6 | ///
7 | /// Helper methods for working with browsers.
8 | ///
9 | public static class BrowserUtility
10 | {
11 | ///
12 | /// Gets the WebDriver setup for the user's default browser.
13 | ///
14 | public static IWebDriverSetup GetSetupForDefaultBrowser()
15 | {
16 | var defaultBrowserIdentifier = GetDefaultBrowserIdentifier();
17 | if (defaultBrowserIdentifier.IndexOf("IE", StringComparison.InvariantCultureIgnoreCase) != -1)
18 | return new InternetExplorerWebDriverSetup();
19 | if (defaultBrowserIdentifier.IndexOf("Chrome", StringComparison.InvariantCultureIgnoreCase) != -1)
20 | return new ChromeWebDriverSetup();
21 | if (defaultBrowserIdentifier.IndexOf("Firefox", StringComparison.InvariantCultureIgnoreCase) != -1)
22 | return new FirefoxWebDriverSetup();
23 |
24 | // If it's an unrecognized browser type, just punt and start IE.
25 | return new InternetExplorerWebDriverSetup();
26 | }
27 |
28 | private static string GetDefaultBrowserIdentifier()
29 | {
30 | using (var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice"))
31 | {
32 | if (key == null)
33 | return string.Empty;
34 | var identifier = key.GetValue("Progid") as string;
35 | return identifier ?? string.Empty;
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Nito.BrowserBoss.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net461
5 | Browser automation toolkit.
6 | 3.0.1
7 |
8 | Stephen Cleary
9 | true
10 | true
11 | browser;automation;linqpad-samples
12 | https://github.com/StephenCleary/BrowserBoss
13 | MIT
14 | true
15 | true
16 | snupkg
17 | Nito
18 | latest
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | true
30 | linqpad-samples
31 |
32 |
33 |
34 |
35 |
36 | all
37 | runtime; build; native; contentfiles; analyzers
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Nito.BrowserBoss.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28803.352
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nito.BrowserBoss", "src\Nito.BrowserBoss\Nito.BrowserBoss.csproj", "{0733220D-463C-4596-BFA5-93FD5DB168B0}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "examples\Example\Example.csproj", "{29B6977D-9F63-434B-B2DF-4550A743DD3F}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "test\UnitTests\UnitTests.csproj", "{CD25A8D3-95DD-4634-B2D0-0299A28F2BEF}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {0733220D-463C-4596-BFA5-93FD5DB168B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {0733220D-463C-4596-BFA5-93FD5DB168B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {0733220D-463C-4596-BFA5-93FD5DB168B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {0733220D-463C-4596-BFA5-93FD5DB168B0}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {29B6977D-9F63-434B-B2DF-4550A743DD3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {29B6977D-9F63-434B-B2DF-4550A743DD3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {29B6977D-9F63-434B-B2DF-4550A743DD3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {29B6977D-9F63-434B-B2DF-4550A743DD3F}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {CD25A8D3-95DD-4634-B2D0-0299A28F2BEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {CD25A8D3-95DD-4634-B2D0-0299A28F2BEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {CD25A8D3-95DD-4634-B2D0-0299A28F2BEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {CD25A8D3-95DD-4634-B2D0-0299A28F2BEF}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {24D30368-AE77-492F-83F0-02F0BACB4B0F}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/WebDrivers/InternetExplorerWebDriverSetup.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.IO.Compression;
3 | using System.Net;
4 | using OpenQA.Selenium;
5 | using OpenQA.Selenium.IE;
6 |
7 | namespace Nito.BrowserBoss.WebDrivers
8 | {
9 | ///
10 | /// Manages the installation/updates for the IE web driver.
11 | ///
12 | public sealed class InternetExplorerWebDriverSetup : WebDriverSetupBase
13 | {
14 | ///
15 | /// Creates an instance responsible for installing/updating the IE web driver.
16 | ///
17 | public InternetExplorerWebDriverSetup()
18 | : base("IE")
19 | {
20 | }
21 |
22 | ///
23 | /// Downloads the specified web driver version.
24 | ///
25 | /// The version to download.
26 | protected override void Update(string version)
27 | {
28 | using (var client = new WebClient())
29 | using (var stream = client.OpenRead("http://selenium-release.storage.googleapis.com/" + version + "/IEDriverServer_Win32_" + version + ".0.zip"))
30 | using (var archive = new ZipArchive(stream))
31 | archive.ExtractToDirectory(Path.Combine(ParentPath, version));
32 | }
33 |
34 | ///
35 | /// Returns the newest version available for download. Currently always returns "2.45".
36 | ///
37 | protected override string AvailableVersion()
38 | {
39 | // TODO: better implementation
40 | return "2.47";
41 | }
42 |
43 | ///
44 | /// Starts a new instance of the web driver, installing or updating it as necessary.
45 | ///
46 | /// Whether to hide the Selenium command window.
47 | public override IWebDriver Start(bool hideCommandWindow = true)
48 | {
49 | var path = Install();
50 | var driverService = InternetExplorerDriverService.CreateDefaultService(path);
51 | driverService.HideCommandPromptWindow = hideCommandWindow;
52 | return new InternetExplorerDriver(driverService);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/WebDrivers/ChromeWebDriverSetup.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.IO.Compression;
3 | using System.Net;
4 | using OpenQA.Selenium;
5 | using OpenQA.Selenium.Chrome;
6 |
7 | namespace Nito.BrowserBoss.WebDrivers
8 | {
9 | ///
10 | /// Installs/updates the Chrome web driver.
11 | ///
12 | public sealed class ChromeWebDriverSetup : WebDriverSetupBase
13 | {
14 | ///
15 | /// Creates an instance responsible for installing/updating the Chrome web driver.
16 | ///
17 | public ChromeWebDriverSetup()
18 | : base("Chrome")
19 | {
20 | }
21 |
22 | ///
23 | /// Downloads the specified web driver version.
24 | ///
25 | /// The version to download.
26 | protected override void Update(string version)
27 | {
28 | using (var client = new WebClient())
29 | using (var stream = client.OpenRead("http://chromedriver.storage.googleapis.com/" + version + "/chromedriver_win32.zip"))
30 | using (var archive = new ZipArchive(stream))
31 | archive.ExtractToDirectory(Path.Combine(ParentPath, version));
32 | }
33 |
34 | ///
35 | /// Returns the newest version available for download.
36 | ///
37 | protected override string AvailableVersion()
38 | {
39 | using (var client = new WebClient())
40 | {
41 | var versionString = client.DownloadString("http://chromedriver.storage.googleapis.com/LATEST_RELEASE");
42 | return System.Text.RegularExpressions.Regex.Replace(versionString, @"\s", "");
43 | }
44 | }
45 |
46 | ///
47 | /// Starts a new instance of the web driver, installing or updating it as necessary.
48 | ///
49 | /// Whether to hide the Selenium command window.
50 | public override IWebDriver Start(bool hideCommandWindow = true)
51 | {
52 | var path = Install();
53 | var driverService = ChromeDriverService.CreateDefaultService(path);
54 | driverService.HideCommandPromptWindow = hideCommandWindow;
55 | return new ChromeDriver(driverService);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/examples/Example/Example.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {29B6977D-9F63-434B-B2DF-4550A743DD3F}
8 | Exe
9 | Properties
10 | Example
11 | Example
12 | v4.7.2
13 | 512
14 |
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {0733220d-463c-4596-bfa5-93fd5db168b0}
54 | Nito.BrowserBoss
55 |
56 |
57 |
58 |
65 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Utility.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace Nito.BrowserBoss
4 | {
5 | ///
6 | /// String utility methods.
7 | ///
8 | public static class Utility
9 | {
10 | ///
11 | /// Surrounds the string value with single or double quotes, returning an expression if necessary.
12 | ///
13 | /// The string value.
14 | public static string XPathString(string value)
15 | {
16 | // Quickly handle the common cases.
17 | if (!value.Contains("'"))
18 | return "'" + value + "'";
19 | if (!value.Contains("\""))
20 | return "\"" + value + "\"";
21 |
22 | // TODO: unit testing. http://stackoverflow.com/questions/1341847/special-character-in-xpath-query
23 | var sb = new StringBuilder();
24 | sb.Append("concat(");
25 | var index = 0;
26 | while (true)
27 | {
28 | var nextDoubleQuote = value.IndexOf('"', index);
29 | var nextSingleQuote = value.IndexOf('\'', index);
30 |
31 | if (nextDoubleQuote == -1)
32 | {
33 | sb.Append("\"" + value.Substring(index) + "\")");
34 | return sb.ToString();
35 | }
36 |
37 | if (nextSingleQuote == -1)
38 | {
39 | sb.Append("'" + value.Substring(index) + "'");
40 | return sb.ToString();
41 | }
42 |
43 | if (index != 0)
44 | sb.Append(",");
45 | if (nextDoubleQuote > nextSingleQuote)
46 | {
47 | sb.Append("\"" + value.Substring(index, nextDoubleQuote - index - 1) + "\",");
48 | index = nextDoubleQuote;
49 | }
50 | else
51 | {
52 | sb.Append("'" + value.Substring(index, nextSingleQuote - index - 1) + "',");
53 | index = nextSingleQuote;
54 | }
55 | }
56 | }
57 |
58 | ///
59 | /// Escapes the string value as necessary and surrounds it with single or double quotes.
60 | ///
61 | /// The string value.
62 | public static string CssString(string value)
63 | {
64 | if (!value.Contains("\\"))
65 | {
66 | if (!value.Contains("'"))
67 | return "'" + value + "'";
68 | if (!value.Contains("\""))
69 | return "\"" + value + "\"";
70 | }
71 |
72 | // TODO: unit tests
73 | var sb = new StringBuilder();
74 | foreach (var ch in value)
75 | {
76 | if (ch == '\\')
77 | sb.Append("\\\\");
78 | else if (ch == '\"')
79 | sb.Append("\\\"");
80 | else
81 | sb.Append(ch);
82 | }
83 | return "\"" + sb + "\"";
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Browser.cs:
--------------------------------------------------------------------------------
1 | using Nito.BrowserBoss.WebDrivers;
2 | using OpenQA.Selenium;
3 | using OpenQA.Selenium.Chrome;
4 | using OpenQA.Selenium.Firefox;
5 | using OpenQA.Selenium.IE;
6 |
7 | namespace Nito.BrowserBoss
8 | {
9 | ///
10 | /// Represents a browser instance.
11 | ///
12 | public sealed class Browser
13 | {
14 | ///
15 | /// Creates a browser wrapper for the specified web driver.
16 | ///
17 | /// The web driver to wrap.
18 | public Browser(IWebDriver webDriver)
19 | {
20 | WebDriver = webDriver;
21 | }
22 |
23 | ///
24 | /// The underlying Selenium web driver.
25 | ///
26 | public IWebDriver WebDriver { get; private set; }
27 |
28 | ///
29 | /// Gets the current URL or navigates to a new URL.
30 | ///
31 | public string Url
32 | {
33 | get { return WebDriver.Url; }
34 | set { WebDriver.Navigate().GoToUrl(value); }
35 | }
36 |
37 | ///
38 | /// Starts the user's default browser, updating the web driver if necessary.
39 | ///
40 | /// Whether to hide the Selenium command window.
41 | public static Browser StartDefault(bool hideCommandWindow = true)
42 | {
43 | return new Browser(BrowserUtility.GetSetupForDefaultBrowser().Start(hideCommandWindow));
44 | }
45 |
46 | ///
47 | /// Starts the Chrome browser, updating the web driver if necessary.
48 | ///
49 | /// Whether to hide the Selenium command window.
50 | public static Browser StartChrome(bool hideCommandWindow = true)
51 | {
52 | return new Browser(new ChromeWebDriverSetup().Start(hideCommandWindow));
53 | }
54 |
55 | ///
56 | /// Starts the Internet Explorer browser, updating the web driver if necessary.
57 | ///
58 | /// Whether to hide the Selenium command window.
59 | public static Browser StartInternetExplorer(bool hideCommandWindow = true)
60 | {
61 | return new Browser(new InternetExplorerWebDriverSetup().Start(hideCommandWindow));
62 | }
63 |
64 | ///
65 | /// Starts the Firefox browser.
66 | ///
67 | /// Whether to hide the Selenium command window.
68 | public static Browser StartFirefox(bool hideCommandWindow = true)
69 | {
70 | return new Browser(new FirefoxWebDriverSetup().Start(hideCommandWindow));
71 | }
72 |
73 | ///
74 | /// Executes JavaScript in this browser.
75 | ///
76 | /// The JavaScript to execute.
77 | /// The arguments to pass to .
78 | public dynamic Script(string script, params object[] args)
79 | {
80 | return ((IJavaScriptExecutor)WebDriver).ExecuteScript(script, args);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Nito.BrowserBoss/Finders/FindByJQueryCss.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Net;
6 | using Newtonsoft.Json;
7 | using OpenQA.Selenium;
8 |
9 | namespace Nito.BrowserBoss.Finders
10 | {
11 | ///
12 | /// Finds elements by JQuery-style CSS selectors. This supports JQuery extensions to CSS selectors: https://api.jquery.com/category/selectors/jquery-selector-extensions/
13 | ///
14 | public sealed class FindByJQueryCss : IFind
15 | {
16 | ///
17 | /// The URI used to download jQuery, if jQuery is not already loaded on the page.
18 | ///
19 | public string JQueryUri { get; set; } = "https://code.jquery.com/jquery-3.4.1.slim.min.js";
20 |
21 | IReadOnlyCollection IFind.Find(ISearchContext context, string searchText)
22 | {
23 | return context.FindElements(new ByJQuery(JQueryUri, searchText));
24 | }
25 |
26 | private sealed class ByJQuery : By
27 | {
28 | private readonly string _jQueryUri;
29 | private readonly string _selector;
30 |
31 | public ByJQuery(string jQueryUri, string selector)
32 | {
33 | _jQueryUri = jQueryUri;
34 | _selector = JsonConvert.SerializeObject(selector);
35 | Description = $"ByJQuery: {selector}";
36 | }
37 |
38 | public override ReadOnlyCollection FindElements(ISearchContext context)
39 | {
40 | var scriptExecutor = (IJavaScriptExecutor)context;
41 | EnsureJQueryIsLoaded(scriptExecutor);
42 |
43 | IEnumerable