Skip to content

Commit 1bf37de

Browse files
committed
WIP #41 - update docco
Provisionally, this is all done now.
1 parent c4df40e commit 1bf37de

76 files changed

Lines changed: 1665 additions & 10461 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/tasks.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"kind": "build"
2727
},
2828
"command": "docfx",
29-
"args": ["build", "CSF.Extensions.WebDriver.Docs/docfx.json"],
29+
"args": ["CSF.Extensions.WebDriver.Docs/docfx.json"],
3030
"problemMatcher": [],
3131
"label": "docfx: build"
3232
},
@@ -35,7 +35,7 @@
3535
"kind": "build"
3636
},
3737
"command": "docfx",
38-
"args": ["build", "CSF.Extensions.WebDriver.Docs/docfx.json", "--serve"],
38+
"args": ["CSF.Extensions.WebDriver.Docs/docfx.json", "--serve"],
3939
"problemMatcher": [],
4040
"label": "docfx: serve"
4141
}

CSF.Extensions.WebDriver.Docs/docfx.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
"**/*.csproj"
99
],
1010
"exclude": [
11-
"CSF.Extensions.WebDriver.Tests/**",
12-
"docs/**"
11+
"docs/**",
12+
"**/bin/**",
13+
"**/obj/**",
14+
"CSF.Extensions.WebDriver.Tests/**"
1315
]
1416
}
1517
],
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# WebDriver identification
2+
3+
Most implementations of `IWebDriver` also implement `IHasCapabilities` and have capabilities indicating the browser name, version and platform.
4+
These values are strings though, which is particularly troublesome for the browser version when we need to answer questions such as _is this browser between versions X and Y?_
5+
6+
When using [the universal WebDriver factory], the returned WebDriver is enhanced with an additional interface: [`IHasBrowserId`].
7+
This interface provides a get-only property of type [`BrowserId`], which in-turn provides a [`BrowserVersion`].
8+
This enhancement may be disabled by setting [`AddBrowserIdentification`] in the WebDriver creation options to `false`.
9+
10+
[the universal WebDriver factory]: index.md
11+
[`IHasBrowserId`]: xref:CSF.Extensions.WebDriver.Identification.IHasBrowserId
12+
[`BrowserId`]: xref:CSF.Extensions.WebDriver.Identification.BrowserId
13+
[`BrowserVersion`]: xref:CSF.Extensions.WebDriver.Identification.BrowserVersion
14+
[`AddBrowserIdentification`]: xref:CSF.Extensions.WebDriver.Factories.WebDriverCreationOptions.AddBrowserIdentification
15+
16+
## A note on proxies
17+
18+
Be aware that when the WebDriver identification functionality is activated, [the WebDriver returned by the universal factory will be _a proxy object_] and not the original concrete WebDriver implementation.
19+
In best-practice scenarios where the WebDriver is utilised only by its interfaces this should make no difference.
20+
More information is available at the linked documentation above.
21+
22+
[the WebDriver returned by the universal factory will be _a proxy object_]: Proxies.md
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Proxied WebDrivers
2+
3+
Two of the functions of the universal factory require adding additional interfaces to the WebDriver:
4+
5+
* [Browser identification]
6+
* [WebDriver quirks]
7+
8+
The only sensible way to do this at runtime, without either disrupting other interfaces which were present on the WebDriver or [being forced to violate LSP], is to make use of a proxying library.
9+
In the case of CSF.Extensions.WebDriver, [Castle DynamicProxy] is used.
10+
11+
What this means is that when the [universal WebDriver factory] returns a WebDriver, if either or both of the functionalities above are enabled, then the WebDriver returned will be a proxy object and not the original concrete implementation of `IWebDriver`.
12+
13+
[Browser identification]: DriverIdentification.md
14+
[WebDriver quirks]: Quirks.md
15+
[being forced to violate LSP]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
16+
[Castle DynamicProxy]: https://www.castleproject.org/projects/dynamicproxy/
17+
[universal WebDriver factory]: index.md
18+
19+
## Consequences
20+
21+
Take the following code as an example, the first two lines would create a minimal default local Chrome WebDriver.
22+
23+
```csharp
24+
// Imagine this factory has been dependency-injected
25+
ICreatesWebDriverFromOptions factory;
26+
27+
var webDriver = factory.GetWebDriver(new () { DriverType = "ChromeDriver" });
28+
var hasActiveDevTools = ((ChromeDriver) webDriver).HasActiveDevToolsSession;
29+
```
30+
31+
_The last line of code above would crash_ with an `InvalidCastException`.
32+
That's because the `webDriver` object is not an instance of `ChromeDriver`, it is a proxy object wrapping that `ChromeDriver` instance.
33+
34+
When using Selenium, and in software development in general, it is bad practice to depend upon concrete classes when interfaces are available.
35+
In well-written logic which depends upon only interfaces, the limitation above does not come into play.
36+
_Proxy WebDrivers have all of the same interfaces as the WebDriver with which they were created_ and provide the same functionality for all of them.
37+
Those interfaces are detected upon the WebDriver as the proxy is created, so third party WebDrivers with additional/unknown interfaces would also be supported.
38+
39+
## Unproxying
40+
41+
If you encounter an (unexpected) situation where the proxied WebDriver causes a problem, this library provides a mechanism of getting the original 'unproxied' WebDriver:
42+
43+
```csharp
44+
var unproxied = maybeProxy.Unproxy();
45+
```
46+
47+
In the example above, if `maybeProxy` was a proxied WebDriver, `unproxied` is now the original WebDriver instance which was wrapped by the proxy.
48+
The [`Unproxy()`] extension method is safe to use on both proxy and non-proxy WebDrivers.
49+
If used upon a WebDriver which is not a proxy then it simply does nothing and returns the same WebDriver instance.
50+
51+
[`Unproxy()`]: xref:OpenQA.Selenium.WebDriverExtensions.Unproxy(OpenQA.Selenium.IWebDriver)

