Skip to content

Commit e6df7ff

Browse files
authored
Merge pull request #42 from csf-dev/feature/41-version2
Resolve #41 - version 2 of the library
2 parents 53663e6 + 1bf37de commit e6df7ff

409 files changed

Lines changed: 29890 additions & 6922 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.

.appveyor.yml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
version: '{branch}-{build}'
1+
image: Visual Studio 2022
2+
version: '{branch}-{build}'
23
init:
34
- cmd: git config --global core.autocrlf true
45
before_build:
5-
- cmd: Tools\Appveyor.before_build.bat
6-
build:
7-
project: CSF.WebDriverExtras.sln
8-
verbosity: normal
9-
test:
10-
assemblies:
11-
except:
12-
- '**\Ploeh.AutoFixture.NUnit3.dll'
6+
- dotnet tool update -g docfx
7+
build_script:
8+
- dotnet build
9+
- docfx CSF.Extensions.WebDriver.Docs\docfx.json
10+
test_script:
11+
- dotnet test

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[*.cs]
2+
3+
# IDE0011: Add braces
4+
csharp_prefer_braces = when_multiline

.gitignore

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
bin/
22
obj/
3-
packages/*/*
4-
testrunner/
5-
.directory
63
*.userprefs
74
*.csproj.user
8-
.vs/
95
TestResult.xml
10-
TestResult.formatted.xml
11-
*.report.txt
12-
Tests/Temp/
13-
Screenshots/
146
*.log
157
*.orig
16-
.xsp4.pid
17-
Backup/
18-
UpgradeLog.htm
198
*.nupkg
9+
CSF.Extensions.WebDriver.Docs/_site/
10+
CSF.Extensions.WebDriver.Docs/api/

.travis.yml

Lines changed: 0 additions & 16 deletions
This file was deleted.

.vscode/tasks.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"type": "dotnet",
6+
"task": "build",
7+
"group": {
8+
"kind": "build",
9+
"isDefault": true
10+
},
11+
"problemMatcher": [],
12+
"label": "dotnet: build"
13+
},
14+
{
15+
"group": {
16+
"kind": "test",
17+
"isDefault": true
18+
},
19+
"command": "dotnet",
20+
"args": ["test"],
21+
"problemMatcher": [],
22+
"label": "dotnet: test"
23+
},
24+
{
25+
"group": {
26+
"kind": "build"
27+
},
28+
"command": "docfx",
29+
"args": ["CSF.Extensions.WebDriver.Docs/docfx.json"],
30+
"problemMatcher": [],
31+
"label": "docfx: build"
32+
},
33+
{
34+
"group": {
35+
"kind": "build"
36+
},
37+
"command": "docfx",
38+
"args": ["CSF.Extensions.WebDriver.Docs/docfx.json", "--serve"],
39+
"problemMatcher": [],
40+
"label": "docfx: serve"
41+
}
42+
]
43+
}

CSF.Extensions.WebDriver.Docs/.nojekyll

Whitespace-only changes.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"metadata": [
3+
{
4+
"src": [
5+
{
6+
"src": "../",
7+
"files": [
8+
"**/*.csproj"
9+
],
10+
"exclude": [
11+
"docs/**",
12+
"**/bin/**",
13+
"**/obj/**",
14+
"CSF.Extensions.WebDriver.Tests/**"
15+
]
16+
}
17+
],
18+
"dest": "api"
19+
}
20+
],
21+
"build": {
22+
"content": [
23+
{
24+
"files": [
25+
"**/*.{md,yml}"
26+
],
27+
"exclude": []
28+
}
29+
],
30+
"resource": [
31+
{
32+
"files": [
33+
"images/**",
34+
".nojekyll"
35+
]
36+
}
37+
],
38+
"output": "../docs",
39+
"template": [
40+
"default",
41+
"modern"
42+
],
43+
"globalMetadata": {
44+
"_appName": "CSF.Extensions.WebDriver",
45+
"_appTitle": "CSF.Extensions.WebDriver",
46+
"_enableSearch": true,
47+
"pdf": false
48+
}
49+
}
50+
}
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)
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

0 commit comments

Comments
 (0)