Skip to content

DFE-Digital/pageobjects-testing-infrastructure

Repository files navigation

Who is this library for

To support .NET Developers and Testers in building Web application tests with composable PageObjects;

  • Removing PageObject and Test references to external query libraries with access to them behind an abstraction. (e.g WebDriver, AngleSharp... )

  • Provide common GDS components with default mappings

  • Encourage composition of PageObjects so that different types of tests can use the same PageObjects.

Getting started

  1. Ensure your test project is using dependency injection - DI

  2. Choose a query library for your test project

  3. Register your pages and components in your tests DI using IPageObject

  4. Use library provided Components in your pages with ComponentFactory<TComponent>

  5. Access pages in your tests with IDocumentSessionClient

Choosing and configuring a library to query for you

// TODO document configuration options for each provider; WebDriverSession, AngleSharpParseOptions (AlwaysUseContext, TextReplacement) - needs to be decoratable for retrypolicy

AngleSharp

services.AddAngleSharp(); 
services.AddWebApplicationFactory<TApplicationProgram>(); // TApplicationProgram is your Program class from your .NET Web Application

WebDriver

services.AddWebDriver();

// configure default WebDriverSessionOptions from configuration
services.ConfigureWebDriverSession(t => {
    t.BrowserType = configuration.BrowserType
    t.Headless = configuration.Headless;
    t.BrowserOptions = configuration.BrowserOptions
});

// OR bind options against the WebDriverSessionOptions object
services.Configure<WebDriverSessionOptions>(configuration);
services.AddSingleton<WebDriverSessionOptions>(sp => sp.GetRequireService<IOptions<WebDriverSessionOptions>>.Value); // ensure non-options is registered

Components available to use

ComponentFactory<IComponent> Role: create components with this IComponent Role: marks components available

// example of using library
public sealed class MyPage : IPageObject
{
    ComponentFactory<HeaderComponent> _headerFactory
  // constructor
  public MyPage(ComponentFactory<HeaderComponent> headerFactory)
  {
    _headerFactory = headerFactory;
  }

  // simplest usage exposing the library type to tests
  public HeaderComponent => _headerFactory.Get(... options)
}

The library supports below components (GDS and supporting e.g AnchorLink <a>)

GDS Header

GDS Footer

GDS Button

GDS Checkboxes

GDS Radio

GDS Fieldset

GDS CookieBanner

GDS TextInput

GDS DateInput

  • GDS
  • Library use Fieldset

GDS Tabs

GDS Details

GDS ErrorMessage

GDS ErrorSummary

GDS Pagination

  • GDS
  • Library use AnchorLink

GDS Panel

GDS Select

GDS Table

---END GDS---

TODO ensure all supporting components are referenced here and reside outside of the GDSComponents folder

Anchor Link

Form

Label

Option

Table parts

Adding your own pages

When building your PageObjects you want to:

IPageObject - Role: mark pages with this

IDocumentSessionClient - Role: create pages with this

public sealed class HomePage : IPageObject

Important register your pages and application components in your DI!

    testServices...
    // top level PageObject
    testServices.AddTransient<IPageObject, HomePage>();
    testServices.AddTransient<NavigationBarComponent>(); // reusable application component

public sealed class HomePage
{
    public HomePage(
        NavigationBarComponent navBar,
        SearchComponent search, 
        FilterComponent filter)
    {
        Search = search ?? throw new ArgumentNullException(nameof(search));
        Filter = filter ?? throw new ArgumentNullException(nameof(filter));
        NavBar = navBar ?? throw new ArgumentNullException(nameof(navBar));
    }

    // Reuse these across Pages
    public NavigationBarComponent NavBar { get; }
    public SearchComponent Search { get; }
    public FilterComponent Filter { get; }
}

Use pages in your tests

Note Ensure you have Registered your pages and application components

public sealed class MyTestClass : BaseTest
{

    [Fact]
    public async Task MyTest()
    {
        // create document
        IDocumentSessionClient documentSessionClient = await GetTestService<IDocumentSessionClient>();

        // pageobjects are not coupled to the path request made for a document
        await documentSession.RequestDocumentAsync(
            (t) => t.SetPath("/"));

        // create page from the documentSession
        HomePage page = documentSession.GetPage<HomePage>();
    }
}

Note You could aggregate a/multiple components to your own tests type e.g Facets or SearchResults being made up of multiple GDS components to make your PageObject API more domain centric

// Basic types example
homePage.GetHeading().Should().Be("Heading"); 

public sealed class HomePage
{
    public string GetHeading() => _headingFactory.GetHeadings().Where(t => t.Type == H1).Text;
}
// Library types example

// GDSComponent provided by the library - record to give value-object semantics (immutable, attribute comparisons, no identity)
public record GDSTextInput
{
    public required string Name { get; init; }
    public required string Value { get; init; }
    public required string? PlaceHolder { get; init; } = null;
    public required string? Type { get; init; } = null;
}

// Page
public sealed class HomePage
{
    public HomePage(ComponentFactory<GDSTextInput> inputFactory)
    
    public GDSTextInput GetSearchInput() => inputFactory.Get(...);
}

// Test
[Fact]
public async Test()
{
    GDSTextInput expectedTextInput = new()
    {
        Name = "searchKeyWord",
        Value = "",
        PlaceHolder = "Search by keyword",
        Type = "text"
    };
    homePage.GetSearchInput().Should().Be(expectedTextInput);
}

Setup Dependency Injection

// This uses the Singleton pattern that wraps the DependencyInjection container ensuring a single instance of the container and, for services to be registered via `IServiceCollection`

internal sealed class DependencyInjection
{
    private static readonly DependencyInjection _instance = new();
    private readonly IServiceProvider _serviceProvider;
    static DependencyInjection()
    {
    }

    private DependencyInjection()
    {
        IServiceCollection services = new ServiceCollection()
            // ToAddAngleSharp .AddAngleSharp<Program>();
            // ToAddWebDriver .AddWebDriver();
            //
        services.AddTransient<IPageObject, ApplicationHomePage>();
        services.AddTransient<ApplicationComponent>();
        _serviceProvider = services.BuildServiceProvider();
    }

    public static DependencyInjection Instance
    {
        get
        {
            return _instance;
        }
    }

    internal IServiceScope CreateScope() => _serviceProvider.CreateScope();
}

// Separately you may want to make this Scope started and disposed in a base test class.

public abstract class BaseTest : IDisposable
{
    private readonly IServiceScope _serviceScope;

    protected BaseHttpTest()
    {
        _serviceScope = DependencyInjection.Instance.CreateScope();
    }

    protected T GetTestService<T>()
        => _serviceScope.ServiceProvider.GetService<T>()
            ?? throw new ArgumentNullException($"Unable to resolve type {typeof(T)}");

    public void Dispose()
    {
        GC.SuppressFinalize(this);
        _serviceScope.Dispose();
    }
}

// Any test class you inherit from BaseTest gets access to the container and a new scope is created per test

public sealed class MyTestClass : BaseTest
{
  [Fact]
  public async Task MyTest()
  {
    IDocumentSession documentSession = GetTestService<IDocumentSession>(); // is available
  }
}

About

A library to support composition of PageModels and Querying.

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages