From 620e1d29bd9ebbff280312aed08ff3f58fefe138 Mon Sep 17 00:00:00 2001 From: tnypxl Date: Sun, 26 Jul 2020 22:35:16 -0500 Subject: [PATCH] Set capabilities from config (#54) * Added a custom dictionary parser for Config.NET. * Allows supporting adding capabilities via config * Implement the custom parser * Allow configuring acceptance of insecure certs. Allow configuring capabilties * Add CreateDriver overload with no args * Rename base options/service map classes Add SetCapability() and AcceptsInsecureCerts() * Implement SetCapabilities() method * Throw error if page key does not exist * Move Selenoid and Docker stuff to their own folder * Fix check for null dictionary --- Basin.Tests/Steps/StepsBase.cs | 2 - Basin/BasinEnv.cs | 5 +- Basin/Config/Extensions/DictionaryParser.cs | 31 ++++++++++++ Basin/Config/Interfaces/IBrowserConfig.cs | 9 ++++ Basin/Core/Browsers/ChromeBrowser.cs | 4 +- Basin/Core/Browsers/FirefoxBrowser.cs | 2 + .../Core/Browsers/InternetExplorerBrowser.cs | 3 ++ .../Browsers/Mappers/ChromeOptionsMapper.cs | 24 +++++++-- .../Browsers/Mappers/ChromeServiceMapper.cs | 2 +- ...erOptionsMapper.cs => DriverOptionsMap.cs} | 11 ++-- ...erServiceMapper.cs => DriverServiceMap.cs} | 4 +- .../Browsers/Mappers/FirefoxOptionsMapper.cs | 43 +++++++++++----- .../Browsers/Mappers/FirefoxServiceMapper.cs | 4 +- .../Mappers/InternetExplorerOptionsMapper.cs | 34 +++++++++---- .../Mappers/InternetExplorerServiceMapper.cs | 2 +- Basin/PageObjects/PageCollection.cs | 5 +- docker-compose.yml | 50 +++++++++++-------- docker/TheInternet/Dockerfile | 7 +++ browsers.json => selenoid/browsers.json | 3 +- 19 files changed, 177 insertions(+), 68 deletions(-) create mode 100644 Basin/Config/Extensions/DictionaryParser.cs rename Basin/Core/Browsers/Mappers/{BrowserOptionsMapper.cs => DriverOptionsMap.cs} (60%) rename Basin/Core/Browsers/Mappers/{BrowserServiceMapper.cs => DriverServiceMap.cs} (67%) create mode 100644 docker/TheInternet/Dockerfile rename browsers.json => selenoid/browsers.json (93%) diff --git a/Basin.Tests/Steps/StepsBase.cs b/Basin.Tests/Steps/StepsBase.cs index 590dc1d..9b278ca 100644 --- a/Basin.Tests/Steps/StepsBase.cs +++ b/Basin.Tests/Steps/StepsBase.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using Basin.PageObjects; using Basin.Selenium; @@ -22,7 +21,6 @@ public static void BeforeFeatureHook() [BeforeScenario] public void BeforeScenarioHook() { - // BasinEnv.UseBrowser("Chrome"); BrowserSession.Init(); } diff --git a/Basin/BasinEnv.cs b/Basin/BasinEnv.cs index eadec0e..f4ed02d 100644 --- a/Basin/BasinEnv.cs +++ b/Basin/BasinEnv.cs @@ -3,6 +3,7 @@ using Basin.Config.Interfaces; using Config.Net; using Basin.PageObjects.Interfaces; +using Basin.Config.Extensions; namespace Basin { @@ -22,7 +23,9 @@ public static class BasinEnv public static void SetConfig(string configPath) { - _config = new CurrentConfig(GetConfig.UseJsonFile(configPath).Build()); + _config = new CurrentConfig(GetConfig.UseJsonFile(configPath) + .UseTypeParser(new DictionaryParser()) + .Build()); Site = _config.Site; Browser = _config.Browser; Pages = _config.Pages; diff --git a/Basin/Config/Extensions/DictionaryParser.cs b/Basin/Config/Extensions/DictionaryParser.cs new file mode 100644 index 0000000..cf4a5b3 --- /dev/null +++ b/Basin/Config/Extensions/DictionaryParser.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Config.Net; +using Newtonsoft.Json; + +namespace Basin.Config.Extensions +{ + public class DictionaryParser : ITypeParser + { + public IEnumerable SupportedTypes => new[] { typeof(Dictionary) }; + + public bool TryParse(string value, Type t, out object result) + { + if (value == null) + { + result = null; + return false; + } + + result = JsonConvert.DeserializeObject(value, t); + + return true; + } + + public string ToRawString(object value) + { + if (value == null) return null; + return JsonConvert.SerializeObject(value); + } + } +} \ No newline at end of file diff --git a/Basin/Config/Interfaces/IBrowserConfig.cs b/Basin/Config/Interfaces/IBrowserConfig.cs index 92885e9..65eb9e1 100644 --- a/Basin/Config/Interfaces/IBrowserConfig.cs +++ b/Basin/Config/Interfaces/IBrowserConfig.cs @@ -25,11 +25,16 @@ public interface IBrowserConfig [Option(DefaultValue = false)] bool Headless { get; set; } + [Option(DefaultValue = false)] + bool AcceptsInsecureCerts { get; set; } + string PathToDriverBinary { get; set; } string PathToBrowserExecutable { get; set; } Uri Host { get; set; } + + Dictionary Capabilities { get; } } public class BrowserConfig : IBrowserConfig @@ -55,5 +60,9 @@ public class BrowserConfig : IBrowserConfig public string PathToBrowserExecutable { get; set; } public Uri Host { get; set; } + + public bool AcceptsInsecureCerts { get; set; } + + public Dictionary Capabilities { get; } } } \ No newline at end of file diff --git a/Basin/Core/Browsers/ChromeBrowser.cs b/Basin/Core/Browsers/ChromeBrowser.cs index 0e36f5b..9bfb2f5 100644 --- a/Basin/Core/Browsers/ChromeBrowser.cs +++ b/Basin/Core/Browsers/ChromeBrowser.cs @@ -24,9 +24,9 @@ public ChromeBrowser(IBrowserConfig config) public IWebDriver Driver { get; set; } - // public void CreateDriver() => Driver = new ChromeDriver(Service, Options); + public void CreateDriver() => CreateDriver(null); - public void CreateDriver(Uri host = null) + public void CreateDriver(Uri host) { Driver = (host == null) ? new ChromeDriver(Service, Options) diff --git a/Basin/Core/Browsers/FirefoxBrowser.cs b/Basin/Core/Browsers/FirefoxBrowser.cs index b750419..d282747 100644 --- a/Basin/Core/Browsers/FirefoxBrowser.cs +++ b/Basin/Core/Browsers/FirefoxBrowser.cs @@ -28,6 +28,8 @@ public FirefoxBrowser(IBrowserConfig config) public IWebDriver Driver { get; set; } + public void CreateDriver() => CreateDriver(null); + public void CreateDriver(Uri host = null) { Driver = host == null diff --git a/Basin/Core/Browsers/InternetExplorerBrowser.cs b/Basin/Core/Browsers/InternetExplorerBrowser.cs index e5b64ca..81245db 100644 --- a/Basin/Core/Browsers/InternetExplorerBrowser.cs +++ b/Basin/Core/Browsers/InternetExplorerBrowser.cs @@ -24,8 +24,11 @@ public InternetExplorerBrowser(IBrowserConfig config) public IWebDriver Driver { get; set; } + public void CreateDriver() => CreateDriver(null); + public void CreateDriver(Uri host) { + Driver = (host == null) ? new InternetExplorerDriver(Service, Options) : new RemoteWebDriver(host, Options); diff --git a/Basin/Core/Browsers/Mappers/ChromeOptionsMapper.cs b/Basin/Core/Browsers/Mappers/ChromeOptionsMapper.cs index fd2d6b5..e4262ab 100644 --- a/Basin/Core/Browsers/Mappers/ChromeOptionsMapper.cs +++ b/Basin/Core/Browsers/Mappers/ChromeOptionsMapper.cs @@ -1,23 +1,25 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Basin.Config.Interfaces; using OpenQA.Selenium.Chrome; namespace Basin.Core.Browsers.Mappers { - public class ChromeOptionsMapper : BrowserOptionsMapper + public class ChromeOptionsMapper : DriverOptionsMap { - public ChromeOptionsMapper() : base(new ChromeOptions()) + public ChromeOptionsMapper() { } - public ChromeOptionsMapper(IBrowserConfig config) : base(new ChromeOptions()) + public ChromeOptionsMapper(IBrowserConfig config) { PathToBrowserBinary = config.PathToBrowserExecutable; BrowserVersion = config.Version; PlatformName = config.PlatformName; Arguments = config.Arguments; EnableHeadlessMode = config.Headless; + AcceptsInsecureCerts = config.AcceptsInsecureCerts; + + SetCapabilities(config.Capabilities); } public override string PathToBrowserBinary @@ -40,6 +42,11 @@ public override IEnumerable Arguments set => Options.AddArguments(value); } + public override bool AcceptsInsecureCerts + { + set => Options.AcceptInsecureCertificates = value; + } + public override bool EnableHeadlessMode { set @@ -48,5 +55,12 @@ public override bool EnableHeadlessMode Options.AddArguments("--headless", "--disable-gpu"); } } + + public override void SetCapabilities(Dictionary caps = null) + { + if (caps == null || caps.Count == 0) return; + + foreach (var cap in caps) Options.AddAdditionalCapability(cap.Key, cap.Value, true); + } } } \ No newline at end of file diff --git a/Basin/Core/Browsers/Mappers/ChromeServiceMapper.cs b/Basin/Core/Browsers/Mappers/ChromeServiceMapper.cs index 86f5979..833c24a 100644 --- a/Basin/Core/Browsers/Mappers/ChromeServiceMapper.cs +++ b/Basin/Core/Browsers/Mappers/ChromeServiceMapper.cs @@ -4,7 +4,7 @@ namespace Basin.Core.Browsers.Mappers { - public class ChromeServiceMapper : BrowserServiceMapper + public class ChromeServiceMapper : DriverServiceMap { public override string PathToDriverBinary { get; set; } diff --git a/Basin/Core/Browsers/Mappers/BrowserOptionsMapper.cs b/Basin/Core/Browsers/Mappers/DriverOptionsMap.cs similarity index 60% rename from Basin/Core/Browsers/Mappers/BrowserOptionsMapper.cs rename to Basin/Core/Browsers/Mappers/DriverOptionsMap.cs index 8863493..a87b119 100644 --- a/Basin/Core/Browsers/Mappers/BrowserOptionsMapper.cs +++ b/Basin/Core/Browsers/Mappers/DriverOptionsMap.cs @@ -3,12 +3,9 @@ namespace Basin.Core.Browsers.Mappers { - public abstract class BrowserOptionsMapper where TDriverOptions : new() + public abstract class DriverOptionsMap where TDriverOptions : new() { - protected BrowserOptionsMapper(TDriverOptions options) - { - Options = options; - } + protected DriverOptionsMap() => Options = new TDriverOptions(); public abstract string PathToBrowserBinary { set; } @@ -20,6 +17,10 @@ protected BrowserOptionsMapper(TDriverOptions options) public abstract bool EnableHeadlessMode { set; } + public abstract bool AcceptsInsecureCerts { set; } + + public abstract void SetCapabilities(Dictionary value); + public TDriverOptions Options { get; } } } \ No newline at end of file diff --git a/Basin/Core/Browsers/Mappers/BrowserServiceMapper.cs b/Basin/Core/Browsers/Mappers/DriverServiceMap.cs similarity index 67% rename from Basin/Core/Browsers/Mappers/BrowserServiceMapper.cs rename to Basin/Core/Browsers/Mappers/DriverServiceMap.cs index bb6d6e0..f30c90b 100644 --- a/Basin/Core/Browsers/Mappers/BrowserServiceMapper.cs +++ b/Basin/Core/Browsers/Mappers/DriverServiceMap.cs @@ -1,9 +1,9 @@ -using Basin.Config.Interfaces; +using Basin.Config.Interfaces; using OpenQA.Selenium; namespace Basin.Core.Browsers.Mappers { - public abstract class BrowserServiceMapper + public abstract class DriverServiceMap { public abstract string PathToDriverBinary { get; set; } diff --git a/Basin/Core/Browsers/Mappers/FirefoxOptionsMapper.cs b/Basin/Core/Browsers/Mappers/FirefoxOptionsMapper.cs index 0bc2229..6775854 100644 --- a/Basin/Core/Browsers/Mappers/FirefoxOptionsMapper.cs +++ b/Basin/Core/Browsers/Mappers/FirefoxOptionsMapper.cs @@ -4,18 +4,22 @@ namespace Basin.Core.Browsers.Mappers { - public class FirefoxOptionsMapper : BrowserOptionsMapper + public class FirefoxOptionsMapper : DriverOptionsMap { - private readonly IBrowserConfig _config; + public FirefoxOptionsMapper() + { + } - public FirefoxOptionsMapper(IBrowserConfig config) : base(new FirefoxOptions()) + public FirefoxOptionsMapper(IBrowserConfig config) { - _config = config; - PathToBrowserBinary = _config.PathToBrowserExecutable; - BrowserVersion = _config.Version; - PlatformName = _config.PlatformName; - Arguments = _config.Arguments; - EnableHeadlessMode = _config.Headless; + PathToBrowserBinary = config.PathToBrowserExecutable; + BrowserVersion = config.Version; + PlatformName = config.PlatformName; + Arguments = config.Arguments; + EnableHeadlessMode = config.Headless; + AcceptsInsecureCerts = config.AcceptsInsecureCerts; + + SetCapabilities(config.Capabilities); } public override string PathToBrowserBinary @@ -35,16 +39,31 @@ public override string PlatformName public override IEnumerable Arguments { - set => Options.AddArguments(value); + set + { + if (value == null) return; + Options.AddArguments(value); + } + } + + public override bool AcceptsInsecureCerts + { + set => Options.AcceptInsecureCertificates = value; } public override bool EnableHeadlessMode { set { - if (value) - Options.AddArgument("-headless"); + if (value) Options.AddArgument("-headless"); } } + + public override void SetCapabilities(Dictionary caps) + { + if (caps == null || caps.Count == 0) return; + + foreach (var cap in caps) Options.AddAdditionalCapability(cap.Key, cap.Value, true); + } } } \ No newline at end of file diff --git a/Basin/Core/Browsers/Mappers/FirefoxServiceMapper.cs b/Basin/Core/Browsers/Mappers/FirefoxServiceMapper.cs index 24e7c87..4c5ca8d 100644 --- a/Basin/Core/Browsers/Mappers/FirefoxServiceMapper.cs +++ b/Basin/Core/Browsers/Mappers/FirefoxServiceMapper.cs @@ -1,12 +1,10 @@ using System; -using System.IO; using Basin.Config.Interfaces; -using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Firefox; namespace Basin.Core.Browsers.Mappers { - public class FirefoxServiceMapper : BrowserServiceMapper + public class FirefoxServiceMapper : DriverServiceMap { public override string PathToDriverBinary { get; set; } diff --git a/Basin/Core/Browsers/Mappers/InternetExplorerOptionsMapper.cs b/Basin/Core/Browsers/Mappers/InternetExplorerOptionsMapper.cs index a7a155b..cc25244 100644 --- a/Basin/Core/Browsers/Mappers/InternetExplorerOptionsMapper.cs +++ b/Basin/Core/Browsers/Mappers/InternetExplorerOptionsMapper.cs @@ -6,22 +6,22 @@ namespace Basin.Core.Browsers.Mappers { - public class InternetExplorerOptionsMapper : BrowserOptionsMapper + public class InternetExplorerOptionsMapper : DriverOptionsMap { - private readonly IBrowserConfig _config; - - public InternetExplorerOptionsMapper() : base(new InternetExplorerOptions()) + public InternetExplorerOptionsMapper() { } - public InternetExplorerOptionsMapper(IBrowserConfig config) : base(new InternetExplorerOptions()) + public InternetExplorerOptionsMapper(IBrowserConfig config) { - _config = config; - PathToBrowserBinary = _config.PathToBrowserExecutable; - BrowserVersion = _config.Version; - PlatformName = _config.PlatformName; - Arguments = _config.Arguments; - EnableHeadlessMode = _config.Headless; + PathToBrowserBinary = config.PathToBrowserExecutable; + BrowserVersion = config.Version; + PlatformName = config.PlatformName; + Arguments = config.Arguments; + EnableHeadlessMode = config.Headless; + AcceptsInsecureCerts = config.AcceptsInsecureCerts; + + SetCapabilities(config.Capabilities); } public override string PathToBrowserBinary @@ -48,6 +48,11 @@ public override IEnumerable Arguments } } + public override bool AcceptsInsecureCerts + { + set => Options.AcceptInsecureCertificates = value; + } + public override bool EnableHeadlessMode { set @@ -55,5 +60,12 @@ public override bool EnableHeadlessMode if (value) throw new NotSupportedException("Internet Explorer does not support headless execution."); } } + + public override void SetCapabilities(Dictionary caps) + { + if (caps == null || caps.Count == 0) return; + + foreach (var cap in caps) Options.AddAdditionalCapability(cap.Key, cap.Value, true); + } } } \ No newline at end of file diff --git a/Basin/Core/Browsers/Mappers/InternetExplorerServiceMapper.cs b/Basin/Core/Browsers/Mappers/InternetExplorerServiceMapper.cs index 46aa6b5..c648d02 100644 --- a/Basin/Core/Browsers/Mappers/InternetExplorerServiceMapper.cs +++ b/Basin/Core/Browsers/Mappers/InternetExplorerServiceMapper.cs @@ -4,7 +4,7 @@ namespace Basin.Core.Browsers.Mappers { - public class InternetExplorerServiceMapper : BrowserServiceMapper + public class InternetExplorerServiceMapper : DriverServiceMap { public override string PathToDriverBinary { get; set; } diff --git a/Basin/PageObjects/PageCollection.cs b/Basin/PageObjects/PageCollection.cs index 0ea1918..ef8659c 100644 --- a/Basin/PageObjects/PageCollection.cs +++ b/Basin/PageObjects/PageCollection.cs @@ -27,9 +27,12 @@ public TPage Get() { var pageKey = typeof(TPage).ToString(); + if (!Pages.ContainsKey(pageKey)) + throw new NullReferenceException($"Collection does not contain a page with key `{pageKey}`."); ; + Pages.TryGetValue(pageKey, out object page); - return (TPage)page ?? throw new NullReferenceException($"Collection does not contain a page with key `{pageKey}`."); + return (TPage)page; } public IDictionary Pages { get; } diff --git a/docker-compose.yml b/docker-compose.yml index b48c653..b6e6c3a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,34 +1,42 @@ -version: "3" +version: '3' services: selenoid: - container_name: selenoid - # network_mode: bridge + network_mode: bridge image: aerokube/selenoid:latest-release volumes: - - "$PWD:/etc/selenoid" + - "./selenoid:/etc/selenoid" - "/var/run/docker.sock:/var/run/docker.sock" - command: - [ - "-conf", - "/etc/selenoid/browsers.json", - "-service-startup-timeout", - "3m0s", - "-session-attempt-timeout", - "3m0s", - "-session-delete-timeout", - "3m0s", - "-timeout", - "5m0s", - ] + - "./selenoid/video:/opt/selenoid/video" + - "./selenoid/logs:/opt/selenoid/logs" + environment: + - OVERRIDE_VIDEO_OUTPUT_DIR=./selenoid/video + command: ["-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs", "-service-startup-timeout", "1m"] ports: - "4444:4444" + selenoid-ui: + network_mode: bridge + image: "aerokube/selenoid-ui" links: - - app:app + - selenoid + - app + ports: + - "8080:8080" + command: ["--selenoid-uri", "http://selenoid:4444"] + + # chrome: + # networks: + # selenoid: null + # image: selenoid/chrome:83.0 + + # firefox: + # networks: + # selenoid: null + # image: selenoid/firefox:78.0 app: - container_name: app - image: gprestes/the-internet - # network_mode: bridge + network_mode: bridge + build: ./docker/TheInternet + # image: gprestes/the-internet ports: - "7080:5000" \ No newline at end of file diff --git a/docker/TheInternet/Dockerfile b/docker/TheInternet/Dockerfile new file mode 100644 index 0000000..5f44e52 --- /dev/null +++ b/docker/TheInternet/Dockerfile @@ -0,0 +1,7 @@ +FROM ruby:2.6.5 +RUN git clone https://github.com/tnypxl/the-internet.git app +WORKDIR /app +RUN gem install bundler +RUN bundle install +EXPOSE 5000 +CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "5000"] \ No newline at end of file diff --git a/browsers.json b/selenoid/browsers.json similarity index 93% rename from browsers.json rename to selenoid/browsers.json index 12db781..9e48e4b 100644 --- a/browsers.json +++ b/selenoid/browsers.json @@ -16,10 +16,11 @@ "83.0": { "image": "selenoid/chrome:83.0", "port": "4444", - "path": "/wd/hub", + "path": "/", "tmpfs": { "/tmp": "size=512m" } + } } }