CSF.Extensions.WebDriver.Docs/docs/QuickStart.md

Whitespace-only changes.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# WebDriver quirks
2+
3+
_There are a lot of web browsers and browser versions out there!_
4+
Unfortunately they do not all behave in a perfectly uniform manner; some WebDriver implementations have bugs and some just have oddities which are unique to them.
5+
A real (albeit now-outdated) example is that [WebDriver for Apple Safari v11 could not change the selection of an HTML `<select>` element].
6+
When faced with that bug, the only course of action a developer could take was to work around it.
7+
8+
[WebDriver for Apple Safari v11 could not change the selection of an HTML `<select>` element]: https://github.com/SeleniumHQ/selenium/issues/5475#issuecomment-365082942
9+
10+
## How the 'quirks' architecture can help
11+
12+
Developers do not want to litter their WebDriver-consuming code with browser detection logic.
13+
Just like in regular web development, [browser detection is bad, feature detection is better].
14+
What the quirks architecture provides is an additional interface, added to WebDrivers created by [the universal WebDriver factory]: [`IHasQuirks`].
15+
16+
WebDrivers which implement `IHasQuirks` can cross-reference their [browser identification] with source data listing which browsers are affected by which quirks.
17+
The result is the [`AllQuirks`] property and the following extension methods (for convenience):
18+
19+
* [`HasQuirk(string)`]
20+
* [`GetQuirks()`]
21+
* [`GetFirstApplicableQuirk(params string[])`]
22+
23+
[browser detection is bad, feature detection is better]: https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection
24+
[the universal WebDriver factory]: index.md
25+
[`IHasQuirks`]: xref:CSF.Extensions.WebDriver.Quirks.IHasQuirks
26+
[browser identification]: DriverIdentification.md
27+
[`AllQuirks`]: xref:CSF.Extensions.WebDriver.Quirks.IHasQuirks.AllQuirks
28+
29+
[`HasQuirk(string)`]: xref:OpenQA.Selenium.WebDriverExtensions.HasQuirk(OpenQA.Selenium.IWebDriver,System.String)
30+
[`GetQuirks()`]: xref:OpenQA.Selenium.WebDriverExtensions.GetQuirks(OpenQA.Selenium.IWebDriver)
31+
[`GetFirstApplicableQuirk(params string[])`]: xref:OpenQA.Selenium.WebDriverExtensions.GetFirstApplicableQuirk(OpenQA.Selenium.IWebDriver,System.String[])
32+
33+
## The quirks source data
34+
35+
Quirks source data may come from two sources.
36+
To use quirks _at least one source must be activated_ and it is recommended to enable both.
37+
38+
* Hard-coded into an application/library
39+
* Supplementary configuration data
40+
41+
The intent is that an application or library may ship with quirks information that is known at the time of writing.
42+
This information may be supplemented or (in part or wholly) overwritten by quirks information provided by the consumer.
43+
This allows consuming logic to react to changes in browser quirks (as time moves on) by adding their own quirks configuration and not needing to wait for an upstream app/library to release an updated version with new quirks source data.
44+
45+
This library uses a simple merging algorithm to combine the hard-coded and options data-sources.
46+
Where the two sources list quirks that the other source does not, the resultant data will contain both quirks.
47+
Where the two sources list the same quirk, the Options data will _win the disagreement_, so to speak, and will shadow the hard-coded data.
48+
49+
Developers may use this technique to update the affected browsers for a quirk or even to (effectively) remove it, by giving it an empty set of affected browsers.
50+
51+
## Setting up quirks functionality
52+
53+
To configure the source data you must activate it in source control.
54+
In the following example, `quirksData` represents data which would be hard-coded into your application/library (design-time).
55+
56+
```csharp
57+
services.AddWebDriverQuirks(quirksData);
58+
```
59+
60+
The [`AddWebDriverQuirks`] method is customisable with a number of parameters, most of which are not shown above.
61+
By default any quirks data specified in [the app Configuration], via [the Options Pattern], will be used to supplement and/or override that hard-coded data.
62+
The default configuration path for quirks data is `WebDriverQuirks`.
63+
64+
Lastly, to use quirks functionality it must also be activated in the [`WebDriverCreationOptions`], via the [`AddBrowserQuirks`] property.
65+
66+
[`AddWebDriverQuirks`]: xref:CSF.Extensions.WebDriver.ServiceCollectionExtensions.AddWebDriverQuirks(Microsoft.Extensions.DependencyInjection.IServiceCollection,CSF.Extensions.WebDriver.Quirks.QuirksData,System.Boolean,System.String)
67+
[the app Configuration]: https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration
68+
[the Options Pattern]: https://learn.microsoft.com/en-us/dotnet/core/extensions/options
69+
[`WebDriverCreationOptions`]: xref:CSF.Extensions.WebDriver.Factories.WebDriverCreationOptions
70+
[`AddBrowserQuirks`]: xref:CSF.Extensions.WebDriver.Factories.WebDriverCreationOptions.AddBrowserQuirks
71+
72+
## A note on proxies
73+
74+
Be aware that when the WebDriver quirks functionality is activated, [the WebDriver returned by the universal factory will be _a proxy object_] and not the original concrete WebDriver implementation.
75+
In best-practice scenarios where the WebDriver is utilised only by its interfaces this should make no difference.
76+
More information is available at the linked documentation above.
77+
78+
[the WebDriver returned by the universal factory will be _a proxy object_]: Proxies.md

