From 6e1a53de2954f54dd4f859034d9eccdc1c879f45 Mon Sep 17 00:00:00 2001 From: CLAWLOR Date: Thu, 2 Apr 2026 12:56:31 +0100 Subject: [PATCH 1/2] not working - maybe start again. remove Custom factrory and AboutPage tests that use a different setup? --- .../AboutSchoolPageTests.cs | 208 ++++++++++++++++++ .../Infrastructure/BasePageTest.cs | 75 +++++++ .../CustomWebApplicationFactory.cs | 62 ++++++ .../IntegrationTestCollection.cs | 7 + .../TestWebApplicationFactory.cs | 197 +++++++++++++++++ .../WebApplicationSetupFixture.cs | 27 +++ SAPPub.Integration.Tests/PageTests.cs | 58 +++++ .../SAPPub.Integration.Tests.csproj | 34 +++ SAPPub.Integration.Tests/SearchTests.cs | 163 ++++++++++++++ SAPPub.Integration.Tests/UnitTest1.cs | 11 + ...icPerformanceEnglishAndMathsResults.cshtml | 33 ++- SAPPub.sln | 11 +- .../UI/Infrastructure/BasePageTest.cs | 2 +- 13 files changed, 884 insertions(+), 4 deletions(-) create mode 100644 SAPPub.Integration.Tests/AboutSchoolPageTests.cs create mode 100644 SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs create mode 100644 SAPPub.Integration.Tests/Infrastructure/CustomWebApplicationFactory.cs create mode 100644 SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs create mode 100644 SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs create mode 100644 SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs create mode 100644 SAPPub.Integration.Tests/PageTests.cs create mode 100644 SAPPub.Integration.Tests/SAPPub.Integration.Tests.csproj create mode 100644 SAPPub.Integration.Tests/SearchTests.cs create mode 100644 SAPPub.Integration.Tests/UnitTest1.cs diff --git a/SAPPub.Integration.Tests/AboutSchoolPageTests.cs b/SAPPub.Integration.Tests/AboutSchoolPageTests.cs new file mode 100644 index 00000000..c1377654 --- /dev/null +++ b/SAPPub.Integration.Tests/AboutSchoolPageTests.cs @@ -0,0 +1,208 @@ +namespace SAPPub.Integration.Tests; + +[Collection("Integration Tests")] +public class AboutSchoolPageTests(WebApplicationSetupFixture fixture) : BasePageTest(fixture) +{ + private Dictionary _schoolUrnToUrlMap = new Dictionary + { + //["105574"] = "school/105574/Loreto%20High%20School%20Chorlton/secondary/about", + //["137552"] = "school/137552/Stewards%20Academy%20-%20Science%20Specialist%2C%20Harlow/secondary/about", + //["100273"] = "school/100273/Saint%20Paul%20Roman%20Catholic%20Infant%20School/secondary/about", + //["107564"] = "school/107564/Todmorden%20High%20School/secondary/about" + ["105574"] = "school/105574", + ["137552"] = "school/137552", + ["100273"] = "school/100273", + ["107564"] = "school/107564" + + }; + + [Fact] + public async Task AboutSchoolPage_LoadsSuccessfully() + { + // Arrange && Act + var response = await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + + // Assert + Assert.NotNull(response); + Assert.Equal(200, response.Status); + } + + [Fact] + public async Task AboutSchoolPage_HasCorrectTitle() + { + // Arrange + await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + + // Act + var title = await Page.TitleAsync(); + + // Assert + Assert.Contains("About the school", title); + } + + [Fact] + public async Task AboutSchoolPage_DisplaysMainHeading() + { + // Arrange + await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + + // Act + var heading = await Page.Locator("h1").TextContentAsync(); + + // Assert + Assert.NotNull(heading); + Assert.NotEmpty(heading!.Trim()); + } + + [Fact] + public async Task AboutSchoolPage_Displays_SchoolName_Caption() + { + // Arrange + await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + + // Act + var schoolNameCaptionLocator = Page.Locator("#school-name-caption"); + var isVisible = await schoolNameCaptionLocator.IsVisibleAsync(); + var schoolNameCaption = await schoolNameCaptionLocator.TextContentAsync(); + + // Assert + Assert.True(isVisible); + Assert.NotNull(schoolNameCaption); + Assert.Equal("Loreto High School Chorlton", schoolNameCaption); + } + + [Theory] + [InlineData("105574", false, false)] + [InlineData("137552", true, false)] + [InlineData("107564", true, true)] + public async Task AboutSchoolPage_Displays_School_Closed_Info(string urn, bool isSchoolClosed, bool hasSchoolClosedDate) + { + // Act + await Page.GotoAsync(_schoolUrnToUrlMap[urn]); + + // Assert + var schoolClosedCard = Page.GetByTestId("school-closed-custom-card"); + + Assert.Equal(isSchoolClosed, await schoolClosedCard.IsVisibleAsync()); + + if (isSchoolClosed) + { + var value = await schoolClosedCard.Locator("p").TextContentAsync(); + + var expectedText = hasSchoolClosedDate ? "This school closed on 23 March 2025" : "Closed"; + + Assert.NotNull(value); + Assert.Equal(expectedText, value.Trim()); + } + } + + [Theory] + [InlineData("105574", false)] + [InlineData("137552", true)] + public async Task AboutSchoolPage_DisplaysSchoolDetails(string urn, bool trustNameIsExpected) + { + // Act + await Page.GotoAsync(_schoolUrnToUrlMap[urn]); + + // Assert + var detailsSummary = Page.Locator("#school-details-summary"); + + Assert.True(await detailsSummary.IsVisibleAsync()); + var row = detailsSummary + .Locator(".govuk-summary-list__row") + .Filter(new() { Has = Page.Locator(".govuk-summary-list__key", new() { HasText = " Academy Trust " }) }); + + if (trustNameIsExpected) + { + var value = await row.Locator(".govuk-summary-list__value").TextContentAsync(); + Assert.NotNull(value); + Assert.Equal("THE PASSMORES CO-OPERATIVE LEARNING COMMUNITY", value.Trim()); + } + else + { + Assert.False(await row.IsVisibleAsync()); + } + } + + [Fact] + public async Task AboutSchoolPage_DisplaysSchoolLocation() + { + // Arrange + await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + + // Act + var isVisible = await Page.Locator("#school-location-summary").IsVisibleAsync(); + + // Assert + Assert.True(isVisible); + } + + [Fact] + public async Task AboutSchoolPage_DisplaysSpecialistUnit() + { + // Arrange + await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + + // Act + var isVisible = await Page.Locator("#details-sen").IsVisibleAsync(); + + // Assert + Assert.True(isVisible); + } + + [Fact] + public async Task AboutSchoolPage_DisplaysSchoolFeatures() + { + // Arrange + await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + + // Act + var isVisible = await Page.Locator("#school-features-summary").IsVisibleAsync(); + + // Assert + Assert.True(isVisible); + } + + [Fact] + public async Task AboutSchoolPage_DisplaysSchoolPolicies() + { + // Arrange + await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + + // Act + var schoolPoliciesSummaryCard = Page.Locator("#school-policies-summary"); + var contactSchoolInfo = schoolPoliciesSummaryCard.GetByTestId("contact-school-info"); + + // Assert + Assert.True(await schoolPoliciesSummaryCard.IsVisibleAsync()); + Assert.False(await contactSchoolInfo.IsVisibleAsync()); + } + + [Fact] + public async Task AboutSchoolPage_DisplaysSchoolPolicies_ContactSchoolText() + { + // Arrange + await Page.GotoAsync(_schoolUrnToUrlMap["100273"]); + + // Act + var schoolPoliciesSummaryCard = Page.Locator("#school-policies-summary"); + var contactSchoolInfo = schoolPoliciesSummaryCard.GetByTestId("contact-school-info"); + var isVisible = await contactSchoolInfo.IsVisibleAsync(); + + // Assert + Assert.True(isVisible); + } + + [Fact] + public async Task AboutSchoolPage_DisplaysPagination() + { + // Arrange + await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + + // Act + var isVisible = await Page.Locator("#about-the-school-pagination").IsVisibleAsync(); + + // Assert + Assert.True(isVisible); + } +} \ No newline at end of file diff --git a/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs b/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs new file mode 100644 index 00000000..f5e35bae --- /dev/null +++ b/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs @@ -0,0 +1,75 @@ +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; + +namespace SAPPub.Integration.Tests; + +public abstract class BasePageTest : PageTest +{ + private readonly WebApplicationSetupFixture _fixture; + + // ReSharper disable once ConvertToPrimaryConstructor + protected BasePageTest(WebApplicationSetupFixture fixture) + { + _fixture = fixture; + + //Uncomment to run tests in headed mode + Environment.SetEnvironmentVariable("HEADED", "1"); + } + + public override BrowserNewContextOptions ContextOptions() + { + return new BrowserNewContextOptions + { + BaseURL = _fixture.BaseUrl.TrimEnd('/'), + IgnoreHTTPSErrors = true, + ViewportSize = new() { Width = 1280, Height = 720 }, + Locale = "en-GB", + TimezoneId = "Europe/London", + JavaScriptEnabled = true, + }; + } + + public override async Task InitializeAsync() + { + await base.InitializeAsync(); + + Page.SetDefaultTimeout((float)TimeSpan.FromSeconds(60).TotalMilliseconds); + Page.SetDefaultNavigationTimeout((float)TimeSpan.FromSeconds(100).TotalMilliseconds); + } + + public async Task WaitForSearchInputsAsync(int timeoutMs = 5000) + { + var selector = "input[name='__Query'], input[name='Query'][type='hidden'], input[name='Query']"; + await Page.WaitForSelectorAsync(selector, new() { Timeout = timeoutMs }); + await Page.WaitForTimeoutAsync(100); + } + public async Task GetQueryInputLocatorAsync(int checkTimeoutMs = 1000) + { + var jsLocator = Page.Locator("input[name='__Query']"); + try + { + if (await jsLocator.CountAsync() > 0) + { + var isVisible = await jsLocator.IsVisibleAsync(); + if (isVisible) return jsLocator; + } + + var serverLocator = Page.Locator("input[name='Query']"); + if (await serverLocator.CountAsync() > 0) return serverLocator; + + var found = await Page.WaitForSelectorAsync("input[name='__Query'], input[name='Query']", new() { Timeout = checkTimeoutMs }); + if (found != null) + { + var nameAttr = await found.GetAttributeAsync("name"); + if (nameAttr == "__Query") + return Page.Locator("input[name='__Query']"); + return Page.Locator("input[name='Query']"); + } + return Page.Locator("input[name='Query']"); + } + catch + { + return Page.Locator("input[name='Query']"); + } + } +} \ No newline at end of file diff --git a/SAPPub.Integration.Tests/Infrastructure/CustomWebApplicationFactory.cs b/SAPPub.Integration.Tests/Infrastructure/CustomWebApplicationFactory.cs new file mode 100644 index 00000000..33ddfd75 --- /dev/null +++ b/SAPPub.Integration.Tests/Infrastructure/CustomWebApplicationFactory.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using SAPPub.Web; + +namespace SAPPub.Integration.Tests; + +public class CustomWebApplicationFactory : WebApplicationFactory +{ + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + var isCi = string.Equals( + Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), + "true", + StringComparison.OrdinalIgnoreCase); + + // Use a dedicated env name for CI to avoid accidentally using real infra. + builder.UseEnvironment(isCi ? "UITests" : "Development"); + + builder.ConfigureAppConfiguration((context, config) => + { + // Ensure we read appsettings + appsettings.{ENV}.json + env vars + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: false) + .AddEnvironmentVariables(); + + // guarantee a connection string exists (but it can be dummy). + //Port = 1 makes it effectively unreachable if anything accidentally tries to connect. + //config.AddInMemoryCollection(new Dictionary + //{ + // ["ConnectionStrings:PostgresConnectionString"] = + // "Host=127.0.0.1;Port=1;Database=x;Username=x;Password=x;Timeout=1;Command Timeout=1" + //}); + }); + + //builder.ConfigureServices((context, services) => + //{ + // services.RemoveAll(typeof(IGenericRepository<>)); + // services.AddTransient(typeof(IGenericRepository<>), typeof(FakeGenericRepository<>)); + + // // BUT still register NpgsqlDataSource so DI smoke tests pass + // services.RemoveAll(); + // services.AddSingleton(sp => + // { + // var cfg = sp.GetRequiredService(); + // var cs = cfg.GetConnectionString("PostgresConnectionString") + // ?? throw new InvalidOperationException("Connection string 'PostgresConnectionString' is not configured."); + + // return NpgsqlDataSource.Create(cs); + // }); + + // return; + //}); + } + + protected override IHost CreateHost(IHostBuilder builder) + { + builder.UseContentRoot(Directory.GetCurrentDirectory()); + return base.CreateHost(builder); + } +} diff --git a/SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs b/SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs new file mode 100644 index 00000000..77a9c08a --- /dev/null +++ b/SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs @@ -0,0 +1,7 @@ +namespace SAPPub.Integration.Tests; + +[CollectionDefinition("Integration Tests", DisableParallelization = true)] +public class IntegrationTestCollection : ICollectionFixture +{ +} + diff --git a/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs b/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs new file mode 100644 index 00000000..0f86f99f --- /dev/null +++ b/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs @@ -0,0 +1,197 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using SAPPub.Core.Interfaces.Repositories.Generic; +using SAPPub.Web; +using SAPPub.Web.Helpers; + +namespace SAPPub.Integration.Tests; + +public class TestWebApplicationFactory : WebApplicationFactory +{ + private IHost? _host; + private static string? _cachedWebProjectPath; + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.UseUrls("http://127.0.0.1:0", "https://127.0.0.1:0"); + builder.UseEnvironment("UITests"); + + // Set content root to web project so static files (wwwroot) are found + var webProjectPath = GetWebProjectPath(); + builder.UseContentRoot(webProjectPath); + builder.UseWebRoot(Path.Combine(webProjectPath, "wwwroot")); + + var testDataFilePath = GetTestDataFilePath(); + var configurationValues = CreateConfigurationValues(testDataFilePath); + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configurationValues) + .Build(); + + builder + .UseConfiguration(configuration) + .ConfigureAppConfiguration(configurationBuilder => + { + configurationBuilder.AddInMemoryCollection(configurationValues); + }) + .ConfigureServices(services => + { + services.RemoveAll(typeof(IGenericRepository<>)); + services.AddSingleton(typeof(IGenericRepository<>), typeof(FakeGenericRepository<>)); + }); + } + + protected override IHost CreateHost(IHostBuilder builder) + { + // Use web project path for content root (for static files) + var webProjectPath = GetWebProjectPath(); + builder.UseContentRoot(webProjectPath); + + // Create the host for TestServer + var testHost = builder.Build(); + + // Modify the host builder to use Kestrel with correct content root + builder.ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseKestrel(); + webHostBuilder.UseContentRoot(webProjectPath); + webHostBuilder.UseWebRoot(Path.Combine(webProjectPath, "wwwroot")); + }); + + // Create and start the Kestrel server + _host = builder.Build(); + _host.Start(); + + // Extract the selected dynamic port + var server = _host.Services.GetRequiredService(); + var addresses = server.Features.Get(); + + ClientOptions.BaseAddress = addresses!.Addresses + .Select(x => new Uri(x)) + .Last(); + + LogStartupInfo(webProjectPath); + + testHost.Start(); + return testHost; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _host?.Dispose(); + } + base.Dispose(disposing); + } + + #region Path Resolution + + private static string GetWebProjectPath() + { + // Cache the path to avoid repeated file system walks + if (_cachedWebProjectPath != null) + { + return _cachedWebProjectPath; + } + + var currentDir = AppContext.BaseDirectory; + var directory = new DirectoryInfo(currentDir); + + // Walk up to find solution root + while (directory != null && !directory.GetFiles("*.sln").Any()) + { + directory = directory.Parent; + } + + if (directory == null) + { + throw new InvalidOperationException( + $"Could not find solution root from {currentDir}"); + } + + // Find web project + var possiblePaths = new[] + { + Path.Combine(directory.FullName, "SAPPub.Web"), + Path.Combine(directory.FullName, "src", "SAPPub.Web"), + }; + + _cachedWebProjectPath = possiblePaths.FirstOrDefault(Directory.Exists) + ?? throw new InvalidOperationException( + $"Could not find SAPPub.Web. Searched: {string.Join(", ", possiblePaths)}"); + + return _cachedWebProjectPath; + } + + private static string GetTestDataFilePath() + { + // First try test project's TestData folder + var testProjectPath = Path.Combine( + AppContext.BaseDirectory, + "UI", + "TestData", + "Establishments-UI-Test-Data.csv"); + + if (File.Exists(testProjectPath)) + { + return testProjectPath; + } + + // Try web project's TestData folder + var webProjectPath = Path.Combine( + GetWebProjectPath(), + "UI", + "TestData", + "Establishments-UI-Test-Data.csv"); + + if (File.Exists(webProjectPath)) + { + return webProjectPath; + } + + throw new FileNotFoundException( + $"Test data file not found. Searched:\n- {testProjectPath}\n- {webProjectPath}"); + } + + #endregion + + #region Configuration + + private static Dictionary CreateConfigurationValues(string testDataFilePath) + { + return new Dictionary + { + { "Establishments:CsvPath", testDataFilePath }, + }; + } + + #endregion + + #region Logging + + private void LogStartupInfo(string webProjectPath) + { + var wwwrootPath = Path.Combine(webProjectPath, "wwwroot"); + var jsPath = Path.Combine(wwwrootPath, "js"); + + if (Directory.Exists(jsPath)) + { + var jsFiles = Directory.GetFiles(jsPath, "*.js", SearchOption.AllDirectories); + // Check for key files + var autocomplete = Path.Combine(jsPath, "accessible-autocomplete.min.js"); + var debounce = Path.Combine(jsPath, "lodash.debounce", "index.js"); + + } + else + { + Console.WriteLine($"❌ JS folder not found: {jsPath}"); + } + } + + #endregion +} \ No newline at end of file diff --git a/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs b/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs new file mode 100644 index 00000000..a1b16618 --- /dev/null +++ b/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs @@ -0,0 +1,27 @@ +namespace SAPPub.Integration.Tests; + +public class WebApplicationSetupFixture : IAsyncLifetime +{ + private TestWebApplicationFactory? _factory; + + public string BaseUrl { get; private set; } = null!; + + public Task InitializeAsync() + { + _factory = new TestWebApplicationFactory(); + + if (_factory.Server == null) throw new InvalidOperationException("Test Server not started"); + + BaseUrl = _factory.ClientOptions.BaseAddress.ToString(); + + return Task.CompletedTask; + } + + public async Task DisposeAsync() + { + if (_factory != null) + { + await _factory.DisposeAsync(); + } + } +} diff --git a/SAPPub.Integration.Tests/PageTests.cs b/SAPPub.Integration.Tests/PageTests.cs new file mode 100644 index 00000000..23877812 --- /dev/null +++ b/SAPPub.Integration.Tests/PageTests.cs @@ -0,0 +1,58 @@ +using System.Net; + +namespace SAPPub.Integration.Tests; + +public class PageTests : IClassFixture +{ + private readonly HttpClient _client; + public PageTests(CustomWebApplicationFactory factory) + { + _client = factory.CreateClient(); + } + + [Fact] + public async Task HomePage_ReturnsSuccess() + { + // Act + var response = await _client.GetAsync("/"); + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("text/html", response.Content.Headers.ContentType?.MediaType); + } + + [Theory] + [InlineData("/")] + [InlineData("/health")] + [InlineData("/healthcheck")] + public async Task CommonPages_ReturnSuccess(string url) + { + // Act + var response = await _client.GetAsync(url); + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task Response_ContainsSecurityHeaders() + { + // Act + var response = await _client.GetAsync("/"); + // Assert + Assert.True(response.Headers.Contains("X-Content-Type-Options")); + Assert.True(response.Headers.Contains("X-Frame-Options")); + Assert.True(response.Headers.Contains("Referrer-Policy")); + var xFrameOptions = response.Headers.GetValues("X-Frame-Options").First(); + Assert.Equal("DENY", xFrameOptions); + } + + [Fact] + public async Task Response_ContainsContentSecurityPolicy() + { + // Act + var response = await _client.GetAsync("/"); + // Assert + Assert.True(response.Headers.Contains("Content-Security-Policy")); + var csp = response.Headers.GetValues("Content-Security-Policy").First(); + Assert.Contains("default-src 'self'", csp); + } +} \ No newline at end of file diff --git a/SAPPub.Integration.Tests/SAPPub.Integration.Tests.csproj b/SAPPub.Integration.Tests/SAPPub.Integration.Tests.csproj new file mode 100644 index 00000000..5e359e59 --- /dev/null +++ b/SAPPub.Integration.Tests/SAPPub.Integration.Tests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + diff --git a/SAPPub.Integration.Tests/SearchTests.cs b/SAPPub.Integration.Tests/SearchTests.cs new file mode 100644 index 00000000..0c8cf02f --- /dev/null +++ b/SAPPub.Integration.Tests/SearchTests.cs @@ -0,0 +1,163 @@ +using Microsoft.Playwright; + +namespace SAPPub.Integration.Tests; + +[Collection("Integration Tests")] +public class SearchTests(WebApplicationSetupFixture fixture) : BasePageTest(fixture) +{ + private string _pageUrl = "search"; + + [Fact] + public async Task SearchPage_LoadsSuccessfully() + { + // Arrange && Act + var response = await Page.GotoAsync(_pageUrl); + + // Assert + Assert.NotNull(response); + Assert.Equal(200, response.Status); + } + + [Fact] + public async Task SearchPage_EnterSchoolName_ShowsViewWithResults() + { + // Arrange + var searchTerm = "durham"; + var response = await Page.GotoAsync(_pageUrl); + + // Act + await Page.FillAsync("#NameSearchTerm", searchTerm); + await Page.ClickAsync("#search"); + await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // Assert + Assert.NotNull(response); + Assert.Equal(200, response.Status); + // assert text box contains search term + var searchBoxValue = await Page.InputValueAsync("#NameSearchTerm"); + Assert.Equal(searchTerm, searchBoxValue); + + // assert that at least one search result is displayed + var rows = Page.Locator(".govuk-summary-list .govuk-summary-list__row"); + var rowHandles = await rows.ElementHandlesAsync(); + int count = await rows.CountAsync(); + Assert.True(count > 0, "Expected at least one search result, but found none."); + } + + [Fact] + public async Task SearchPage_EnterSchoolName_NoResults_ShowsViewWithNoResults() + { + // Arrange + var searchTerm = "xyz"; + var response = await Page.GotoAsync(_pageUrl); + + // Act + await Page.FillAsync("#NameSearchTerm", searchTerm); + await Page.ClickAsync("#search"); + await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // Assert + Assert.NotNull(response); + Assert.Equal(200, response.Status); + // assert text box contains search term + var searchBoxValue = await Page.InputValueAsync("#NameSearchTerm "); + Assert.Equal(searchTerm, searchBoxValue); + + // assert no list items are displayed + var rows = Page.Locator(".govuk-summary-list .govuk-summary-list__row"); + var rowHandles = await rows.ElementHandlesAsync(); + int count = await rows.CountAsync(); + Assert.True(count == 0, "Expected no search results, but found some."); + + // assert that the no results message is displayed + var noResultsMessage = await Page.Locator("[data-testid='no-results-heading']").InnerTextAsync(); + Assert.Contains("Try another search", noResultsMessage); + } + + [Fact] + public async Task SearchPage_EnterValidPostcode_ShowsViewWithResults() + { + // Arrange + var searchTerm = "M21 7SW"; + var response = await Page.GotoAsync(_pageUrl); + + // Act + await Page.FillAsync("#LocationSearchTerm", searchTerm); + await Page.ClickAsync("#search"); + await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // Assert + Assert.NotNull(response); + Assert.Equal(200, response.Status); + // assert text box contains search term + var searchBoxValue = await Page.InputValueAsync("#LocationSearchTerm"); + Assert.Equal(searchTerm, searchBoxValue); + + // assert that at least one search result is displayed + var rows = Page.Locator(".govuk-summary-list .govuk-summary-list__row"); + var rowHandles = await rows.ElementHandlesAsync(); + int count = await rows.CountAsync(); + Assert.True(count > 0, "Expected at least one search result, but found none."); + } + + [Fact] + public async Task SearchPage_EnterInvalidPostcode_ShowsViewWithErrorMessge() + { + // Arrange + var searchTerm = "NE1"; + var response = await Page.GotoAsync(_pageUrl); + + // Act + await Page.FillAsync("#LocationSearchTerm", searchTerm); + await Page.ClickAsync("#search"); + await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // Assert + Assert.NotNull(response); + Assert.Equal(200, response.Status); + // assert text box contains search term + var searchBoxValue = await Page.InputValueAsync("#LocationSearchTerm"); + Assert.Equal(searchTerm, searchBoxValue); + + // error box is displayed with correct message + var errorLink = Page.Locator(".govuk-error-summary__list a[href='#LocationSearchTerm']"); + Assert.Equal("Enter a full postcode", await errorLink.InnerTextAsync()); + + // assert no list items are displayed + var rows = Page.Locator(".govuk-summary-list .govuk-summary-list__row"); + var rowHandles = await rows.ElementHandlesAsync(); + int count = await rows.CountAsync(); + Assert.True(count == 0, "Expected no search results, but found some."); + } + + [Fact] + public async Task SearchPage_Enter_Closed_SchoolName_ShowsViewWithResults() + { + // Arrange + var searchTerm = "Todmorden High School"; + var response = await Page.GotoAsync(_pageUrl); + + // Act + await Page.FillAsync("#NameSearchTerm", searchTerm); + await Page.ClickAsync("#search"); + await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // Assert + Assert.NotNull(response); + Assert.Equal(200, response.Status); + // assert text box contains search term + var searchBoxValue = await Page.InputValueAsync("#NameSearchTerm"); + Assert.Equal(searchTerm, searchBoxValue); + + // assert that at least one search result is displayed + var rows = Page.GetByTestId("school-closed-tag"); + + var rowHandles = await rows.ElementHandlesAsync(); + int count = await rows.CountAsync(); + Assert.True(count == 1, "Expected one closed school, but found more than one."); + + var value = await rows.First.TextContentAsync(); + Assert.NotNull(value); + Assert.Equal("Closed in March 2025", value.Trim()); + } +} diff --git a/SAPPub.Integration.Tests/UnitTest1.cs b/SAPPub.Integration.Tests/UnitTest1.cs new file mode 100644 index 00000000..e2db4097 --- /dev/null +++ b/SAPPub.Integration.Tests/UnitTest1.cs @@ -0,0 +1,11 @@ +namespace SAPPub.Integration.Tests +{ + public class UnitTest1 + { + [Fact] + public void Test1() + { + + } + } +} \ No newline at end of file diff --git a/SAPPub.Web/Views/SecondarySchool/AcademicPerformanceEnglishAndMathsResults.cshtml b/SAPPub.Web/Views/SecondarySchool/AcademicPerformanceEnglishAndMathsResults.cshtml index afd65595..d2109f52 100644 --- a/SAPPub.Web/Views/SecondarySchool/AcademicPerformanceEnglishAndMathsResults.cshtml +++ b/SAPPub.Web/Views/SecondarySchool/AcademicPerformanceEnglishAndMathsResults.cshtml @@ -91,6 +91,8 @@ @(Model.SelectedGrade == GcseGradeDataSelection.Grade5AndAbove ? "Grade 5 is comparable to the top of the old grade C." : "The bottom of grade 4 is comparable to the bottom of the old grade C")

+ @if(false) + { @@ -170,7 +172,36 @@
- + + } + else + { +
+ + + + + @foreach (var label in Model.AllGcseOverTimeData.Labels) + { + + } + + + + @foreach (var dataset in Model.AllGcseOverTimeData.Datasets.Select((value, i) => (value, i))) + { + + + @foreach (var data in dataset.value.Data) + { + + } + + } + +
@label
@dataset.value.Label@data%
+
+ }
diff --git a/SAPPub.sln b/SAPPub.sln index 44933707..6c972bff 100644 --- a/SAPPub.sln +++ b/SAPPub.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.1.11312.151 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.37027.9 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAPPub.Web", "SAPPub.Web\SAPPub.Web.csproj", "{24624574-4DE4-4A91-8273-7AE3EEE13537}" EndProject @@ -28,6 +28,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAPData", "SAPData\SAPData. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAPData.Unit.Tests", "SAPData.Tests.Unit\SAPData.Unit.Tests.csproj", "{2FD6F8C0-9D97-7714-4445-AD6E47BEB671}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAPPub.Integration.Tests", "SAPPub.Integration.Tests\SAPPub.Integration.Tests.csproj", "{F638C830-CD9C-47A3-8763-33611A98CD3B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -66,6 +68,10 @@ Global {2FD6F8C0-9D97-7714-4445-AD6E47BEB671}.Debug|Any CPU.Build.0 = Debug|Any CPU {2FD6F8C0-9D97-7714-4445-AD6E47BEB671}.Release|Any CPU.ActiveCfg = Release|Any CPU {2FD6F8C0-9D97-7714-4445-AD6E47BEB671}.Release|Any CPU.Build.0 = Release|Any CPU + {F638C830-CD9C-47A3-8763-33611A98CD3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F638C830-CD9C-47A3-8763-33611A98CD3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F638C830-CD9C-47A3-8763-33611A98CD3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F638C830-CD9C-47A3-8763-33611A98CD3B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -75,6 +81,7 @@ Global {EB1D1EAE-E264-4FF5-ACB9-8614FD694C69} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {5F164DC3-0185-4D92-B689-7A004F81C232} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {2FD6F8C0-9D97-7714-4445-AD6E47BEB671} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {F638C830-CD9C-47A3-8763-33611A98CD3B} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0D7E6B09-2E5D-4BA4-899B-FBCC5A7F92CD} diff --git a/Tests/SAPPub.Web.Tests/UI/Infrastructure/BasePageTest.cs b/Tests/SAPPub.Web.Tests/UI/Infrastructure/BasePageTest.cs index d571a7a7..ff1c83a9 100644 --- a/Tests/SAPPub.Web.Tests/UI/Infrastructure/BasePageTest.cs +++ b/Tests/SAPPub.Web.Tests/UI/Infrastructure/BasePageTest.cs @@ -13,7 +13,7 @@ protected BasePageTest(WebApplicationSetupFixture fixture) _fixture = fixture; //Uncomment to run tests in headed mode - //Environment.SetEnvironmentVariable("HEADED", "1"); + Environment.SetEnvironmentVariable("HEADED", "1"); } public override BrowserNewContextOptions ContextOptions() From 479f408aff8bdfe9a3cb563bc36d3f4b5c9a104f Mon Sep 17 00:00:00 2001 From: CLAWLOR Date: Thu, 9 Apr 2026 18:50:04 +0100 Subject: [PATCH 2/2] first tests - wip --- .github/workflows/build-and-deploy.yml | 67 +++++++ .../CustomWebApplicationFactory.cs | 62 ------- SAPPub.Integration.Tests/PageTests.cs | 58 ------- SAPPub.Integration.Tests/SearchTests.cs | 163 ------------------ SAPPub.Integration.Tests/UnitTest1.cs | 11 -- SAPPub.Web/Program.cs | 10 +- SAPPub.Web/appsettings.Development.json | 5 +- SAPPub.sln | 12 +- .../Infrastructure/BasePageTest.cs | 6 +- .../IntegrationTestCollection.cs | 0 .../TestWebApplicationFactory.cs | 62 ++++--- .../WebApplicationSetupFixture.cs | 7 + .../SAPPub.IntegrationTests.csproj | 10 +- .../AboutSchoolPageTests.cs | 98 +++-------- .../AttainmentPageTests.cs | 78 +++++++++ 15 files changed, 237 insertions(+), 412 deletions(-) delete mode 100644 SAPPub.Integration.Tests/Infrastructure/CustomWebApplicationFactory.cs delete mode 100644 SAPPub.Integration.Tests/PageTests.cs delete mode 100644 SAPPub.Integration.Tests/SearchTests.cs delete mode 100644 SAPPub.Integration.Tests/UnitTest1.cs rename {SAPPub.Integration.Tests => Tests/SAPPub.Integration.Tests}/Infrastructure/BasePageTest.cs (95%) rename {SAPPub.Integration.Tests => Tests/SAPPub.Integration.Tests}/Infrastructure/IntegrationTestCollection.cs (100%) rename {SAPPub.Integration.Tests => Tests/SAPPub.Integration.Tests}/Infrastructure/TestWebApplicationFactory.cs (73%) rename {SAPPub.Integration.Tests => Tests/SAPPub.Integration.Tests}/Infrastructure/WebApplicationSetupFixture.cs (74%) rename SAPPub.Integration.Tests/SAPPub.Integration.Tests.csproj => Tests/SAPPub.Integration.Tests/SAPPub.IntegrationTests.csproj (84%) rename {SAPPub.Integration.Tests => Tests/SAPPub.Integration.Tests/SecondarySchoolTests}/AboutSchoolPageTests.cs (50%) create mode 100644 Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AttainmentPageTests.cs diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 1a34972f..b0bd8295 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -514,6 +514,73 @@ jobs: smoke-test: false service: ${{ vars.TEAMS_MSG_SERVICE_NAME }} + post-deploy-tests: + name: Run Integration Tests on Deployed Test Environment + needs: deploy + if: ${{ matrix.environment == 'test' }} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + # Set up .NET + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore dependencies + run: dotnet restore + - name: Install Playwright browsers (.NET) + run: pwsh ./Tests/SAPPub.Integration.Tests/bin/Release/net8.0/playwright.ps1 install + + - name: Run Integration Tests with Coverage + env: + HEADED: 0 + PLAYWRIGHT_IGNORE_HTTPS_ERRORS: true + ASPNETCORE_ENVIRONMENT: CI + run: + dotnet test Tests/SAPPub.Integration.Tests/SAPPub.IntegrationTests.csproj \ + --no-build \ + --configuration Release \ + --results-directory "TestResults" \ + --logger "trx;LogFileName=$(basename "$proj" .csproj).trx" \ + --collect "XPlat Code Coverage"\ + /p:CollectCoverage=true \ + /p:CoverletOutputFormat=cobertura \ + /p:DeterministicReport=true \ + /p:UseSourceLink=true \ + /p:Exclude='[*]AspNetCoreGeneratedDocument.*%2c[*]AspNetCore.Views.*' \ + /p:ExcludeByAttribute='GeneratedCodeAttribute*%2cObsoleteAttribute*%2cExcludeFromCodeCoverage' \ + --verbosity normal + done + + - name: Upload Test Result Files + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: "**/TestResults/**/*" + retention-days: 5 + + - name: Upload UI artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: ui-test-artifacts + path: | + **/test-artifacts/screenshots/** + **/test-artifacts/videos/** + **/test-artifacts/traces/** + + - name: Publish Test Results + uses: dorny/test-reporter@v2 + if: always() + with: + reporter: 'dotnet-trx' + path: "**/TestResults/**/*.trx" + name: 'Test Results' + # --------------------------- # MANUAL DEPLOY # --------------------------- diff --git a/SAPPub.Integration.Tests/Infrastructure/CustomWebApplicationFactory.cs b/SAPPub.Integration.Tests/Infrastructure/CustomWebApplicationFactory.cs deleted file mode 100644 index 33ddfd75..00000000 --- a/SAPPub.Integration.Tests/Infrastructure/CustomWebApplicationFactory.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using SAPPub.Web; - -namespace SAPPub.Integration.Tests; - -public class CustomWebApplicationFactory : WebApplicationFactory -{ - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - var isCi = string.Equals( - Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), - "true", - StringComparison.OrdinalIgnoreCase); - - // Use a dedicated env name for CI to avoid accidentally using real infra. - builder.UseEnvironment(isCi ? "UITests" : "Development"); - - builder.ConfigureAppConfiguration((context, config) => - { - // Ensure we read appsettings + appsettings.{ENV}.json + env vars - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: false) - .AddEnvironmentVariables(); - - // guarantee a connection string exists (but it can be dummy). - //Port = 1 makes it effectively unreachable if anything accidentally tries to connect. - //config.AddInMemoryCollection(new Dictionary - //{ - // ["ConnectionStrings:PostgresConnectionString"] = - // "Host=127.0.0.1;Port=1;Database=x;Username=x;Password=x;Timeout=1;Command Timeout=1" - //}); - }); - - //builder.ConfigureServices((context, services) => - //{ - // services.RemoveAll(typeof(IGenericRepository<>)); - // services.AddTransient(typeof(IGenericRepository<>), typeof(FakeGenericRepository<>)); - - // // BUT still register NpgsqlDataSource so DI smoke tests pass - // services.RemoveAll(); - // services.AddSingleton(sp => - // { - // var cfg = sp.GetRequiredService(); - // var cs = cfg.GetConnectionString("PostgresConnectionString") - // ?? throw new InvalidOperationException("Connection string 'PostgresConnectionString' is not configured."); - - // return NpgsqlDataSource.Create(cs); - // }); - - // return; - //}); - } - - protected override IHost CreateHost(IHostBuilder builder) - { - builder.UseContentRoot(Directory.GetCurrentDirectory()); - return base.CreateHost(builder); - } -} diff --git a/SAPPub.Integration.Tests/PageTests.cs b/SAPPub.Integration.Tests/PageTests.cs deleted file mode 100644 index 23877812..00000000 --- a/SAPPub.Integration.Tests/PageTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Net; - -namespace SAPPub.Integration.Tests; - -public class PageTests : IClassFixture -{ - private readonly HttpClient _client; - public PageTests(CustomWebApplicationFactory factory) - { - _client = factory.CreateClient(); - } - - [Fact] - public async Task HomePage_ReturnsSuccess() - { - // Act - var response = await _client.GetAsync("/"); - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("text/html", response.Content.Headers.ContentType?.MediaType); - } - - [Theory] - [InlineData("/")] - [InlineData("/health")] - [InlineData("/healthcheck")] - public async Task CommonPages_ReturnSuccess(string url) - { - // Act - var response = await _client.GetAsync(url); - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task Response_ContainsSecurityHeaders() - { - // Act - var response = await _client.GetAsync("/"); - // Assert - Assert.True(response.Headers.Contains("X-Content-Type-Options")); - Assert.True(response.Headers.Contains("X-Frame-Options")); - Assert.True(response.Headers.Contains("Referrer-Policy")); - var xFrameOptions = response.Headers.GetValues("X-Frame-Options").First(); - Assert.Equal("DENY", xFrameOptions); - } - - [Fact] - public async Task Response_ContainsContentSecurityPolicy() - { - // Act - var response = await _client.GetAsync("/"); - // Assert - Assert.True(response.Headers.Contains("Content-Security-Policy")); - var csp = response.Headers.GetValues("Content-Security-Policy").First(); - Assert.Contains("default-src 'self'", csp); - } -} \ No newline at end of file diff --git a/SAPPub.Integration.Tests/SearchTests.cs b/SAPPub.Integration.Tests/SearchTests.cs deleted file mode 100644 index 0c8cf02f..00000000 --- a/SAPPub.Integration.Tests/SearchTests.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Microsoft.Playwright; - -namespace SAPPub.Integration.Tests; - -[Collection("Integration Tests")] -public class SearchTests(WebApplicationSetupFixture fixture) : BasePageTest(fixture) -{ - private string _pageUrl = "search"; - - [Fact] - public async Task SearchPage_LoadsSuccessfully() - { - // Arrange && Act - var response = await Page.GotoAsync(_pageUrl); - - // Assert - Assert.NotNull(response); - Assert.Equal(200, response.Status); - } - - [Fact] - public async Task SearchPage_EnterSchoolName_ShowsViewWithResults() - { - // Arrange - var searchTerm = "durham"; - var response = await Page.GotoAsync(_pageUrl); - - // Act - await Page.FillAsync("#NameSearchTerm", searchTerm); - await Page.ClickAsync("#search"); - await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); - - // Assert - Assert.NotNull(response); - Assert.Equal(200, response.Status); - // assert text box contains search term - var searchBoxValue = await Page.InputValueAsync("#NameSearchTerm"); - Assert.Equal(searchTerm, searchBoxValue); - - // assert that at least one search result is displayed - var rows = Page.Locator(".govuk-summary-list .govuk-summary-list__row"); - var rowHandles = await rows.ElementHandlesAsync(); - int count = await rows.CountAsync(); - Assert.True(count > 0, "Expected at least one search result, but found none."); - } - - [Fact] - public async Task SearchPage_EnterSchoolName_NoResults_ShowsViewWithNoResults() - { - // Arrange - var searchTerm = "xyz"; - var response = await Page.GotoAsync(_pageUrl); - - // Act - await Page.FillAsync("#NameSearchTerm", searchTerm); - await Page.ClickAsync("#search"); - await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); - - // Assert - Assert.NotNull(response); - Assert.Equal(200, response.Status); - // assert text box contains search term - var searchBoxValue = await Page.InputValueAsync("#NameSearchTerm "); - Assert.Equal(searchTerm, searchBoxValue); - - // assert no list items are displayed - var rows = Page.Locator(".govuk-summary-list .govuk-summary-list__row"); - var rowHandles = await rows.ElementHandlesAsync(); - int count = await rows.CountAsync(); - Assert.True(count == 0, "Expected no search results, but found some."); - - // assert that the no results message is displayed - var noResultsMessage = await Page.Locator("[data-testid='no-results-heading']").InnerTextAsync(); - Assert.Contains("Try another search", noResultsMessage); - } - - [Fact] - public async Task SearchPage_EnterValidPostcode_ShowsViewWithResults() - { - // Arrange - var searchTerm = "M21 7SW"; - var response = await Page.GotoAsync(_pageUrl); - - // Act - await Page.FillAsync("#LocationSearchTerm", searchTerm); - await Page.ClickAsync("#search"); - await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); - - // Assert - Assert.NotNull(response); - Assert.Equal(200, response.Status); - // assert text box contains search term - var searchBoxValue = await Page.InputValueAsync("#LocationSearchTerm"); - Assert.Equal(searchTerm, searchBoxValue); - - // assert that at least one search result is displayed - var rows = Page.Locator(".govuk-summary-list .govuk-summary-list__row"); - var rowHandles = await rows.ElementHandlesAsync(); - int count = await rows.CountAsync(); - Assert.True(count > 0, "Expected at least one search result, but found none."); - } - - [Fact] - public async Task SearchPage_EnterInvalidPostcode_ShowsViewWithErrorMessge() - { - // Arrange - var searchTerm = "NE1"; - var response = await Page.GotoAsync(_pageUrl); - - // Act - await Page.FillAsync("#LocationSearchTerm", searchTerm); - await Page.ClickAsync("#search"); - await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); - - // Assert - Assert.NotNull(response); - Assert.Equal(200, response.Status); - // assert text box contains search term - var searchBoxValue = await Page.InputValueAsync("#LocationSearchTerm"); - Assert.Equal(searchTerm, searchBoxValue); - - // error box is displayed with correct message - var errorLink = Page.Locator(".govuk-error-summary__list a[href='#LocationSearchTerm']"); - Assert.Equal("Enter a full postcode", await errorLink.InnerTextAsync()); - - // assert no list items are displayed - var rows = Page.Locator(".govuk-summary-list .govuk-summary-list__row"); - var rowHandles = await rows.ElementHandlesAsync(); - int count = await rows.CountAsync(); - Assert.True(count == 0, "Expected no search results, but found some."); - } - - [Fact] - public async Task SearchPage_Enter_Closed_SchoolName_ShowsViewWithResults() - { - // Arrange - var searchTerm = "Todmorden High School"; - var response = await Page.GotoAsync(_pageUrl); - - // Act - await Page.FillAsync("#NameSearchTerm", searchTerm); - await Page.ClickAsync("#search"); - await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); - - // Assert - Assert.NotNull(response); - Assert.Equal(200, response.Status); - // assert text box contains search term - var searchBoxValue = await Page.InputValueAsync("#NameSearchTerm"); - Assert.Equal(searchTerm, searchBoxValue); - - // assert that at least one search result is displayed - var rows = Page.GetByTestId("school-closed-tag"); - - var rowHandles = await rows.ElementHandlesAsync(); - int count = await rows.CountAsync(); - Assert.True(count == 1, "Expected one closed school, but found more than one."); - - var value = await rows.First.TextContentAsync(); - Assert.NotNull(value); - Assert.Equal("Closed in March 2025", value.Trim()); - } -} diff --git a/SAPPub.Integration.Tests/UnitTest1.cs b/SAPPub.Integration.Tests/UnitTest1.cs deleted file mode 100644 index e2db4097..00000000 --- a/SAPPub.Integration.Tests/UnitTest1.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace SAPPub.Integration.Tests -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - - } - } -} \ No newline at end of file diff --git a/SAPPub.Web/Program.cs b/SAPPub.Web/Program.cs index b09328f2..2e8decca 100644 --- a/SAPPub.Web/Program.cs +++ b/SAPPub.Web/Program.cs @@ -85,12 +85,14 @@ public static void Main(string[] args) // Only required for real runtime environments if (builder.Environment.IsDevelopment() || builder.Environment.IsProduction() || builder.Environment.IsStaging()) throw new InvalidOperationException("Connection string 'PostgresConnectionString' is not configured."); - - // For Testing/UITests: use a harmless dummy so nothing accidentally connects - connectionString = "Host=127.0.0.1;Port=1;Database=x;Username=x;Password=x;Timeout=1;Command Timeout=1"; } - builder.Services.AddSingleton(_ => NpgsqlDataSource.Create(connectionString)); + builder.Services.AddSingleton(_ => + { + var builder = new NpgsqlDataSourceBuilder(connectionString); + builder.EnableParameterLogging(); + return builder.Build(); + }); builder.Services.AddDependencies(builder.Environment, builder.Configuration); builder.Services.AddLuceneDependencies(); diff --git a/SAPPub.Web/appsettings.Development.json b/SAPPub.Web/appsettings.Development.json index 027d3798..37653a82 100644 --- a/SAPPub.Web/appsettings.Development.json +++ b/SAPPub.Web/appsettings.Development.json @@ -6,6 +6,9 @@ } }, "ConnectionStrings": { - "PostgresConnectionString": "Host=localhost;Port=5432;Database=SAPData;Username=postgres;Password=postgres" + "PostgresConnectionString": "" + }, + "Playwright": { + "Headed": true } } diff --git a/SAPPub.sln b/SAPPub.sln index 6c972bff..63df417f 100644 --- a/SAPPub.sln +++ b/SAPPub.sln @@ -28,7 +28,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAPData", "SAPData\SAPData. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAPData.Unit.Tests", "SAPData.Tests.Unit\SAPData.Unit.Tests.csproj", "{2FD6F8C0-9D97-7714-4445-AD6E47BEB671}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAPPub.Integration.Tests", "SAPPub.Integration.Tests\SAPPub.Integration.Tests.csproj", "{F638C830-CD9C-47A3-8763-33611A98CD3B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAPPub.IntegrationTests", "Tests\SAPPub.Integration.Tests\SAPPub.IntegrationTests.csproj", "{D90BBB38-FA34-5ABD-F17F-F30AF1D84C6A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -68,10 +68,10 @@ Global {2FD6F8C0-9D97-7714-4445-AD6E47BEB671}.Debug|Any CPU.Build.0 = Debug|Any CPU {2FD6F8C0-9D97-7714-4445-AD6E47BEB671}.Release|Any CPU.ActiveCfg = Release|Any CPU {2FD6F8C0-9D97-7714-4445-AD6E47BEB671}.Release|Any CPU.Build.0 = Release|Any CPU - {F638C830-CD9C-47A3-8763-33611A98CD3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F638C830-CD9C-47A3-8763-33611A98CD3B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F638C830-CD9C-47A3-8763-33611A98CD3B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F638C830-CD9C-47A3-8763-33611A98CD3B}.Release|Any CPU.Build.0 = Release|Any CPU + {D90BBB38-FA34-5ABD-F17F-F30AF1D84C6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D90BBB38-FA34-5ABD-F17F-F30AF1D84C6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D90BBB38-FA34-5ABD-F17F-F30AF1D84C6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D90BBB38-FA34-5ABD-F17F-F30AF1D84C6A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -81,7 +81,7 @@ Global {EB1D1EAE-E264-4FF5-ACB9-8614FD694C69} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {5F164DC3-0185-4D92-B689-7A004F81C232} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {2FD6F8C0-9D97-7714-4445-AD6E47BEB671} = {0AB3BF05-4346-4AA6-1389-037BE0695223} - {F638C830-CD9C-47A3-8763-33611A98CD3B} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {D90BBB38-FA34-5ABD-F17F-F30AF1D84C6A} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0D7E6B09-2E5D-4BA4-899B-FBCC5A7F92CD} diff --git a/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs b/Tests/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs similarity index 95% rename from SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs rename to Tests/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs index f5e35bae..d3faba69 100644 --- a/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs +++ b/Tests/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs @@ -12,8 +12,10 @@ protected BasePageTest(WebApplicationSetupFixture fixture) { _fixture = fixture; - //Uncomment to run tests in headed mode - Environment.SetEnvironmentVariable("HEADED", "1"); + if (_fixture.IsHeaded()) + { + Environment.SetEnvironmentVariable("HEADED", "1"); + } } public override BrowserNewContextOptions ContextOptions() diff --git a/SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs b/Tests/SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs similarity index 100% rename from SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs rename to Tests/SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs diff --git a/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs b/Tests/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs similarity index 73% rename from SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs rename to Tests/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs index 0f86f99f..987864be 100644 --- a/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs +++ b/Tests/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs @@ -5,9 +5,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using SAPPub.Core.Interfaces.Repositories.Generic; using SAPPub.Web; -using SAPPub.Web.Helpers; namespace SAPPub.Integration.Tests; @@ -16,33 +14,49 @@ public class TestWebApplicationFactory : WebApplicationFactory private IHost? _host; private static string? _cachedWebProjectPath; + public IConfiguration GetAppConfiguration() + { + using var scope = Services.CreateScope(); + return scope.ServiceProvider.GetRequiredService(); + } + protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseUrls("http://127.0.0.1:0", "https://127.0.0.1:0"); - builder.UseEnvironment("UITests"); // Set content root to web project so static files (wwwroot) are found - var webProjectPath = GetWebProjectPath(); - builder.UseContentRoot(webProjectPath); - builder.UseWebRoot(Path.Combine(webProjectPath, "wwwroot")); - - var testDataFilePath = GetTestDataFilePath(); - var configurationValues = CreateConfigurationValues(testDataFilePath); - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(configurationValues) - .Build(); - - builder - .UseConfiguration(configuration) - .ConfigureAppConfiguration(configurationBuilder => - { - configurationBuilder.AddInMemoryCollection(configurationValues); - }) - .ConfigureServices(services => - { - services.RemoveAll(typeof(IGenericRepository<>)); - services.AddSingleton(typeof(IGenericRepository<>), typeof(FakeGenericRepository<>)); - }); + //var webProjectPath = GetWebProjectPath(); + //builder.UseContentRoot(webProjectPath); + //builder.UseWebRoot(Path.Combine(webProjectPath, "wwwroot")); + + + //--------------------------------------------------------------------------------------- + // shouldn't need any of this - pipeline should use the web app's standard config + // but leaving here for now in case we need to override config or services for testing purposes + // ------------------------------------------------------------------------------------------------ + //var testDataFilePath = GetTestDataFilePath(); + //var configurationValues = CreateConfigurationValues(testDataFilePath); + //var configuration = new ConfigurationBuilder() + // .AddInMemoryCollection(configurationValues) + // .Build(); + + //builder + // .UseConfiguration(configuration) + // .ConfigureAppConfiguration(configurationBuilder => + // { + // configurationBuilder.AddInMemoryCollection(configurationValues); + // }) + // .ConfigureServices(services => + // { + // services.RemoveAll(typeof(IGenericRepository<>)); + // services.AddSingleton(typeof(IGenericRepository<>), typeof(FakeGenericRepository<>)); + // }); + + //builder.ConfigureAppConfiguration((context, config) => + //{ + // config.AddJsonFile("appsettings.json") + // .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true); + //}); } protected override IHost CreateHost(IHostBuilder builder) diff --git a/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs b/Tests/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs similarity index 74% rename from SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs rename to Tests/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs index a1b16618..f46f6663 100644 --- a/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs +++ b/Tests/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs @@ -17,6 +17,13 @@ public Task InitializeAsync() return Task.CompletedTask; } + public bool IsHeaded() + { + return _factory? + .GetAppConfiguration() + .GetSection("Playwright")["Headed"] is string headed && bool.TryParse(headed, out var isHeaded) && isHeaded; + } + public async Task DisposeAsync() { if (_factory != null) diff --git a/SAPPub.Integration.Tests/SAPPub.Integration.Tests.csproj b/Tests/SAPPub.Integration.Tests/SAPPub.IntegrationTests.csproj similarity index 84% rename from SAPPub.Integration.Tests/SAPPub.Integration.Tests.csproj rename to Tests/SAPPub.Integration.Tests/SAPPub.IntegrationTests.csproj index 5e359e59..32c57a03 100644 --- a/SAPPub.Integration.Tests/SAPPub.Integration.Tests.csproj +++ b/Tests/SAPPub.Integration.Tests/SAPPub.IntegrationTests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -16,15 +16,15 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - + diff --git a/SAPPub.Integration.Tests/AboutSchoolPageTests.cs b/Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AboutSchoolPageTests.cs similarity index 50% rename from SAPPub.Integration.Tests/AboutSchoolPageTests.cs rename to Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AboutSchoolPageTests.cs index c1377654..adf075d6 100644 --- a/SAPPub.Integration.Tests/AboutSchoolPageTests.cs +++ b/Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AboutSchoolPageTests.cs @@ -1,26 +1,17 @@ -namespace SAPPub.Integration.Tests; +using SAPPub.Integration.Tests; + +namespace SAPPub.IntegrationTests.SecondarySchoolTests; [Collection("Integration Tests")] public class AboutSchoolPageTests(WebApplicationSetupFixture fixture) : BasePageTest(fixture) { - private Dictionary _schoolUrnToUrlMap = new Dictionary - { - //["105574"] = "school/105574/Loreto%20High%20School%20Chorlton/secondary/about", - //["137552"] = "school/137552/Stewards%20Academy%20-%20Science%20Specialist%2C%20Harlow/secondary/about", - //["100273"] = "school/100273/Saint%20Paul%20Roman%20Catholic%20Infant%20School/secondary/about", - //["107564"] = "school/107564/Todmorden%20High%20School/secondary/about" - ["105574"] = "school/105574", - ["137552"] = "school/137552", - ["100273"] = "school/100273", - ["107564"] = "school/107564" - - }; + private string PageUrl(string urn) => $"/school/{urn}"; [Fact] public async Task AboutSchoolPage_LoadsSuccessfully() { // Arrange && Act - var response = await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + var response = await Page.GotoAsync(PageUrl("105574")); // Assert Assert.NotNull(response); @@ -30,8 +21,8 @@ public async Task AboutSchoolPage_LoadsSuccessfully() [Fact] public async Task AboutSchoolPage_HasCorrectTitle() { - // Arrange - await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + // PageUrl + await Page.GotoAsync(PageUrl("105574")); // Act var title = await Page.TitleAsync(); @@ -44,7 +35,7 @@ public async Task AboutSchoolPage_HasCorrectTitle() public async Task AboutSchoolPage_DisplaysMainHeading() { // Arrange - await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + await Page.GotoAsync(PageUrl("105574")); // Act var heading = await Page.Locator("h1").TextContentAsync(); @@ -58,7 +49,7 @@ public async Task AboutSchoolPage_DisplaysMainHeading() public async Task AboutSchoolPage_Displays_SchoolName_Caption() { // Arrange - await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + await Page.GotoAsync(PageUrl("105574")); // Act var schoolNameCaptionLocator = Page.Locator("#school-name-caption"); @@ -72,13 +63,11 @@ public async Task AboutSchoolPage_Displays_SchoolName_Caption() } [Theory] - [InlineData("105574", false, false)] - [InlineData("137552", true, false)] - [InlineData("107564", true, true)] - public async Task AboutSchoolPage_Displays_School_Closed_Info(string urn, bool isSchoolClosed, bool hasSchoolClosedDate) + [InlineData("114311", true, "31 December 2022")] + public async Task AboutSchoolPage_Displays_School_Closed_Info(string urn, bool isSchoolClosed, string? date) { // Act - await Page.GotoAsync(_schoolUrnToUrlMap[urn]); + await Page.GotoAsync(PageUrl(urn)); // Assert var schoolClosedCard = Page.GetByTestId("school-closed-custom-card"); @@ -89,7 +78,7 @@ public async Task AboutSchoolPage_Displays_School_Closed_Info(string urn, bool i { var value = await schoolClosedCard.Locator("p").TextContentAsync(); - var expectedText = hasSchoolClosedDate ? "This school closed on 23 March 2025" : "Closed"; + var expectedText = date != null ? $"This school closed on {date}" : "Closed"; Assert.NotNull(value); Assert.Equal(expectedText, value.Trim()); @@ -97,12 +86,12 @@ public async Task AboutSchoolPage_Displays_School_Closed_Info(string urn, bool i } [Theory] - [InlineData("105574", false)] - [InlineData("137552", true)] - public async Task AboutSchoolPage_DisplaysSchoolDetails(string urn, bool trustNameIsExpected) + [InlineData("105574", null)] + [InlineData("137552", "THE PASSMORES CO-OPERATIVE LEARNING COMMUNITY")] + public async Task AboutSchoolPage_DisplaysTrustNameRow_WhenTrustNameNotNull(string urn, string? trustName) { // Act - await Page.GotoAsync(_schoolUrnToUrlMap[urn]); + await Page.GotoAsync(PageUrl(urn)); // Assert var detailsSummary = Page.Locator("#school-details-summary"); @@ -112,11 +101,11 @@ public async Task AboutSchoolPage_DisplaysSchoolDetails(string urn, bool trustNa .Locator(".govuk-summary-list__row") .Filter(new() { Has = Page.Locator(".govuk-summary-list__key", new() { HasText = " Academy Trust " }) }); - if (trustNameIsExpected) + if (trustName != null) { var value = await row.Locator(".govuk-summary-list__value").TextContentAsync(); Assert.NotNull(value); - Assert.Equal("THE PASSMORES CO-OPERATIVE LEARNING COMMUNITY", value.Trim()); + Assert.Equal(trustName, value.Trim()); } else { @@ -128,7 +117,7 @@ public async Task AboutSchoolPage_DisplaysSchoolDetails(string urn, bool trustNa public async Task AboutSchoolPage_DisplaysSchoolLocation() { // Arrange - await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + await Page.GotoAsync(PageUrl("105574")); // Act var isVisible = await Page.Locator("#school-location-summary").IsVisibleAsync(); @@ -141,7 +130,7 @@ public async Task AboutSchoolPage_DisplaysSchoolLocation() public async Task AboutSchoolPage_DisplaysSpecialistUnit() { // Arrange - await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + await Page.GotoAsync(PageUrl("105574")); // Act var isVisible = await Page.Locator("#details-sen").IsVisibleAsync(); @@ -154,7 +143,7 @@ public async Task AboutSchoolPage_DisplaysSpecialistUnit() public async Task AboutSchoolPage_DisplaysSchoolFeatures() { // Arrange - await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); + await Page.GotoAsync(PageUrl("105574")); // Act var isVisible = await Page.Locator("#school-features-summary").IsVisibleAsync(); @@ -162,47 +151,4 @@ public async Task AboutSchoolPage_DisplaysSchoolFeatures() // Assert Assert.True(isVisible); } - - [Fact] - public async Task AboutSchoolPage_DisplaysSchoolPolicies() - { - // Arrange - await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); - - // Act - var schoolPoliciesSummaryCard = Page.Locator("#school-policies-summary"); - var contactSchoolInfo = schoolPoliciesSummaryCard.GetByTestId("contact-school-info"); - - // Assert - Assert.True(await schoolPoliciesSummaryCard.IsVisibleAsync()); - Assert.False(await contactSchoolInfo.IsVisibleAsync()); - } - - [Fact] - public async Task AboutSchoolPage_DisplaysSchoolPolicies_ContactSchoolText() - { - // Arrange - await Page.GotoAsync(_schoolUrnToUrlMap["100273"]); - - // Act - var schoolPoliciesSummaryCard = Page.Locator("#school-policies-summary"); - var contactSchoolInfo = schoolPoliciesSummaryCard.GetByTestId("contact-school-info"); - var isVisible = await contactSchoolInfo.IsVisibleAsync(); - - // Assert - Assert.True(isVisible); - } - - [Fact] - public async Task AboutSchoolPage_DisplaysPagination() - { - // Arrange - await Page.GotoAsync(_schoolUrnToUrlMap["105574"]); - - // Act - var isVisible = await Page.Locator("#about-the-school-pagination").IsVisibleAsync(); - - // Assert - Assert.True(isVisible); - } } \ No newline at end of file diff --git a/Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AttainmentPageTests.cs b/Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AttainmentPageTests.cs new file mode 100644 index 00000000..6d946a5d --- /dev/null +++ b/Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AttainmentPageTests.cs @@ -0,0 +1,78 @@ +using Microsoft.Playwright; +using SAPPub.Integration.Tests; +using System.Text.RegularExpressions; + +namespace SAPPub.IntegrationTests.SecondarySchoolTests; + +[Collection("Integration Tests")] +public class AttainmentPageTests(WebApplicationSetupFixture fixture) : BasePageTest(fixture) +{ + private string PageUrl(string urn) => $"/school/{urn}"; + + [Theory] + [InlineData("136745", 39.8, 44.1, 46)] + [InlineData("137638", 44.2, 44.1, 46)] + [InlineData("142894", 39.2, 44.1, 46)] + [InlineData("144496", 49.6, 44.1, 46)] + [InlineData("144991", 45.6, 44.1, 46)] + [InlineData("145564", 50.4, 44.1, 46)] + [InlineData("147531", 49.1, 44.1, 46)] + [InlineData("147894", 57.5, 44.1, 46)] + [InlineData("147711", 45.8, 44.1, 46)] + [InlineData("136971", 46.4, 44.1, 46)] + [InlineData("137903", 46.9, 44.1, 46)] + [InlineData("148109", 44.7, 44.1, 46)] + [InlineData("145253", 41.5, 44.1, 46)] + [InlineData("148706", 39.4, 44.1, 46)] + [InlineData("138717", 48.2, 44.1, 46)] + [InlineData("149962", 35.7, 44.1, 46)] + [InlineData("136770", 43.9, 44.1, 46)] + [InlineData("114308", 53.4, 44.1, 46)] + [InlineData("137696", 45.2, 44.1, 46)] + [InlineData("149251", 37.4, 44.1, 46)] + [InlineData("114312", 55.2, 44.1, 46)] + [InlineData("147122", 41.4, 44.1, 46)] + [InlineData("136451", 52.2, 44.1, 46)] + [InlineData("149739", 47.8, 44.1, 46)] + [InlineData("147670", 51.5, 44.1, 46)] + [InlineData("138075", 51.8, 44.1, 46)] + [InlineData("137702", 46.4, 44.1, 46)] + [InlineData("143583", 57.7, 44.1, 46)] + [InlineData("148304", 44.5, 44.1, 46)] + [InlineData("138172", 44.9, 44.1, 46)] + public async Task AboutSchoolPage_LoadsSuccessfully(string urn, double expectedAttainmentSchool, double expectedAttainmentLA, double expectedAttainmentEngland) + { + // Arrange && Act + var _ = await Page.GotoAsync(PageUrl(urn)); + var response = await ClickAcademicPerformanceLinkAsync(); + + // Assert + var schoolAttainment8 = await GetScoreAsync("attainment8-establishment-card", "The attainment 8 score for this school is"); + Assert.NotNull(schoolAttainment8); + Assert.Equal(expectedAttainmentSchool.ToString("F1"), schoolAttainment8); + } + + private Task ClickAcademicPerformanceLinkAsync() + { + var response = Page.RunAndWaitForResponseAsync( + async () => + { + await Page.GetByRole(AriaRole.Link, new() { Name = "Academic performance" }).ClickAsync(); + }, + response => response.Url.Contains("/academic-performance-attainment-and-progress") && response.Status == 200 + ); + return response; + } + + public async Task GetScoreAsync(string dataTestid, string textString) + { + var card = Page.Locator($"[data-testid='{dataTestid}']"); + var p = card.Locator("p.govuk-body", new() { HasTextString = textString }); + var input = await p.InnerTextAsync(); + var match = Regex.Matches(input, @"\d+(\.\d+)?") + .Cast() + .Last(); + + return match.Value; + } +}