-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
32 changed files
with
1,406 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ on: | |
jobs: | ||
build: | ||
|
||
runs-on: ubuntu-latest | ||
runs-on: windows-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
|
15 changes: 15 additions & 0 deletions
15
Dfe.Data.SearchPrototype/Web/Tests/AcceptanceTests/Drivers/IWebDriverContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using OpenQA.Selenium; | ||
using OpenQA.Selenium.Support.UI; | ||
using Xunit.Abstractions; | ||
|
||
namespace Dfe.Data.SearchPrototype.Web.Tests.Acceptance.Drivers; | ||
|
||
public interface IWebDriverContext : IDisposable | ||
{ | ||
IWebDriver Driver { get; } | ||
IWait<IWebDriver> Wait { get; } | ||
void GoToUri(string path); | ||
void GoToUri(string baseUri, string path); | ||
void TakeScreenshot(ITestOutputHelper logger, string testName); | ||
} | ||
|
9 changes: 9 additions & 0 deletions
9
Dfe.Data.SearchPrototype/Web/Tests/AcceptanceTests/Drivers/IWebDriverFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using OpenQA.Selenium; | ||
|
||
namespace Dfe.Data.SearchPrototype.Web.Tests.Acceptance.Drivers; | ||
|
||
public interface IWebDriverFactory | ||
{ | ||
// TODO: reimplement Lazy<IWebDriver> | ||
IWebDriver CreateDriver(); | ||
} |
139 changes: 139 additions & 0 deletions
139
Dfe.Data.SearchPrototype/Web/Tests/AcceptanceTests/Drivers/WebDriverContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
using OpenQA.Selenium.Support.UI; | ||
using OpenQA.Selenium; | ||
using System.Drawing; | ||
using Dfe.Data.SearchPrototype.Web.Tests.Acceptance.Options; | ||
using Microsoft.Extensions.Options; | ||
using Xunit.Abstractions; | ||
|
||
namespace Dfe.Data.SearchPrototype.Web.Tests.Acceptance.Drivers; | ||
|
||
public class WebDriverContext : IWebDriverContext | ||
{ | ||
private readonly WebDriverOptions _driverOptions; | ||
// TODO: reimplement Lazy<IWebDriver> | ||
private readonly IWebDriver _driver; | ||
private readonly Lazy<IWait<IWebDriver>> _wait; | ||
private readonly string _baseUri; | ||
|
||
public IWebDriver Driver => _driver; | ||
public IWait<IWebDriver> Wait => _wait.Value; | ||
private Type[] IgnoredExceptions { get; } = [typeof(StaleElementReferenceException)]; | ||
|
||
public WebDriverContext( | ||
IWebDriverFactory factory, | ||
IOptions<WebOptions> options, | ||
IOptions<WebDriverOptions> driverOptions | ||
) | ||
{ | ||
_driver = factory?.CreateDriver() ?? throw new ArgumentNullException(nameof(factory)); | ||
_baseUri = options.Value.GetWebUri() ?? throw new ArgumentNullException(nameof(options)); | ||
_wait = new(() => InitializeWait(Driver, IgnoredExceptions)); | ||
_driverOptions = driverOptions.Value; | ||
} | ||
|
||
private IJavaScriptExecutor JsExecutor => | ||
Driver as IJavaScriptExecutor ?? | ||
throw new ArgumentNullException(nameof(IJavaScriptExecutor)); | ||
|
||
/// <summary> | ||
/// Navigate to relative path | ||
/// </summary> | ||
/// <param name="path"></param> | ||
/// <returns></returns> | ||
public void GoToUri(string path) => GoToUri(_baseUri, path); | ||
|
||
/// <summary> | ||
/// Navigate to a uri | ||
/// </summary> | ||
/// <param name="baseUri">baseUri for site e.g https://google.co.uk</param> | ||
/// <param name="path">path from baseUri defaults to '/' e.g '/login'</param> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
public void GoToUri(string baseUri, string path = "/") | ||
{ | ||
_ = baseUri ?? throw new ArgumentNullException(nameof(baseUri)); | ||
var absoluteUri = $"{baseUri.TrimEnd('/')}{path}"; | ||
if (Uri.TryCreate(absoluteUri, UriKind.Absolute, out var uri)) | ||
{ | ||
Driver.Navigate().GoToUrl(uri); | ||
} | ||
else | ||
{ | ||
throw new ArgumentException(nameof(absoluteUri)); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Dispose of <see cref="IWebDriver"/> | ||
/// </summary> | ||
public void Dispose() | ||
{ | ||
Dispose(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
|
||
protected virtual void Dispose(bool disposing) | ||
{ | ||
if (disposing) | ||
{ | ||
using (Driver) | ||
{ | ||
Driver.Quit(); | ||
} | ||
} | ||
} | ||
|
||
public void TakeScreenshot(ITestOutputHelper logger, string testName) | ||
{ | ||
|
||
// Allows alternative path | ||
var baseScreenshotDirectory = | ||
Path.IsPathFullyQualified(_driverOptions.ScreenshotsDirectory) ? | ||
_driverOptions.ScreenshotsDirectory : | ||
Path.Combine(Directory.GetCurrentDirectory(), _driverOptions.ScreenshotsDirectory); | ||
|
||
Directory.CreateDirectory(baseScreenshotDirectory); | ||
|
||
var outputPath = Path.Combine( | ||
baseScreenshotDirectory, | ||
testName + ".png" | ||
); | ||
|
||
// Maximise viewport | ||
Driver.Manage().Window.Size = new Size( | ||
width: GetBrowserWidth(), | ||
height: GetBrowserHeight() | ||
); | ||
|
||
// Screenshot | ||
(Driver as ITakesScreenshot)? | ||
.GetScreenshot() | ||
.SaveAsFile(outputPath); | ||
|
||
logger.WriteLine($"SCREENSHOT SAVED IN LOCATION: {outputPath}"); | ||
} | ||
|
||
private static IWait<IWebDriver> InitializeWait(IWebDriver driver, Type[] ignoredExceptions) | ||
{ | ||
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(20)); | ||
wait.IgnoreExceptionTypes(ignoredExceptions); | ||
return wait; | ||
} | ||
|
||
private int GetBrowserWidth() => | ||
// Math.max returns a 64 bit number requiring casting | ||
(int)(long)JsExecutor.ExecuteScript( | ||
@"return Math.max( | ||
window.innerWidth, | ||
document.body.scrollWidth, | ||
document.documentElement.scrollWidth)" | ||
); | ||
|
||
private int GetBrowserHeight() => | ||
// Math.max returns a 64 bit number requiring casting | ||
(int)(long)JsExecutor.ExecuteScript( | ||
@"return Math.max( | ||
window.innerHeight, | ||
document.body.scrollHeight, | ||
document.documentElement.scrollHeight)" | ||
); | ||
} |
47 changes: 47 additions & 0 deletions
47
Dfe.Data.SearchPrototype/Web/Tests/AcceptanceTests/Drivers/WebDriverExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using OpenQA.Selenium; | ||
using OpenQA.Selenium.Support.UI; | ||
using SeleniumExtras.WaitHelpers; | ||
using Xunit; | ||
|
||
namespace Dfe.Data.SearchPrototype.Web.Tests.Acceptance.Drivers; | ||
|
||
public static class WebDriverExtensions | ||
{ | ||
public static void ElementDoesNotExist(this IWebDriverContext context, Func<IWebElement> locate) | ||
{ | ||
context.Wait.Timeout = TimeSpan.FromSeconds(4); | ||
Assert.ThrowsAny<WebDriverTimeoutException>(locate); | ||
} | ||
|
||
public static IWebElement UntilElementContainsText(this IWebElement element, IWebDriverContext context, string text) | ||
{ | ||
_ = text ?? throw new ArgumentNullException(nameof(text)); | ||
context.Wait.Message = $"Element did not contain text {text}"; | ||
context.Wait.Until(t => element.Text.Contains(text)); | ||
context.Wait.Message = string.Empty; | ||
return element; | ||
} | ||
|
||
public static IWebElement UntilElementTextIs(IWebElement element, IWait<IWebDriver> wait, string text) | ||
{ | ||
_ = text ?? throw new ArgumentNullException(nameof(text)); | ||
wait.Message = $"Element did not equal text {text}"; | ||
wait.Until(t => element.Text.Contains(text)); | ||
wait.Message = string.Empty; | ||
return element; | ||
} | ||
|
||
public static IWebElement UntilAriaExpandedIs(this IWait<IWebDriver> wait, bool isExpanded, By locator) | ||
{ | ||
var element = wait.UntilElementExists(locator); | ||
wait.Until(_ => element.GetAttribute("aria-expanded") == (isExpanded ? "true" : "false")); | ||
return element; | ||
} | ||
public static IWebElement UntilElementExists(this IWait<IWebDriver> wait, By by) => wait.Until(ExpectedConditions.ElementExists(by)); | ||
|
||
public static IWebElement UntilElementIsVisible(this IWait<IWebDriver> wait, By by) => wait.Until(ExpectedConditions.ElementIsVisible(by)); | ||
|
||
public static IWebElement UntilElementIsClickable(this IWait<IWebDriver> wait, By by) => wait.Until(ExpectedConditions.ElementToBeClickable(by)); | ||
public static IReadOnlyList<IWebElement> UntilMultipleElementsExist(this IWait<IWebDriver> wait, By by) => wait.Until(ExpectedConditions.PresenceOfAllElementsLocatedBy(by)); | ||
public static IReadOnlyList<IWebElement> UntilMultipleElementsVisible(this IWait<IWebDriver> wait, By by) => wait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(by)); | ||
} |
117 changes: 117 additions & 0 deletions
117
Dfe.Data.SearchPrototype/Web/Tests/AcceptanceTests/Drivers/WebDriverFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
using OpenQA.Selenium.Chrome; | ||
using OpenQA.Selenium.Firefox; | ||
using OpenQA.Selenium; | ||
using Microsoft.Extensions.Options; | ||
using System.Drawing; | ||
using Dfe.Data.SearchPrototype.Web.Tests.Acceptance.Options; | ||
|
||
namespace Dfe.Data.SearchPrototype.Web.Tests.Acceptance.Drivers; | ||
|
||
public sealed class WebDriverFactory : IWebDriverFactory | ||
{ | ||
private static readonly IEnumerable<string> DEFAULT_OPTIONS = new[] | ||
{ | ||
"--incognito", | ||
"--safebrowsing-disable-download-protection", | ||
"--no-sandbox", | ||
"--start-maximized", | ||
"--start-fullscreen" | ||
}; | ||
|
||
private static readonly Dictionary<string, (int x, int y)> MOBILE_VIEWPORTS = new() | ||
{ | ||
{ "desktop", (1920, 1080) }, | ||
{ "iphone14", (390, 844) }, | ||
{ "iphone11", (414, 896) } | ||
}; | ||
|
||
private static TimeSpan DEFAULT_PAGE_LOAD_TIMEOUT = TimeSpan.FromSeconds(30); | ||
|
||
private readonly WebDriverOptions _webDriverOptions; | ||
|
||
private readonly WebDriverSessionOptions _sessionOptions; | ||
|
||
public WebDriverFactory( | ||
IOptions<WebDriverOptions> webDriverOptions, | ||
WebDriverSessionOptions sessionOptions | ||
) | ||
{ | ||
_webDriverOptions = webDriverOptions?.Value ?? throw new ArgumentNullException(nameof(webDriverOptions)); | ||
_sessionOptions = sessionOptions ?? throw new ArgumentNullException(nameof(_sessionOptions)); | ||
} | ||
|
||
public IWebDriver CreateDriver() | ||
{ | ||
// viewports are expressed as cartesian coordinates (x,y) | ||
var viewportDoesNotExist = !MOBILE_VIEWPORTS.TryGetValue(_sessionOptions.Device, out var viewport); | ||
|
||
if (viewportDoesNotExist) | ||
{ | ||
throw new ArgumentException($"device value {_sessionOptions.Device} has no mapped viewport"); | ||
} | ||
var (width, height) = viewport; | ||
|
||
_webDriverOptions.DriverBinaryDirectory ??= Directory.GetCurrentDirectory(); | ||
|
||
IWebDriver driver = _sessionOptions switch | ||
{ | ||
{ DisableJs: true } or { Browser: "firefox" } => CreateFirefoxDriver(_webDriverOptions, _sessionOptions), | ||
_ => CreateChromeDriver(_webDriverOptions) | ||
}; | ||
|
||
driver.Manage().Window.Size = new Size(width, height); | ||
driver.Manage().Cookies.DeleteAllCookies(); | ||
driver.Manage().Timeouts().PageLoad = DEFAULT_PAGE_LOAD_TIMEOUT; | ||
return driver; | ||
|
||
} | ||
|
||
private static ChromeDriver CreateChromeDriver( | ||
WebDriverOptions driverOptions | ||
) | ||
{ | ||
ChromeOptions option = new(); | ||
|
||
option.AddArguments(DEFAULT_OPTIONS); | ||
|
||
// chromium based browsers using new headless switch https://www.selenium.dev/blog/2023/headless-is-going-away/ | ||
|
||
if (driverOptions.Headless) | ||
{ | ||
option.AddArgument("--headless=new"); | ||
} | ||
|
||
option.AddUserProfilePreference("safebrowsing.enabled", true); | ||
option.AddUserProfilePreference("download.prompt_for_download", false); | ||
option.AddUserProfilePreference("disable-popup-blocking", "true"); | ||
option.AddArgument("--window-size=1920,1080"); | ||
return new ChromeDriver(driverOptions.DriverBinaryDirectory, option); | ||
} | ||
|
||
private static FirefoxDriver CreateFirefoxDriver( | ||
WebDriverOptions driverOptions, | ||
WebDriverSessionOptions sessionOptions | ||
) | ||
{ | ||
var options = new FirefoxOptions | ||
{ | ||
// TODO load TLS cert into firefox options | ||
AcceptInsecureCertificates = true, | ||
EnableDevToolsProtocol = true, | ||
}; | ||
|
||
options.AddArguments(DEFAULT_OPTIONS); | ||
|
||
if (driverOptions.Headless) | ||
{ | ||
options.AddArgument("--headless"); | ||
} | ||
|
||
if (sessionOptions.DisableJs) | ||
{ | ||
options.SetPreference("javascript.enabled", false); | ||
} | ||
|
||
return new(driverOptions.DriverBinaryDirectory, options); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
Dfe.Data.SearchPrototype/Web/Tests/AcceptanceTests/Extensions/StringExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using System.Text.RegularExpressions; | ||
|
||
namespace Dfe.Data.SearchPrototype.Web.Tests.Acceptance.Extensions; | ||
|
||
public static class StringExtensions | ||
{ | ||
public static readonly Regex HtmlReplacer = new("<[^>]*>"); | ||
public static string? ToLowerRemoveHyphens(this string? str) | ||
=> string.IsNullOrEmpty(str) ? str : str.Replace(' ', '-').ToLower(); | ||
|
||
public static string? ReplaceHTML(this string? str) | ||
=> string.IsNullOrEmpty(str) ? str : HtmlReplacer.Replace(str, string.Empty); | ||
|
||
public static string SanitiseToHTML(string input) | ||
{ | ||
var ouput = input.Replace("\"", "\'"); | ||
return ouput; | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
Dfe.Data.SearchPrototype/Web/Tests/AcceptanceTests/Features/AccessibilityTests.feature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
Feature: AccessibilityTests | ||
|
||
Scenario: Home page accessibility | ||
When the user views the home page | ||
Then the home page is accessible | ||
|
||
@ignore | ||
Scenario: Search results page accessibility | ||
When the user views the search results page | ||
Then the search results page is accessible |
Oops, something went wrong.