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.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/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.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 44933707..63df417f 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.IntegrationTests", "Tests\SAPPub.Integration.Tests\SAPPub.IntegrationTests.csproj", "{D90BBB38-FA34-5ABD-F17F-F30AF1D84C6A}" +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 + {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 @@ -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} + {D90BBB38-FA34-5ABD-F17F-F30AF1D84C6A} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0D7E6B09-2E5D-4BA4-899B-FBCC5A7F92CD} diff --git a/Tests/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs b/Tests/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs new file mode 100644 index 00000000..d3faba69 --- /dev/null +++ b/Tests/SAPPub.Integration.Tests/Infrastructure/BasePageTest.cs @@ -0,0 +1,77 @@ +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; + + if (_fixture.IsHeaded()) + { + 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/Tests/SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs b/Tests/SAPPub.Integration.Tests/Infrastructure/IntegrationTestCollection.cs new file mode 100644 index 00000000..77a9c08a --- /dev/null +++ b/Tests/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/Tests/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs b/Tests/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs new file mode 100644 index 00000000..987864be --- /dev/null +++ b/Tests/SAPPub.Integration.Tests/Infrastructure/TestWebApplicationFactory.cs @@ -0,0 +1,211 @@ +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.Web; + +namespace SAPPub.Integration.Tests; + +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"); + + // Set content root to web project so static files (wwwroot) are found + //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) + { + // 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/Tests/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs b/Tests/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs new file mode 100644 index 00000000..f46f6663 --- /dev/null +++ b/Tests/SAPPub.Integration.Tests/Infrastructure/WebApplicationSetupFixture.cs @@ -0,0 +1,34 @@ +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 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) + { + await _factory.DisposeAsync(); + } + } +} diff --git a/Tests/SAPPub.Integration.Tests/SAPPub.IntegrationTests.csproj b/Tests/SAPPub.Integration.Tests/SAPPub.IntegrationTests.csproj new file mode 100644 index 00000000..32c57a03 --- /dev/null +++ b/Tests/SAPPub.Integration.Tests/SAPPub.IntegrationTests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + diff --git a/Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AboutSchoolPageTests.cs b/Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AboutSchoolPageTests.cs new file mode 100644 index 00000000..adf075d6 --- /dev/null +++ b/Tests/SAPPub.Integration.Tests/SecondarySchoolTests/AboutSchoolPageTests.cs @@ -0,0 +1,154 @@ +using SAPPub.Integration.Tests; + +namespace SAPPub.IntegrationTests.SecondarySchoolTests; + +[Collection("Integration Tests")] +public class AboutSchoolPageTests(WebApplicationSetupFixture fixture) : BasePageTest(fixture) +{ + private string PageUrl(string urn) => $"/school/{urn}"; + + [Fact] + public async Task AboutSchoolPage_LoadsSuccessfully() + { + // Arrange && Act + var response = await Page.GotoAsync(PageUrl("105574")); + + // Assert + Assert.NotNull(response); + Assert.Equal(200, response.Status); + } + + [Fact] + public async Task AboutSchoolPage_HasCorrectTitle() + { + // PageUrl + await Page.GotoAsync(PageUrl("105574")); + + // Act + var title = await Page.TitleAsync(); + + // Assert + Assert.Contains("About the school", title); + } + + [Fact] + public async Task AboutSchoolPage_DisplaysMainHeading() + { + // Arrange + await Page.GotoAsync(PageUrl("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(PageUrl("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("114311", true, "31 December 2022")] + public async Task AboutSchoolPage_Displays_School_Closed_Info(string urn, bool isSchoolClosed, string? date) + { + // Act + await Page.GotoAsync(PageUrl(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 = date != null ? $"This school closed on {date}" : "Closed"; + + Assert.NotNull(value); + Assert.Equal(expectedText, value.Trim()); + } + } + + [Theory] + [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(PageUrl(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 (trustName != null) + { + var value = await row.Locator(".govuk-summary-list__value").TextContentAsync(); + Assert.NotNull(value); + Assert.Equal(trustName, value.Trim()); + } + else + { + Assert.False(await row.IsVisibleAsync()); + } + } + + [Fact] + public async Task AboutSchoolPage_DisplaysSchoolLocation() + { + // Arrange + await Page.GotoAsync(PageUrl("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(PageUrl("105574")); + + // Act + var isVisible = await Page.Locator("#details-sen").IsVisibleAsync(); + + // Assert + Assert.True(isVisible); + } + + [Fact] + public async Task AboutSchoolPage_DisplaysSchoolFeatures() + { + // Arrange + await Page.GotoAsync(PageUrl("105574")); + + // Act + var isVisible = await Page.Locator("#school-features-summary").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; + } +} 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()