CSF.Extensions.WebDriver.Docs/docs/UniversalFactory.md

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Universal WebDriver factory
2+
3+
A common requirement when performing browser testing is to conduct tests using a variety of browsers.
4+
This helps ensure that app functionality is not reliant upon a particular browser feature or quirk and it's truly cross-browser compatible.
5+
The universal WebDriver factory is a configuration-driven mechanism by which WebDriver instances may be constructed.
6+
It is based upon Microsoft [Dependency Injection] and optionally the [Options Pattern] and [Configuration].
7+
8+
The WebDriver factory is the mechanism by which other functionality in this library is activated.
9+
To begin using it, follow the three steps below.
10+
11+
[Dependency Injection]: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection
12+
[Options Pattern]: https://learn.microsoft.com/en-us/dotnet/core/extensions/options
13+
[Configuration]: https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration
14+
15+
## 1. Add the factory to dependency injection
16+
17+
It is recommended to use [`AddWebDriverFactory`] with your dependency injection configuration.
18+
This enables the Microsoft Options Pattern and Configuration for the WebDriver factory.
19+
You may alternatively use [`AddWebDriverFactoryWithoutOptionsPattern`] if you do not with to use those technologies, although some features will be unavailable to you if you choose this.
20+
21+
```csharp
22+
services.AddConfiguration();
23+
services.AddWebDriverFactory();
24+
```
25+
26+
There are overloads of `AddWebDriverFactory` available to:
27+
28+
* Specify a non-default configuration path for the WebDriver factory options; the default is `WebDriverFactory`
29+
* Specify a configuration section from which to build the WebDriver factory options
30+
* Specify an additional configuration callback to provide extra options outside the configuration system
31+
32+
Read the documentation for these functions (linked above) for more info.
33+
34+
[`AddWebDriverFactory`]: xref:CSF.Extensions.WebDriver.ServiceCollectionExtensions.AddWebDriverFactory(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.String,System.Action{CSF.Extensions.WebDriver.Factories.WebDriverCreationOptionsCollection})
35+
[`AddWebDriverFactoryWithoutOptionsPattern`]: xref:CSF.Extensions.WebDriver.ServiceCollectionExtensions.AddWebDriverFactoryWithoutOptionsPattern(Microsoft.Extensions.DependencyInjection.IServiceCollection)
36+
37+
## 2. Include configuration for one or more WebDrivers
38+
39+
This configuration should be written using whichever configuration mechanism you wish to use.
40+
Here is an example using the common `appsettings.json` format:
41+
42+
```json
43+
{
44+
"WebDriverFactory": {
45+
"DriverConfigurations": {
46+
"MyRemoteSafari": {
47+
"DriverType": "RemoteWebDriver",
48+
"OptionsType": "SafariOptions",
49+
"GridUrl": "https://gridurl.example.com/url-path"
50+
},
51+
"MyLocalChrome": {
52+
"DriverType": "ChromeDriver"
53+
}
54+
},
55+
"SelectedConfiguration": "MyLocalChrome"
56+
}
57+
}
58+
```
59+
60+
You may set one of your configurations to be 'the selected default' if you wish, enabling you to use [`GetDefaultWebDriver()`].
61+
Do not forget that you may provide configuration from multiple sources; for example you may specify your available driver configurations in a JSON file but specify the default selected one via a command-line parameter such as:
62+
63+
```txt
64+
--WebDriverFactory::SelectedConfiguration MyConfigurationName
65+
```
66+
67+
> [!TIP]
68+
> Do not store secrets such as passwords in your configuration.
69+
> The methods of [`IGetsWebDriver`] and [`ICreatesWebDriverFromOptions`] provide parameters whereby secrets may be injected into the `DriverOptions` from external sources, such as environment variables.
70+
> This avoids the need to add secrets to source-controlled files.
71+
72+
[`GetDefaultWebDriver()`]: xref:CSF.Extensions.WebDriver.IGetsWebDriver.GetDefaultWebDriver(System.Action{OpenQA.Selenium.DriverOptions})
73+
74+
## 3. Inject and use the services
75+
76+
Use dependency injection to inject an [`IGetsWebDriver`].
77+
Use this service to get WebDriver instances.
78+
79+
`IGetsWebDriver` is unavailable if you used [`AddWebDriverFactoryWithoutOptionsPattern`] when setting this functionality up.
80+
In that case you must use [`ICreatesWebDriverFromOptions`] instead.
81+
This service offers the same functionality except that the consumer is responsible for specifying the [`WebDriverCreationOptions`]; they are not retrieved from Options.
82+
83+
[`IGetsWebDriver`]: xref:CSF.Extensions.WebDriver.IGetsWebDriver
84+
[`ICreatesWebDriverFromOptions`]: xref:CSF.Extensions.WebDriver.Factories.ICreatesWebDriverFromOptions
85+
[`WebDriverCreationOptions`]: xref:CSF.Extensions.WebDriver.Factories.WebDriverCreationOptions
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
- name: Quick start
2-
href: QuickStart.md
31
- name: Universal factory
4-
href: UniversalFactory.md
2+
href: index.md
53
- name: Driver identification
64
href: DriverIdentification.md
75
- name: Quirks
86
href: Quirks.md
7+
- name: Proxies
8+
href: Proxies.md

CSF.Extensions.WebDriver.Docs/index.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ This is broadly organised into three features:
1111
* A mechanism to [reliably identify WebDriver instances] and their version information, after they are created
1212
* A mechanism to ['mark WebDrivers up' with information about their quirks] which affect that browser/WebDriver/version combination
1313

14-
To get going straight away, read [the **quick start** documentation].
15-
1614
[Selenium WebDriver]: https://www.selenium.dev/documentation/webdriver/
17-
[universal WebDriver factory]: Docs/UniversalFactory.md
15+
[universal WebDriver factory]: Docs/index.md
1816
[reliably identify WebDriver instances]: Docs/DriverIdentification.md
1917
['mark WebDrivers up' with information about their quirks]: Docs/Quirks.md
20-
[the **quick start** documentation]: Docs/QuickStart.md

0 commit comments

Comments
 (0)