Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
CathLass committed Jul 31, 2024
2 parents 8702c51 + 617287b commit 50ce9aa
Show file tree
Hide file tree
Showing 32 changed files with 1,406 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
jobs:
build:

runs-on: ubuntu-latest
runs-on: windows-latest

steps:
- uses: actions/checkout@v4
Expand Down
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);
}

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();
}
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)"
);
}
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));
}
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);
}
}
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;
}
}
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
Loading

0 comments on commit 50ce9aa

Please sign in to comment.