diff --git a/.github/workflows/dotnetCi.yml b/.github/workflows/dotnetCi.yml
index 4d9a126..fee0793 100644
--- a/.github/workflows/dotnetCi.yml
+++ b/.github/workflows/dotnetCi.yml
@@ -24,10 +24,19 @@ jobs:
timeout-minutes: 30
env:
+ RunNumber: ${{ github.run_number }}.${{ github.run_attempt }}
VersionSuffix: ci.${{ github.run_number }}
+ SonarCloudProject: csf-dev_CSF.Extensions.WebDriver
+ SonarCloudUsername: craigfowler-github
+ SonarCloudUrl: https://sonarcloud.io
+ SonarCloudSecretKey: ${{ secrets.SONARCLOUDKEY }}
Configuration: Debug
+ Tfm: net8.0
DotnetVersion: 8.0.x
DISPLAY: :99
+ BranchName: ${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }}
+ BranchParam: ${{ github.event_name == 'pull_request' && 'sonar.pullrequest.branch' || 'sonar.branch.name' }}
+ PullRequestParam: ${{ github.event_name == 'pull_request' && format('/d:sonar.pullrequest.key={0}', github.event.number) || '' }}
steps:
- name: Checkout
@@ -43,6 +52,10 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DotnetVersion }}
+ - name: Install SonarScanner
+ run: dotnet tool install --global dotnet-sonarscanner
+ - name: Install Coverlet console
+ run: dotnet tool install --global coverlet.console --version 8.0.1
- name: Install DocFX
run: dotnet tool install --global docfx
# See https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
@@ -58,15 +71,35 @@ jobs:
# Build and test the solution
+ - name: Start SonarScanner
+ run: >
+ dotnet sonarscanner begin
+ /k:${{ env.SonarCloudProject }}
+ /v:GitHub_build_${{ env.RunNumber }}
+ /o:${{ env.SonarCloudUsername }}
+ /d:sonar.host.url=${{ env.SonarCloudUrl }}
+ /d:sonar.token=${{ env.SonarCloudSecretKey }}
+ /d:${{ env.BranchParam }}=${{ env.BranchName }} ${{ env.PullRequestParam }}
+ /s:$PWD/.sonarqube-analysisproperties.xml
- name: Build the solution
- run: dotnet build -c ${{ env.Configuration }}
- - name: Run .NET tests
+ run: dotnet build -c ${{ env.Configuration }} --no-incremental
+ - name: Run .NET tests with coverage
id: dotnet_tests
- run: dotnet test
- continue-on-error: true
+ shell: bash {0}
+ run: |
+ coverlet CSF.Extensions.WebDriver.Tests/bin/$Configuration/$Tfm/CSF.Extensions.WebDriver.Tests.dll --target "dotnet" --targetargs "test -c $Configuration --no-build --logger:nunit --test-adapter-path:." -f opencover -o "CSF.Extensions.WebDriver.Tests/TestResults/CSF.Extensions.WebDriver.Tests.opencover.xml"
+ exitCode=$?
+ if [ $exitCode -ne 0 ]
+ then
+ echo "One or more tests have failed; this build should eventually fail"
+ echo "failures=true" >> "$GITHUB_OUTPUT"
+ fi
# Post-test tasks (artifacts, overall status)
+ - name: Stop SonarScanner
+ run:
+ dotnet sonarscanner end /d:sonar.token=${{ env.SonarCloudSecretKey }}
- name: Gracefully stop Xvfb
run: killall Xvfb
continue-on-error: true
@@ -74,9 +107,9 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: NUnit test results
- path: Tests/*.Tests/**/TestResults.xml
+ path: '**/TestResults/*.xml'
- name: Fail the build if any test failures
- if: steps.dotnet_tests.outcome == 'failure'
+ if: steps.dotnet_tests.outputs.failures == 'failure'
run: |
echo "Failing the build due to test failures"
exit 1
diff --git a/.github/workflows/publishDocsWebsite.yml b/.github/workflows/publishDocsWebsite.yml
new file mode 100644
index 0000000..45df5ee
--- /dev/null
+++ b/.github/workflows/publishDocsWebsite.yml
@@ -0,0 +1,47 @@
+name: Update docs website
+
+on:
+ push:
+ branches: [ "master" ]
+
+jobs:
+
+ # This job builds the documentation website
+ # and the commits that to the master branch,
+ # which will result in replacing the currently published site
+
+ build_and_commit:
+ name: Build & commit docs website
+ runs-on: ubuntu-slim
+
+ env:
+ DotnetVersion: 8.0.x
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Add .NET global tools location to PATH
+ run: echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
+ - name: Install .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ env.DotnetVersion }}
+ - name: Install DocFX
+ run: dotnet tool install --global docfx
+ - name: Remove old docs
+ run: rm -rf docs/*
+ - name: Build new docs
+ working-directory: CSF.Extensions.WebDriver.Docs
+ run: docfx docfx.json
+ - name: Add & Commit
+ uses: EndBug/add-and-commit@v9.1.4
+ with:
+ add: -A docs/
+ default_author: github_actor
+ committer_name: Github Actions Workflow (bot)
+ committer_email: github-actions-workflow@bots.noreply.github.com
+ fetch: true
+ message: Publish updated documentation website
+ push: origin master
+
+
diff --git a/.gitignore b/.gitignore
index 83e31d9..d59afba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,7 @@ bin/
obj/
*.userprefs
*.csproj.user
-TestResult.xml
+**/TestResults/**
*.log
*.orig
*.nupkg
diff --git a/.sonarqube-analysisproperties.xml b/.sonarqube-analysisproperties.xml
new file mode 100644
index 0000000..dc90498
--- /dev/null
+++ b/.sonarqube-analysisproperties.xml
@@ -0,0 +1,13 @@
+
+
+ CSF.Extensions.WebDriver.Tests\**\*,**\*Exception.cs
+ docs\**\*
+ CSF.Extensions.WebDriver.Tests\**\*
+ **\TestResults.xml
+ **\TestResults\*.opencover.xml
+ false
+ CSF.Extensions.WebDriver.Tests\**\*
+ true
+
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver.Tests/CSF.Extensions.WebDriver.Tests.csproj b/CSF.Extensions.WebDriver.Tests/CSF.Extensions.WebDriver.Tests.csproj
index 3231f5e..9f4868f 100644
--- a/CSF.Extensions.WebDriver.Tests/CSF.Extensions.WebDriver.Tests.csproj
+++ b/CSF.Extensions.WebDriver.Tests/CSF.Extensions.WebDriver.Tests.csproj
@@ -10,12 +10,15 @@
-
-
-
-
-
-
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
diff --git a/CSF.Extensions.WebDriver.Tests/ConfigurationFactory.cs b/CSF.Extensions.WebDriver.Tests/ConfigurationFactory.cs
new file mode 100644
index 0000000..ad49df0
--- /dev/null
+++ b/CSF.Extensions.WebDriver.Tests/ConfigurationFactory.cs
@@ -0,0 +1,25 @@
+using Microsoft.Extensions.Configuration;
+
+namespace CSF.Extensions.WebDriver;
+
+public static class ConfigurationFactory
+{
+ ///
+ /// Helper method to create an from a specified JSON string.
+ ///
+ /// A JSON string which will be used as the basis for the returned config.
+ /// A task exposing a configuration object, created from the JSON string.
+ public static async Task GetConfigurationAsync(string jsonConfig)
+ {
+ var builder = new ConfigurationBuilder();
+
+ var stream = new MemoryStream ();
+ using var writer = new StreamWriter(stream, leaveOpen: true);
+ await writer.WriteAsync(jsonConfig);
+ await writer.FlushAsync();
+ stream.Position = 0;
+
+ builder.AddJsonStream(stream);
+ return builder.Build();
+ }
+}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver.Tests/Factories/DriverTypeProviderTests.cs b/CSF.Extensions.WebDriver.Tests/Factories/DriverTypeProviderTests.cs
new file mode 100644
index 0000000..d0efa61
--- /dev/null
+++ b/CSF.Extensions.WebDriver.Tests/Factories/DriverTypeProviderTests.cs
@@ -0,0 +1,59 @@
+using Microsoft.Extensions.Configuration;
+using OpenQA.Selenium.Chrome;
+
+namespace CSF.Extensions.WebDriver.Factories;
+
+[TestFixture, Parallelizable]
+public class DriverTypeProviderTests
+{
+ [Test, AutoMoqData]
+ public void TryGetDriverTypeShouldReturnTrueForAKnownType([StandardTypes] DriverTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config)
+ {
+ options.DriverType = nameof(ChromeDriver);
+ Assert.That(sut.TryGetDriverType(options, config, out _), Is.True);
+ }
+
+ [Test, AutoMoqData]
+ public void TryGetDriverTypeShouldReturnFalseForAnUnknownType([StandardTypes] DriverTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config)
+ {
+ options.DriverType = "Elephant";
+ options.DriverFactoryType = null;
+ Assert.That(sut.TryGetDriverType(options, config, out _), Is.False);
+ }
+
+ [Test, AutoMoqData]
+ public void TryGetDriverTypeShouldReturnFalseIfDriverTypeIsNullAndDriverFactoryIsToo([StandardTypes] DriverTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config)
+ {
+ options.DriverType = null;
+ options.DriverFactoryType = null;
+ Assert.That(sut.TryGetDriverType(options, config, out _), Is.False);
+ }
+
+ [Test, AutoMoqData]
+ public void TryGetDriverTypeShouldReturnTrueForAnUnknownTypeIfDriverFactoryIsNotNull([StandardTypes] DriverTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config,
+ string factoryType)
+ {
+ options.DriverType = "Elephant";
+ options.DriverFactoryType = factoryType;
+ Assert.That(sut.TryGetDriverType(options, config, out _), Is.True);
+ }
+
+ [Test, AutoMoqData]
+ public void TryGetDriverTypeShouldReturnTrueIfDriverTypeIsNullIfDriverFactoryIsNotNull([StandardTypes] DriverTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config,
+ string factoryType)
+ {
+ options.DriverType = null;
+ options.DriverFactoryType = factoryType;
+ Assert.That(sut.TryGetDriverType(options, config, out _), Is.True);
+ }
+}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver.Tests/Factories/LogLevelDriverOptionsFactoryDecoratorTests.cs b/CSF.Extensions.WebDriver.Tests/Factories/LogLevelDriverOptionsFactoryDecoratorTests.cs
new file mode 100644
index 0000000..f61acd4
--- /dev/null
+++ b/CSF.Extensions.WebDriver.Tests/Factories/LogLevelDriverOptionsFactoryDecoratorTests.cs
@@ -0,0 +1,67 @@
+using Microsoft.Extensions.Configuration;
+using NUnit.Framework.Internal;
+using OpenQA.Selenium;
+
+namespace CSF.Extensions.WebDriver.Factories;
+
+[TestFixture, Parallelizable]
+public class LogLevelDriverOptionsFactoryDecoratorTests
+{
+ [Test, AutoMoqData]
+ public void CreateOptionsShouldSetLogLevelIfSpecifiedInConfig([Frozen] ICreatesDriverOptions wrapped,
+ LogLevelDriverOptionsFactoryDecorator sut,
+ Type optionsType,
+ IConfigurationSection config)
+ {
+ var options = new InspectableDriverOptions();
+ Mock.Get(wrapped).Setup(x => x.CreateOptions(optionsType, config)).Returns(options);
+ Mock.Get(config)
+ .Setup(x => x.GetSection(nameof(WebDriverCreationOptions.BrowserLogLevel)))
+ .Returns(Mock.Of(x => x.Value == LogLevel.Severe.ToString()));
+
+ sut.CreateOptions(optionsType, config);
+
+ Assert.That(options.GetLoggingPrefs()[LogType.Browser], Is.EqualTo("SEVERE"));
+ }
+
+ [Test, AutoMoqData]
+ public void CreateOptionsShouldNotSetLogLevelIfOmittedInConfig([Frozen] ICreatesDriverOptions wrapped,
+ LogLevelDriverOptionsFactoryDecorator sut,
+ Type optionsType,
+ IConfigurationSection config)
+ {
+ var options = new InspectableDriverOptions();
+ Mock.Get(wrapped).Setup(x => x.CreateOptions(optionsType, config)).Returns(options);
+ Mock.Get(config)
+ .Setup(x => x.GetSection(nameof(WebDriverCreationOptions.BrowserLogLevel)))
+ .Returns(Mock.Of(x => x.Value == null));
+
+ sut.CreateOptions(optionsType, config);
+
+ Assert.That(options.GetLoggingPrefs(), Is.Null);
+ }
+
+ [Test, AutoMoqData]
+ public void CreateOptionsShouldNotSetLogLevelIfConfigIsInvalid([Frozen] ICreatesDriverOptions wrapped,
+ LogLevelDriverOptionsFactoryDecorator sut,
+ Type optionsType,
+ IConfigurationSection config)
+ {
+ var options = new InspectableDriverOptions();
+ Mock.Get(wrapped).Setup(x => x.CreateOptions(optionsType, config)).Returns(options);
+ Mock.Get(config)
+ .Setup(x => x.GetSection(nameof(WebDriverCreationOptions.BrowserLogLevel)))
+ .Returns(Mock.Of(x => x.Value == "invalid level"));
+
+ sut.CreateOptions(optionsType, config);
+
+ Assert.That(options.GetLoggingPrefs(), Is.Null);
+ }
+
+ class InspectableDriverOptions : DriverOptions
+ {
+ public Dictionary GetLoggingPrefs() => GenerateLoggingPreferencesDictionary();
+
+ public override ICapabilities ToCapabilities() => throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver.Tests/Factories/OptionsTypeProviderTests.cs b/CSF.Extensions.WebDriver.Tests/Factories/OptionsTypeProviderTests.cs
new file mode 100644
index 0000000..ab6ec85
--- /dev/null
+++ b/CSF.Extensions.WebDriver.Tests/Factories/OptionsTypeProviderTests.cs
@@ -0,0 +1,70 @@
+using Microsoft.Extensions.Configuration;
+using OpenQA.Selenium.Chrome;
+using OpenQA.Selenium.Remote;
+
+namespace CSF.Extensions.WebDriver.Factories;
+
+[TestFixture, Parallelizable]
+public class OptionsTypeProviderTests
+{
+ [Test, AutoMoqData]
+ public void TryGetOptionsTypeShouldReturnTrueForAKnownType([StandardTypes] OptionsTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config)
+ {
+ options.OptionsType = nameof(ChromeOptions);
+ Assert.That(sut.TryGetOptionsType(options, config, typeof(ChromeDriver), out _), Is.True);
+ }
+
+ [Test, AutoMoqData]
+ public void TryGetOptionsTypeShouldReturnFalseForAnUnknownType([StandardTypes] OptionsTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config)
+ {
+ options.OptionsType = "Elephant";
+ options.DriverFactoryType = null;
+ Assert.That(sut.TryGetOptionsType(options, config, typeof(ChromeDriver), out _), Is.False);
+ }
+
+ [Test, AutoMoqData]
+ public void TryGetOptionsTypeShouldReturnTrueIfOptionsTypeIsNullButDriverIsAKnownType([StandardTypes] OptionsTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config)
+ {
+ options.OptionsType = null;
+ options.DriverFactoryType = null;
+ Assert.That(sut.TryGetOptionsType(options, config, typeof(ChromeDriver), out _), Is.True);
+ }
+
+ [Test, AutoMoqData]
+ public void TryGetOptionsTypeShouldReturnFalseIfOptionsTypeIsNullButDriverIsAnUnknownType([StandardTypes] OptionsTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config)
+ {
+ options.OptionsType = null;
+ options.DriverFactoryType = null;
+ Assert.That(sut.TryGetOptionsType(options, config, typeof(RemoteWebDriver), out _), Is.False);
+ }
+
+ [Test, AutoMoqData]
+ public void TryGetOptionsTypeShouldReturnTrueForAnUnknownTypeIfDriverFactoryTypeIsNotNull([StandardTypes] OptionsTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config,
+ string factoryType)
+ {
+ options.OptionsType = "Elephant";
+ options.DriverFactoryType = factoryType;
+ Assert.That(sut.TryGetOptionsType(options, config, typeof(ChromeDriver), out _), Is.True);
+ }
+
+ [Test, AutoMoqData]
+ public void TryGetOptionsTypeShouldReturnTrueIfOptionsTypeIsNullIfDriverFactoryIsNotNull([StandardTypes] OptionsTypeProvider sut,
+ WebDriverCreationOptions options,
+ IConfigurationSection config,
+ string factoryType)
+ {
+ options.OptionsType = null;
+ options.DriverFactoryType = factoryType;
+ Assert.That(sut.TryGetOptionsType(options, config, typeof(RemoteWebDriver), out _), Is.True);
+ }
+}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver.Tests/Factories/StandardTypesAttribute.cs b/CSF.Extensions.WebDriver.Tests/Factories/StandardTypesAttribute.cs
index 0da317f..a0d57c0 100644
--- a/CSF.Extensions.WebDriver.Tests/Factories/StandardTypesAttribute.cs
+++ b/CSF.Extensions.WebDriver.Tests/Factories/StandardTypesAttribute.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using AutoFixture;
+using Microsoft.Extensions.Configuration;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Remote;
@@ -25,19 +26,95 @@ public override ICustomization GetCustomization(ParameterInfo parameter)
public class StandardTypesCustomization : ICustomization
{
+ static readonly Type
+ chromeDriverType = typeof(ChromeDriver),
+ firefoxDriverType = typeof(FirefoxDriver),
+ remoteDriverType = typeof(RemoteWebDriver),
+ safariDriverType = typeof(SafariDriver),
+ chromeOptionsType = typeof(ChromeOptions),
+ firefoxOptionsType = typeof(FirefoxOptions),
+ safariOptionsType = typeof(SafariOptions);
+
public void Customize(IFixture fixture)
+ {
+ CustomizeDriverAndOptionsTypeProvider(fixture);
+ CustomizeOptionsTypeProvider(fixture);
+ CustomizeDriverType(fixture);
+ }
+
+ static void CustomizeDriverAndOptionsTypeProvider(IFixture fixture)
{
fixture.Customize(c => c.FromFactory(() => {
var mock = new Mock(MockBehavior.Strict);
- mock.Setup(x => x.GetWebDriverType(nameof(ChromeDriver))).Returns(typeof(ChromeDriver));
- mock.Setup(x => x.GetWebDriverType(nameof(FirefoxDriver))).Returns(typeof(FirefoxDriver));
- mock.Setup(x => x.GetWebDriverType(nameof(RemoteWebDriver))).Returns(typeof(RemoteWebDriver));
- mock.Setup(x => x.GetWebDriverType(nameof(SafariDriver))).Returns(typeof(SafariDriver));
- mock.Setup(x => x.GetWebDriverOptionsType(typeof(ChromeDriver), null)).Returns(typeof(ChromeOptions));
- mock.Setup(x => x.GetWebDriverOptionsType(typeof(FirefoxDriver), null)).Returns(typeof(FirefoxOptions));
- mock.Setup(x => x.GetWebDriverOptionsType(typeof(SafariDriver), null)).Returns(typeof(SafariOptions));
+ mock.Setup(x => x.GetWebDriverType(nameof(ChromeDriver))).Returns(chromeDriverType);
+ mock.Setup(x => x.GetWebDriverType(nameof(FirefoxDriver))).Returns(firefoxDriverType);
+ mock.Setup(x => x.GetWebDriverType(nameof(RemoteWebDriver))).Returns(remoteDriverType);
+ mock.Setup(x => x.GetWebDriverType(nameof(SafariDriver))).Returns(safariDriverType);
+ mock.Setup(x => x.GetWebDriverOptionsType(chromeDriverType, null)).Returns(chromeOptionsType);
+ mock.Setup(x => x.GetWebDriverOptionsType(firefoxDriverType, null)).Returns(firefoxOptionsType);
+ mock.Setup(x => x.GetWebDriverOptionsType(safariDriverType, null)).Returns(safariOptionsType);
return mock.Object;
}));
+
fixture.Freeze();
}
+
+ static void CustomizeOptionsTypeProvider(IFixture fixture)
+ {
+ fixture.Customize(c => c.FromFactory(() =>
+ {
+ var mock = new Mock(MockBehavior.Strict);
+ Type nullType = null!;
+ mock
+ .Setup(x => x.TryGetOptionsType(It.IsAny(), It.IsAny(), It.IsAny(), out nullType))
+ .Returns(false);
+ var chromeType = chromeOptionsType;
+ mock
+ .Setup(x => x.TryGetOptionsType(It.IsAny(), It.IsAny(), chromeDriverType, out chromeType))
+ .Returns(true);
+ var firefoxType = firefoxOptionsType;
+ mock
+ .Setup(x => x.TryGetOptionsType(It.IsAny(), It.IsAny(), firefoxDriverType, out firefoxType))
+ .Returns(true);
+ var safariType = safariOptionsType;
+ mock
+ .Setup(x => x.TryGetOptionsType(It.IsAny(), It.IsAny(), safariDriverType, out safariType))
+ .Returns(true);
+ return mock.Object;
+ }));
+
+ fixture.Freeze();
+ }
+
+ static void CustomizeDriverType(IFixture fixture)
+ {
+ fixture.Customize(c => c.FromFactory(() =>
+ {
+ var mock = new Mock(MockBehavior.Strict);
+ Type nullType = null!;
+ mock
+ .Setup(x => x.TryGetDriverType(It.IsAny(), It.IsAny(), out nullType))
+ .Returns(false);
+ var chromeType = chromeDriverType;
+ mock
+ .Setup(x => x.TryGetDriverType(It.Is(o => o.DriverType == nameof(ChromeDriver)), It.IsAny(), out chromeType))
+ .Returns(true);
+ var firefoxType = firefoxDriverType;
+ mock
+ .Setup(x => x.TryGetDriverType(It.Is(o => o.DriverType == nameof(FirefoxDriver)), It.IsAny(), out firefoxType))
+ .Returns(true);
+ var remoteType = remoteDriverType;
+ mock
+ .Setup(x => x.TryGetDriverType(It.Is(o => o.DriverType == nameof(RemoteWebDriver)), It.IsAny(), out remoteType))
+ .Returns(true);
+ var safariType = safariDriverType;
+ mock
+ .Setup(x => x.TryGetDriverType(It.Is(o => o.DriverType == nameof(SafariDriver)), It.IsAny(), out safariType))
+ .Returns(true);
+
+ return mock.Object;
+ }));
+
+ fixture.Freeze();
+ }
}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver.Tests/Factories/WebDriverCreationConfigureOptionsTests.cs b/CSF.Extensions.WebDriver.Tests/Factories/WebDriverCreationConfigureOptionsTests.cs
index 59a190b..a8a1115 100644
--- a/CSF.Extensions.WebDriver.Tests/Factories/WebDriverCreationConfigureOptionsTests.cs
+++ b/CSF.Extensions.WebDriver.Tests/Factories/WebDriverCreationConfigureOptionsTests.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Remote;
@@ -11,9 +12,10 @@ namespace CSF.Extensions.WebDriver.Factories;
public class WebDriverCreationConfigureOptionsTests
{
[Test,AutoMoqData]
- public async Task ConfigureShouldBeAbleToSetupLocalChromeDriverWithSimpleOptionsFromJsonConfiguration([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider)
+ public async Task ConfigureShouldBeAbleToSetupLocalChromeDriverWithSimpleOptionsFromJsonConfiguration([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider)
{
- var options = await GetOptionsAsync(typeProvider,
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""Test"": {
@@ -47,9 +49,10 @@ public async Task ConfigureShouldBeAbleToSetupLocalChromeDriverWithSimpleOptions
[Test,AutoMoqData]
- public async Task ConfigureShouldBeAbleToSetupTwoLocalDriversWithSimpleOptionsFromJsonConfiguration([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider)
+ public async Task ConfigureShouldBeAbleToSetupTwoLocalDriversWithSimpleOptionsFromJsonConfiguration([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider)
{
- var options = await GetOptionsAsync(typeProvider,
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""SampleChrome"": {
@@ -87,9 +90,10 @@ public async Task ConfigureShouldBeAbleToSetupTwoLocalDriversWithSimpleOptionsFr
}
[Test,AutoMoqData]
- public async Task ConfigureShouldBeAbleToSetupLocalChromeDriverWithNoOptionsFromJsonConfiguration([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider)
+ public async Task ConfigureShouldBeAbleToSetupLocalChromeDriverWithNoOptionsFromJsonConfiguration([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider)
{
- var options = await GetOptionsAsync(typeProvider,
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""Test"": { ""DriverType"": ""ChromeDriver"" }
@@ -110,9 +114,10 @@ public async Task ConfigureShouldBeAbleToSetupLocalChromeDriverWithNoOptionsFrom
}
[Test,AutoMoqData]
- public async Task ConfigureShouldBeAbleToGetSelectedConfigWhenThereIsOnlyOnePresent([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider)
+ public async Task ConfigureShouldBeAbleToGetSelectedConfigWhenThereIsOnlyOnePresent([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider)
{
- var options = await GetOptionsAsync(typeProvider,
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""Test"": { ""DriverType"": ""ChromeDriver"" }
@@ -123,9 +128,10 @@ public async Task ConfigureShouldBeAbleToGetSelectedConfigWhenThereIsOnlyOnePres
}
[Test,AutoMoqData]
- public async Task ConfigureShouldBeAbleToAddACustomizerToSomeOptions([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider)
+ public async Task ConfigureShouldBeAbleToAddACustomizerToSomeOptions([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider)
{
- var options = await GetOptionsAsync(typeProvider,
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""Test"": { ""DriverType"": ""ChromeDriver"", ""OptionsCustomizerType"": ""CSF.Extensions.WebDriver.Factories.SampleCustomizer, CSF.Extensions.WebDriver.Tests"" }
@@ -136,9 +142,10 @@ public async Task ConfigureShouldBeAbleToAddACustomizerToSomeOptions([StandardTy
}
[Test,AutoMoqData]
- public async Task ConfigureShouldBeAbleToGetSelectedConfigWhenASelectedConfigIsNamed([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider)
+ public async Task ConfigureShouldBeAbleToGetSelectedConfigWhenASelectedConfigIsNamed([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider)
{
- var options = await GetOptionsAsync(typeProvider,
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""Test"": { ""DriverType"": ""ChromeDriver"" },
@@ -151,9 +158,10 @@ public async Task ConfigureShouldBeAbleToGetSelectedConfigWhenASelectedConfigIsN
}
[Test,AutoMoqData]
- public async Task ConfigureShouldProvideThrowWhenThereAreTwoConfigsAndNoExplicitSelection([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider)
+ public async Task ConfigureShouldProvideThrowWhenThereAreTwoConfigsAndNoExplicitSelection([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider)
{
- var options = await GetOptionsAsync(typeProvider,
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""Test"": { ""DriverType"": ""ChromeDriver"" },
@@ -165,9 +173,10 @@ public async Task ConfigureShouldProvideThrowWhenThereAreTwoConfigsAndNoExplicit
}
[Test,AutoMoqData]
- public async Task ConfigureShouldNotThrowForANonsenseDriverType([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider)
+ public async Task ConfigureShouldNotThrowForANonsenseDriverType([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider)
{
- var options = await GetOptionsAsync(typeProvider,
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""Test"": { ""DriverType"": ""NonexistentDriver"" }
@@ -178,9 +187,10 @@ public async Task ConfigureShouldNotThrowForANonsenseDriverType([StandardTypes]
}
[Test,AutoMoqData]
- public async Task ConfigureShouldNotThrowForARemoteDriverWithoutOptionsType([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider)
+ public async Task ConfigureShouldNotThrowForARemoteDriverWithoutOptionsType([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider)
{
- var options = await GetOptionsAsync(typeProvider,
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""Test"": { ""DriverType"": ""RemoteWebDriver"" }
@@ -191,14 +201,15 @@ public async Task ConfigureShouldNotThrowForARemoteDriverWithoutOptionsType([Sta
}
[Test,AutoMoqData]
- public async Task ConfigureShouldBeAbleToSetupRemoteDriverWithSimpleOptionsFromJsonConfiguration([StandardTypes] IGetsWebDriverAndOptionsTypes typeProvider,
+ public async Task ConfigureShouldBeAbleToSetupRemoteDriverWithSimpleOptionsFromJsonConfiguration([StandardTypes] IGetsDriverType driverTypeProvider,
+ [StandardTypes] IGetsOptionsType optionsTypeProvider,
[TestLogger] ILogger logger)
{
- Mock.Get(typeProvider)
- .Setup(x => x.GetWebDriverOptionsType(typeof(RemoteWebDriver), "SafariOptions"))
- .Returns(typeof(SafariOptions));
-
- var options = await GetOptionsAsync(typeProvider,
+ var type = typeof(SafariOptions);
+ Mock.Get(optionsTypeProvider)
+ .Setup(x => x.TryGetOptionsType(It.Is(o => o.OptionsType == nameof(SafariOptions)), It.IsAny(), typeof(RemoteWebDriver), out type))
+ .Returns(true);
+ var options = await GetOptionsAsync(driverTypeProvider, optionsTypeProvider,
@"{
""DriverConfigurations"": {
""Test"": { ""DriverType"": ""RemoteWebDriver"", ""OptionsType"": ""SafariOptions"" }
@@ -208,24 +219,6 @@ public async Task ConfigureShouldBeAbleToSetupRemoteDriverWithSimpleOptionsFromJ
Assert.That(options.DriverConfigurations, Is.Not.Empty);
}
- ///
- /// Helper method to create an from a specified JSON string.
- ///
- /// A JSON string which will be used as the basis for the returned config.
- /// A task exposing a configuration object, created from the JSON string.
- static async Task GetConfigurationAsync(string jsonConfig)
- {
- var builder = new ConfigurationBuilder();
-
- var stream = new MemoryStream ();
- using var writer = new StreamWriter(stream, leaveOpen: true);
- await writer.WriteAsync(jsonConfig);
- await writer.FlushAsync();
- stream.Position = 0;
-
- builder.AddJsonStream(stream);
- return builder.Build();
- }
///
/// Creates and exercises in order to create a new
@@ -234,15 +227,20 @@ static async Task GetConfigurationAsync(string jsonConfig)
/// The type provider for web driver and options types
/// The JSON config from which to create the options
/// A task exposing the webdriver creation options collection, configured by the SUT
- static async Task GetOptionsAsync(IGetsWebDriverAndOptionsTypes typeProvider,
+ static async Task GetOptionsAsync(IGetsDriverType driverTypeProvider,
+ IGetsOptionsType optionsTypeProvider,
string json,
ILogger? logger = null)
{
var options = new WebDriverCreationOptionsCollection();
- var config = await GetConfigurationAsync(json);
- var sut = new WebDriverCreationConfigureOptions(new WebDriverConfigurationItemParser(typeProvider, Mock.Of>()),
+ var config = await ConfigurationFactory.GetConfigurationAsync(json);
+ ICreatesDriverOptions optionsFactory = new ActivatorDriverOptionsFactory();
+ optionsFactory = new ConfigBindingDriverOptionsFactoryDecorator(optionsFactory);
+ var parser = new WebDriverConfigurationItemParser(driverTypeProvider, optionsTypeProvider, optionsFactory, Mock.Of>());
+ var sut = new WebDriverCreationConfigureOptions(parser,
config,
- logger ?? Mock.Of>());
+ logger ?? Mock.Of>(),
+ c => {});
sut.Configure(options);
return options;
}
diff --git a/CSF.Extensions.WebDriver.Tests/Proxies/WebDriverProxyFactoryIntegrationTests.cs b/CSF.Extensions.WebDriver.Tests/Proxies/WebDriverProxyFactoryIntegrationTests.cs
index 4ac9f4e..56770b6 100644
--- a/CSF.Extensions.WebDriver.Tests/Proxies/WebDriverProxyFactoryIntegrationTests.cs
+++ b/CSF.Extensions.WebDriver.Tests/Proxies/WebDriverProxyFactoryIntegrationTests.cs
@@ -9,7 +9,7 @@ namespace CSF.Extensions.WebDriver.Proxies;
[TestFixture, Parallelizable, Description("Integration tests for IGetsProxyWebDriver and all of its dependencies. These tests use DI.")]
public class WebDriverProxyFactoryIntegrationTests
{
- readonly IServiceProvider services;
+ IServiceProvider services;
[Test,AutoMoqData]
public void GetProxyWebDriverShouldGetAWebDriverWhichCanBeUnproxied(IWebDriver webDriver)
@@ -76,8 +76,8 @@ public void GetProxyWebDriverShouldReturnAnObjectWhichHasQuirksFromStaticDataWhe
});
}
-
- public WebDriverProxyFactoryIntegrationTests()
+ [OneTimeSetUp]
+ public void Setup()
{
var serviceCollection = new ServiceCollection();
serviceCollection
@@ -98,4 +98,11 @@ public WebDriverProxyFactoryIntegrationTests()
}});
services = serviceCollection.BuildServiceProvider();
}
+
+ [OneTimeTearDown]
+ public void Teardown()
+ {
+ if(services is IDisposable disp)
+ disp.Dispose();
+ }
}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver.Tests/TestLoggerAttribute.cs b/CSF.Extensions.WebDriver.Tests/TestLoggerAttribute.cs
index b57659b..2a9eb16 100644
--- a/CSF.Extensions.WebDriver.Tests/TestLoggerAttribute.cs
+++ b/CSF.Extensions.WebDriver.Tests/TestLoggerAttribute.cs
@@ -58,6 +58,6 @@ public class TestContextLogger : ILogger
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
{
- TestContext.WriteLine($"{logLevel}: {formatter(state, exception)}{((exception is not null) ? ("\n" + exception.ToString()) : string.Empty)}");
+ TestContext.Out.WriteLine($"{logLevel}: {formatter(state, exception)}{((exception is not null) ? ("\n" + exception.ToString()) : string.Empty)}");
}
}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver/Factories/ActivatorDriverOptionsFactory.cs b/CSF.Extensions.WebDriver/Factories/ActivatorDriverOptionsFactory.cs
new file mode 100644
index 0000000..dd50de6
--- /dev/null
+++ b/CSF.Extensions.WebDriver/Factories/ActivatorDriverOptionsFactory.cs
@@ -0,0 +1,16 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using OpenQA.Selenium;
+
+namespace CSF.Extensions.WebDriver.Factories
+{
+ ///
+ /// Implementation of which uses to create the options object.
+ ///
+ public class ActivatorDriverOptionsFactory : ICreatesDriverOptions
+ {
+ ///
+ public DriverOptions CreateOptions(Type optionsType, IConfigurationSection config)
+ => (DriverOptions) Activator.CreateInstance(optionsType);
+ }
+}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver/Factories/ConfigBindingDriverOptionsFactoryDecorator.cs b/CSF.Extensions.WebDriver/Factories/ConfigBindingDriverOptionsFactoryDecorator.cs
new file mode 100644
index 0000000..d5e8d01
--- /dev/null
+++ b/CSF.Extensions.WebDriver/Factories/ConfigBindingDriverOptionsFactoryDecorator.cs
@@ -0,0 +1,32 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using OpenQA.Selenium;
+
+namespace CSF.Extensions.WebDriver.Factories
+{
+ ///
+ /// Decorator for which binds the configuration to the created options.
+ ///
+ public class ConfigBindingDriverOptionsFactoryDecorator : ICreatesDriverOptions
+ {
+ readonly ICreatesDriverOptions wrapped;
+
+ ///
+ public DriverOptions CreateOptions(Type optionsType, IConfigurationSection config)
+ {
+ var options = wrapped.CreateOptions(optionsType, config);
+ config.Bind("Options", options);
+ return options;
+ }
+
+ ///
+ /// Initialises a new instance of .
+ ///
+ /// The wrapped service
+ /// If is .
+ public ConfigBindingDriverOptionsFactoryDecorator(ICreatesDriverOptions wrapped)
+ {
+ this.wrapped = wrapped ?? throw new ArgumentNullException(nameof(wrapped));
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver/Factories/DriverTypeProvider.cs b/CSF.Extensions.WebDriver/Factories/DriverTypeProvider.cs
new file mode 100644
index 0000000..6b72e01
--- /dev/null
+++ b/CSF.Extensions.WebDriver/Factories/DriverTypeProvider.cs
@@ -0,0 +1,63 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using OpenQA.Selenium;
+
+namespace CSF.Extensions.WebDriver.Factories
+{
+ ///
+ /// Default implementation of .
+ ///
+ public class DriverTypeProvider : IGetsDriverType
+ {
+ readonly ILogger logger;
+ readonly IGetsWebDriverAndOptionsTypes typeProvider;
+
+ ///
+ public bool TryGetDriverType(WebDriverCreationOptions options, IConfigurationSection configuration, out Type driverType)
+ {
+ driverType = null;
+
+ if(options.DriverType is null)
+ {
+ if(options.DriverFactoryType == null)
+ logger.LogError("{ParamName} is mandatory unless {FactoryTypeKey} is specified; the configuration '{ConfigKey}' will be omitted.",
+ nameof(WebDriverCreationOptions.DriverType),
+ nameof(WebDriverCreationOptions.DriverFactoryType),
+ configuration.Key);
+ return options.DriverFactoryType != null;
+ }
+
+ try
+ {
+ driverType = typeProvider.GetWebDriverType(options.DriverType);
+ return true;
+ }
+ catch(Exception e)
+ {
+ if(options.DriverFactoryType == null)
+ logger.LogError(e,
+ "No implementation of {WebDriverIface} could be found for the {DriverTypeProp} '{DriverType}'; the driver configuration '{ConfigKey}' will be omitted. " +
+ "Reminder: If the driver type is not one which is shipped with Selenium then you must specify its assembly-qualified type name.",
+ nameof(IWebDriver),
+ nameof(WebDriverCreationOptions.DriverType),
+ options.DriverType,
+ configuration.Key);
+ return options.DriverFactoryType != null;
+ }
+ }
+
+ ///
+ /// Initialises a new instance of .
+ ///
+ /// A logger
+ /// A provider for the concrete types of web driver and options
+ /// If any parameter is
+ public DriverTypeProvider(ILogger logger, IGetsWebDriverAndOptionsTypes typeProvider)
+ {
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ this.typeProvider = typeProvider ?? throw new ArgumentNullException(nameof(typeProvider));
+ }
+ }
+}
+
diff --git a/CSF.Extensions.WebDriver/Factories/ICreatesDriverOptions.cs b/CSF.Extensions.WebDriver/Factories/ICreatesDriverOptions.cs
new file mode 100644
index 0000000..341c0f8
--- /dev/null
+++ b/CSF.Extensions.WebDriver/Factories/ICreatesDriverOptions.cs
@@ -0,0 +1,20 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using OpenQA.Selenium;
+
+namespace CSF.Extensions.WebDriver.Factories
+{
+ ///
+ /// An object which creates and returns a new object which derives from .
+ ///
+ public interface ICreatesDriverOptions
+ {
+ ///
+ /// Creates and returns a new driver options instance.
+ ///
+ /// The desired type of the options object
+ /// The CSF.Extensions.WebDriver configuration
+ /// A new driver options instance
+ DriverOptions CreateOptions(Type optionsType, IConfigurationSection config);
+ }
+}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver/Factories/IGetsDriverType.cs b/CSF.Extensions.WebDriver/Factories/IGetsDriverType.cs
new file mode 100644
index 0000000..f3ec5a0
--- /dev/null
+++ b/CSF.Extensions.WebDriver/Factories/IGetsDriverType.cs
@@ -0,0 +1,29 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using OpenQA.Selenium;
+
+namespace CSF.Extensions.WebDriver.Factories
+{
+ ///
+ /// An object which can get the concrete type of a WebDriver, indicated by the configuration.
+ ///
+ public interface IGetsDriverType
+ {
+ ///
+ /// Validates and gets the of the implementation of implementation indicated by the configuration.
+ ///
+ ///
+ ///
+ /// Note that it is valid for the driver type to be if is specified.
+ /// In that scenario, the driver type is unused, but it still indicates a valid configuration.
+ ///
+ ///
+ /// The options, as they have been parsed so far
+ /// The configuration section
+ /// If this method returns then this is a of the web driver, otherwise
+ /// this value is undefined and must be ignored.
+ /// if the driver type information is valid; if not
+ bool TryGetDriverType(WebDriverCreationOptions options, IConfigurationSection configuration, out Type driverType);
+ }
+}
+
diff --git a/CSF.Extensions.WebDriver/Factories/IGetsOptionsType.cs b/CSF.Extensions.WebDriver/Factories/IGetsOptionsType.cs
new file mode 100644
index 0000000..cc89fb1
--- /dev/null
+++ b/CSF.Extensions.WebDriver/Factories/IGetsOptionsType.cs
@@ -0,0 +1,31 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using OpenQA.Selenium;
+
+namespace CSF.Extensions.WebDriver.Factories
+{
+ ///
+ /// An object which can get the concrete type of some WebDriver Options, indicated by the configuration.
+ ///
+ public interface IGetsOptionsType
+ {
+ ///
+ /// Validates and gets the of the implementation of implementation indicated by the configuration.
+ ///
+ ///
+ ///
+ /// Note that it is valid for the options type to be if is specified.
+ /// In that scenario, the options type is unused, but it still indicates a valid configuration.
+ ///
+ ///
+ /// The options, as they have been parsed so far
+ /// The configuration section
+ /// The type of the Web Driver, as has already been determined by
+ /// .
+ /// If this method returns then this is a of the driver options, otherwise
+ /// this value is undefined and must be ignored.
+ /// if the driver type information is valid; if not
+ bool TryGetOptionsType(WebDriverCreationOptions options, IConfigurationSection configuration, Type driverType, out Type optionsType);
+ }
+}
+
diff --git a/CSF.Extensions.WebDriver/Factories/LogLevelDriverOptionsFactoryDecorator.cs b/CSF.Extensions.WebDriver/Factories/LogLevelDriverOptionsFactoryDecorator.cs
new file mode 100644
index 0000000..55bb5ff
--- /dev/null
+++ b/CSF.Extensions.WebDriver/Factories/LogLevelDriverOptionsFactoryDecorator.cs
@@ -0,0 +1,35 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using OpenQA.Selenium;
+
+namespace CSF.Extensions.WebDriver.Factories
+{
+ ///
+ /// Decorator for which conditionally sets the logging preference for the
+ /// options, based upon .
+ ///
+ public class LogLevelDriverOptionsFactoryDecorator : ICreatesDriverOptions
+ {
+ readonly ICreatesDriverOptions wrapped;
+
+ ///
+ public DriverOptions CreateOptions(Type optionsType, IConfigurationSection config)
+ {
+ var options = wrapped.CreateOptions(optionsType, config);
+ var logLevel = config.GetValue(nameof(WebDriverCreationOptions.BrowserLogLevel));
+ if(logLevel != null && Enum.TryParse(logLevel, out var parsedLevel))
+ options.SetLoggingPreference(LogType.Browser, parsedLevel);
+ return options;
+ }
+
+ ///
+ /// Initialises a new instance of .
+ ///
+ /// The wrapped service
+ /// If is .
+ public LogLevelDriverOptionsFactoryDecorator(ICreatesDriverOptions wrapped)
+ {
+ this.wrapped = wrapped ?? throw new ArgumentNullException(nameof(wrapped));
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver/Factories/OptionsTypeProvider.cs b/CSF.Extensions.WebDriver/Factories/OptionsTypeProvider.cs
new file mode 100644
index 0000000..afb2127
--- /dev/null
+++ b/CSF.Extensions.WebDriver/Factories/OptionsTypeProvider.cs
@@ -0,0 +1,77 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using OpenQA.Selenium;
+
+namespace CSF.Extensions.WebDriver.Factories
+{
+ ///
+ /// Default implementation of .
+ ///
+ public class OptionsTypeProvider : IGetsOptionsType
+ {
+ readonly ILogger logger;
+ readonly IGetsWebDriverAndOptionsTypes typeProvider;
+ readonly ICreatesDriverOptions optionsFactory;
+
+ ///
+ public bool TryGetOptionsType(WebDriverCreationOptions options, IConfigurationSection configuration, Type driverType, out Type optionsType)
+ {
+ optionsType = null;
+
+ try
+ {
+ optionsType = typeProvider.GetWebDriverOptionsType(driverType, options.OptionsType);
+ }
+ catch(Exception e)
+ {
+ if(options.DriverFactoryType == null)
+ logger.LogError(e,
+ "No type deriving from {OptionsBase} could be found for the combination of {WebDriverIface} {DriverType} and {OptionsTypeProp} '{OptionsType}'; the configuration '{ConfigKey}' will be omitted. " +
+ "See the exception details for more information.",
+ nameof(DriverOptions),
+ nameof(IWebDriver),
+ driverType?.Name,
+ nameof(WebDriverCreationOptions.OptionsType),
+ options.OptionsType,
+ configuration.Key);
+
+ return options.DriverFactoryType != null;
+ }
+
+ try
+ {
+ options.OptionsFactory = GetOptions(optionsType, configuration);
+ return true;
+ }
+ catch(Exception e)
+ {
+ if(options.DriverFactoryType == null)
+ logger.LogError(e,
+ "An unexpected error occurred creating or binding to the {OptionsClass} type {OptionsType}; the configuration '{ConfigKey}' will be omitted.",
+ nameof(DriverOptions),
+ optionsType.FullName,
+ configuration.Key);
+ return options.DriverFactoryType != null;
+ }
+ }
+
+ Func GetOptions(Type optionsType, IConfigurationSection config)
+ => () => optionsFactory.CreateOptions(optionsType, config);
+
+ ///
+ /// Initialises a new instance of .
+ ///
+ /// A logger
+ /// A provider for the concrete types of web driver and options
+ /// A factory for instances of
+ /// If any parameter is
+ public OptionsTypeProvider(ILogger logger, IGetsWebDriverAndOptionsTypes typeProvider, ICreatesDriverOptions optionsFactory)
+ {
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ this.typeProvider = typeProvider ?? throw new ArgumentNullException(nameof(typeProvider));
+ this.optionsFactory = optionsFactory ?? throw new ArgumentNullException(nameof(optionsFactory));
+ }
+ }
+}
+
diff --git a/CSF.Extensions.WebDriver/Factories/WebDriverConfigurationItemParser.cs b/CSF.Extensions.WebDriver/Factories/WebDriverConfigurationItemParser.cs
index 7b657ab..e07bedf 100644
--- a/CSF.Extensions.WebDriver/Factories/WebDriverConfigurationItemParser.cs
+++ b/CSF.Extensions.WebDriver/Factories/WebDriverConfigurationItemParser.cs
@@ -10,7 +10,9 @@ namespace CSF.Extensions.WebDriver.Factories
///
public class WebDriverConfigurationItemParser : IParsesSingleWebDriverConfigurationSection
{
- readonly IGetsWebDriverAndOptionsTypes typeProvider;
+ readonly IGetsDriverType driverTypeProvider;
+ readonly IGetsOptionsType optionsTypeProvider;
+ readonly ICreatesDriverOptions optionsFactory;
readonly ILogger logger;
///
@@ -32,122 +34,20 @@ public WebDriverCreationOptions GetDriverConfiguration(IConfigurationSection con
if(configuration.GetSection(nameof(WebDriverCreationOptions.AddBrowserQuirks)).Exists())
creationOptions.AddBrowserQuirks = configuration.GetValue(nameof(WebDriverCreationOptions.AddBrowserQuirks));
- if(!TryGetDriverType(creationOptions, configuration, out var driverType))
+ if(!driverTypeProvider.TryGetDriverType(creationOptions, configuration, out var driverType))
return null;
- if(!TryGetOptionsType(creationOptions, configuration, driverType, out var optionsType))
+ if(!optionsTypeProvider.TryGetOptionsType(creationOptions, configuration, driverType, out var optionsType))
return null;
+ creationOptions.OptionsFactory = () => optionsFactory.CreateOptions(optionsType, configuration);
+
if(!TrySetOptionsCustomizer(creationOptions, configuration, optionsType))
return null;
return creationOptions;
}
- ///
- /// Validates and gets the of the implementation of implementation indicated by the configuration.
- ///
- ///
- ///
- /// Note that it is valid for the driver type to be if is specified.
- /// In that scenario, the driver type is unused, but it still indicates a valid configuration.
- ///
- ///
- /// The options, as they have been parsed so far
- /// The configuration section
- /// If this method returns then this is a of the web driver, otherwise
- /// this value is undefined and must be ignored.
- /// if the driver type information is valid; if not
- bool TryGetDriverType(WebDriverCreationOptions options, IConfigurationSection configuration, out Type driverType)
- {
- driverType = null;
-
- if(options.DriverType is null)
- {
- if(options.DriverFactoryType == null)
- logger.LogError("{ParamName} is mandatory unless {FactoryTypeKey} is specified; the configuration '{ConfigKey}' will be omitted.",
- nameof(WebDriverCreationOptions.DriverType),
- nameof(WebDriverCreationOptions.DriverFactoryType),
- configuration.Key);
- return options.DriverFactoryType != null ? true : false;
- }
-
- try
- {
- driverType = typeProvider.GetWebDriverType(options.DriverType);
- return true;
- }
- catch(Exception e)
- {
- if(options.DriverFactoryType == null)
- logger.LogError(e,
- "No implementation of {WebDriverIface} could be found for the {DriverTypeProp} '{DriverType}'; the driver configuration '{ConfigKey}' will be omitted. " +
- "Reminder: If the driver type is not one which is shipped with Selenium then you must specify its assembly-qualified type name.",
- nameof(IWebDriver),
- nameof(WebDriverCreationOptions.DriverType),
- options.DriverType,
- configuration.Key);
- return options.DriverFactoryType != null ? true : false;
- }
- }
-
- ///
- /// Validates and gets the of the implementation of implementation indicated by the configuration.
- ///
- ///
- ///
- /// Note that it is valid for the options type to be if is specified.
- /// In that scenario, the options type is unused, but it still indicates a valid configuration.
- ///
- ///
- /// The options, as they have been parsed so far
- /// The configuration section
- /// The type of the Web Driver, as has already been determined by
- /// .
- /// If this method returns then this is a of the driver options, otherwise
- /// this value is undefined and must be ignored.
- /// if the driver type information is valid; if not
- bool TryGetOptionsType(WebDriverCreationOptions options, IConfigurationSection configuration, Type driverType, out Type optionsType)
- {
- optionsType = null;
-
- try
- {
- optionsType = typeProvider.GetWebDriverOptionsType(driverType, options.OptionsType);
- }
- catch(Exception e)
- {
- if(options.DriverFactoryType == null)
- logger.LogError(e,
- "No type deriving from {OptionsBase} could be found for the combination of {WebDriverIface} {DriverType} and {OptionsTypeProp} '{OptionsType}'; the configuration '{ConfigKey}' will be omitted. " +
- "See the exception details for more information.",
- nameof(DriverOptions),
- nameof(IWebDriver),
- driverType?.Name,
- nameof(WebDriverCreationOptions.OptionsType),
- options?.OptionsType,
- configuration.Key);
-
- return options.DriverFactoryType != null ? true : false;
- }
-
- try
- {
- options.OptionsFactory = GetOptions(optionsType, configuration);
- return true;
- }
- catch(Exception e)
- {
- if(options.DriverFactoryType == null)
- logger.LogError(e,
- "An unexpected error occurred creating or binding to the {OptionsClass} type {OptionsType}; the configuration '{ConfigKey}' will be omitted.",
- nameof(DriverOptions),
- optionsType.FullName,
- configuration.Key);
- return options.DriverFactoryType != null ? true : false;
- }
- }
-
bool TrySetOptionsCustomizer(WebDriverCreationOptions options, IConfigurationSection configuration, Type optionsType)
{
var customizerTypeName = configuration.GetValue("OptionsCustomizerType");
@@ -167,16 +67,6 @@ bool TrySetOptionsCustomizer(WebDriverCreationOptions options, IConfigurationSec
}
}
- static Func GetOptions(Type optionsType, IConfigurationSection config)
- {
- return () =>
- {
- var options = (DriverOptions)Activator.CreateInstance(optionsType);
- config.Bind("Options", options);
- return options;
- };
- }
-
static object GetOptionsCustomizer(Type optionsType, string customizerTypeName)
{
if(string.IsNullOrWhiteSpace(customizerTypeName)) return null;
@@ -189,16 +79,22 @@ static object GetOptionsCustomizer(Type optionsType, string customizerTypeName)
return Activator.CreateInstance(customizerType);
}
-
+
///
/// Initializes a new instance of the class.
///
- /// The provider for web driver and options types.
+ /// A service to get the driver type
+ /// A service to get the options type
+ /// A service to get the driver options
/// The logger for this parser.
- public WebDriverConfigurationItemParser(IGetsWebDriverAndOptionsTypes typeProvider,
+ public WebDriverConfigurationItemParser(IGetsDriverType driverTypeProvider,
+ IGetsOptionsType optionsTypeProvider,
+ ICreatesDriverOptions optionsFactory,
ILogger logger)
{
- this.typeProvider = typeProvider ?? throw new ArgumentNullException(nameof(typeProvider));
+ this.driverTypeProvider = driverTypeProvider ?? throw new ArgumentNullException(nameof(driverTypeProvider));
+ this.optionsTypeProvider = optionsTypeProvider ?? throw new ArgumentNullException(nameof(optionsTypeProvider));
+ this.optionsFactory = optionsFactory ?? throw new ArgumentNullException(nameof(optionsFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
}
diff --git a/CSF.Extensions.WebDriver/Factories/WebDriverCreationConfigureOptions.cs b/CSF.Extensions.WebDriver/Factories/WebDriverCreationConfigureOptions.cs
index 8266383..6c63d95 100644
--- a/CSF.Extensions.WebDriver/Factories/WebDriverCreationConfigureOptions.cs
+++ b/CSF.Extensions.WebDriver/Factories/WebDriverCreationConfigureOptions.cs
@@ -25,13 +25,20 @@ public sealed class WebDriverCreationConfigureOptions : IConfigureOptions logger;
+ readonly Action configureOptions;
///
public void Configure(WebDriverCreationOptionsCollection options)
+ {
+ ConfigureUsingConfig(options);
+ configureOptions?.Invoke(options);
+ }
+
+ void ConfigureUsingConfig(WebDriverCreationOptionsCollection options)
{
if(configuration is null)
{
- logger.LogWarning("Configuration for {TypeName} is null; the WebDriver creation options will be left unconfigured. " +
+ logger.LogWarning("Configuration for {TypeName} is null; the WebDriver creation options will not be configured from any configuration source. " +
"Reminder: By default the configuration path is '{Path}'.",
nameof(WebDriverCreationOptionsCollection),
ServiceCollectionExtensions.FactoryConfigPath);
@@ -50,7 +57,7 @@ IDictionary GetDriverConfigurations(IConfigura
.Where(x => x.Value != null)
.ToDictionary(k => k.Key, v => v.Value);
-
+
///
/// Initialises a new instance of .
@@ -58,14 +65,17 @@ IDictionary GetDriverConfigurations(IConfigura
/// A parser for a single configuration item.
/// The app configuration.
/// A logging implementation.
+ /// An optional configuration action/callback to further configure the options
/// If either parameter is .
public WebDriverCreationConfigureOptions(IParsesSingleWebDriverConfigurationSection configParser,
IConfiguration configuration,
- ILogger logger)
+ ILogger logger,
+ Action configureOptions)
{
this.configParser = configParser ?? throw new ArgumentNullException(nameof(configParser));
this.configuration = configuration;
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ this.configureOptions = configureOptions;
}
}
}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver/Factories/WebDriverCreationOptions.cs b/CSF.Extensions.WebDriver/Factories/WebDriverCreationOptions.cs
index f07cff9..fac6b9b 100644
--- a/CSF.Extensions.WebDriver/Factories/WebDriverCreationOptions.cs
+++ b/CSF.Extensions.WebDriver/Factories/WebDriverCreationOptions.cs
@@ -276,9 +276,30 @@ public Func OptionsFactory
///
public bool AddBrowserQuirks { get; set; } = true;
+ ///
+ /// Gets or sets a value which indicates the logging level that the web browser should retain within its console.
+ ///
+ ///
+ ///
+ /// This setting is effective only for browsers which provide direct access to logs. At the time of writing this is only
+ /// Chromium-based browsers such as Chrome or Edge.
+ ///
+ ///
+ /// The value of this property must correspond to the string representation of a Selenium .
+ /// If this value is then the log level will be left at the browser's default.
+ ///
+ ///
+ /// The setting from this option (if set) will be used via a call to options.SetLoggingPreference(LogType.Browser, LOG_LEVEL);
+ /// where: options is the object used to create the web driver, and LOG_LEVEL is an enum value
+ /// derived from the value of this property. The actual supported values for LOG_LEVEL are browser-specific, for the target web
+ /// browser.
+ ///
+ ///
+ public string BrowserLogLevel { get; set; }
+
static Func GetUnsetOptionsFactory()
{
- return () => throw new InvalidOperationException($"Driver options cannot be created via {nameof(OptionsFactory)}; either {nameof(DriverType)} must be set to a type which indicates a deterministic options type or {nameof(OptionsType)} must set set. If you are using a custom {nameof(DriverFactoryType)} then it may not be appropriate to create options in this way.");
+ return () => throw new InvalidOperationException($"Driver options cannot be created via {nameof(OptionsFactory)}; either {nameof(DriverType)} must be set to a type which indicates a deterministic options type or {nameof(OptionsType)} must be set. If you are using a custom {nameof(DriverFactoryType)} then it may not be appropriate to create options in this way.");
}
}
}
\ No newline at end of file
diff --git a/CSF.Extensions.WebDriver/ServiceCollectionExtensions.cs b/CSF.Extensions.WebDriver/ServiceCollectionExtensions.cs
index d5014fc..36334a9 100644
--- a/CSF.Extensions.WebDriver/ServiceCollectionExtensions.cs
+++ b/CSF.Extensions.WebDriver/ServiceCollectionExtensions.cs
@@ -64,12 +64,8 @@ public static IServiceCollection AddWebDriverFactory(this IServiceCollection ser
string configPath = FactoryConfigPath,
Action configureOptions = null)
{
- AddWebDriverFactoryWithoutOptionsPattern(services);
-
- services.AddTransient();
- services.AddTransient();
+ AddWebDriverFactory(services, configureOptions);
services.AddTransient(GetOptionsConfigService(configPath, configureOptions));
- services.AddOptions().Configure(configureOptions ?? (o => {}));
return services;
}
@@ -114,15 +110,25 @@ public static IServiceCollection AddWebDriverFactory(this IServiceCollection ser
public static IServiceCollection AddWebDriverFactory(this IServiceCollection services,
IConfigurationSection configSection,
Action configureOptions = null)
+ {
+ AddWebDriverFactory(services, configureOptions);
+ services.AddTransient(GetOptionsConfigService(configSection, configureOptions));
+
+ return services;
+ }
+
+ static void AddWebDriverFactory(this IServiceCollection services,
+ Action configureOptions = null)
{
AddWebDriverFactoryWithoutOptionsPattern(services);
services.AddTransient();
services.AddTransient();
- services.AddTransient(GetOptionsConfigService(configSection, configureOptions));
+ services.AddTransient();
+ services.AddTransient();
+ AddDriverOptionsFactory(services);
services.AddOptions().Configure(configureOptions ?? (o => {}));
-
- return services;
+ services.AddTransient();
}
///
@@ -236,7 +242,10 @@ static Func
{
IConfiguration configSection = services.GetRequiredService().GetSection(configPath);
- return ActivatorUtilities.CreateInstance(services, configSection);
+ return new WebDriverCreationConfigureOptions(services.GetRequiredService(),
+ configSection,
+ services.GetRequiredService>(),
+ configureOptions);
};
}
@@ -245,19 +254,36 @@ static Func
{
- return ActivatorUtilities.CreateInstance(services, configSection, configureOptions);
+ return new WebDriverCreationConfigureOptions(services.GetRequiredService(),
+ configSection,
+ services.GetRequiredService>(),
+ configureOptions);
};
}
- static IServiceCollection AddLoggingIfNotAlreadyAdded(IServiceCollection services)
+ static void AddLoggingIfNotAlreadyAdded(IServiceCollection services)
{
if(services.Any(s => s.ServiceType == typeof(ILoggerFactory)))
- return services;
+ return;
services.AddTransient();
services.AddTransient(typeof(ILogger<>), typeof(NullLogger<>));
+ }
- return services;
+ static void AddDriverOptionsFactory(IServiceCollection services)
+ {
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+ services.AddTransient(s =>
+ {
+ ICreatesDriverOptions service = s.GetRequiredService();
+ service = ActivatorUtilities.CreateInstance(s, service);
+ service = ActivatorUtilities.CreateInstance(s, service);
+
+ return service;
+ });
}
}
}