namespace ProtoTest.Demo; using System.IO; using System.Net; using global::NUnit.Framework; using Northstar.ProtoTest; using ProtoTest.Core; using ProtoTest.Data; using ProtoTest.Http; using ProtoTest.NUnit; using ProtoTest.Rest; using ProtoTest.SampleApp.Contracts; using ProtoTest.SampleApp.Domain; using ProtoTest.Sheets; using ProtoTest.Web; /// /// The Northstar console end to end in one session: sign in on the console's own sign-in screen, land on /// the dashboard, create a project and its environment on the screen, deploy and watch the rail change, /// pay an invoice in billing, and download the monthly report from reports and verify it with Sheets. /// The journey deliberately ends on the not-found screen without asserting anything there, so page /// coverage shows a page that was visited but never verified. /// [Application(NorthstarTargets.Api)] [NorthstarTenant(PlanIds.Growth)] [Auth] [LoginAs("owner")] [RequiresCapability( ProtoCapabilityKinds.Server, CapabilityName = "Northstar standalone", Reason = "The standalone application is only started when the suite owns the store.")] [RequiresConsoleBuild] public sealed class WebJourney { private const string ProjectName = "console-atlas"; [ProtoTest] [SignedInAs] public async Task TheConsoleFromSignInToTheMonthlyReport() { // The login strategy drove /console/signin with the tenant's token; the dashboard is the landing. var dashboard = Proto.Context.Web().Page(); await dashboard.Page.Should.BeVisibleAsync(NorthstarConsole.Wait); await dashboard.Title.Should.HaveTextAsync("Dashboard", NorthstarConsole.Wait); await dashboard.Plan.Should.HaveTextAsync("Growth", NorthstarConsole.Wait); // Create the project on the console's projects screen, as one named flow. await dashboard.NavProjects.ClickAsync(); var projects = Proto.Context.Web().Page(); await projects.Title.Should.HaveTextAsync("Projects", NorthstarConsole.Wait); await projects.Flow("Create project") .Click(page => page.NewProject) .Fill(page => page.NameInput, ProjectName) .Click(page => page.Create) .RunAsync(); var projectRow = projects.Project(ProjectName); await projectRow.Link.Should.HaveTextAsync(ProjectName, NorthstarConsole.Wait); await projectRow.Status.Should.HaveTextAsync("Active", NorthstarConsole.Wait); // Open the detail screen and create the environment the release lands in. await projectRow.Link.ClickAsync(); var project = Proto.Context.Web().Page(); await project.Name.Should.HaveTextAsync(ProjectName, NorthstarConsole.Wait); await project.EnvironmentsEmpty.Should.BeVisibleAsync(NorthstarConsole.Wait); await project.Flow("Create the preview environment") .Click(page => page.NewEnvironment) .Fill(page => page.EnvironmentNameInput, "preview") .Click(page => page.CreateEnvironment) .RunAsync(); var environment = project.Environment("preview"); await environment.Name.Should.HaveTextAsync("preview", NorthstarConsole.Wait); await environment.Version.Should.HaveTextAsync("none", NorthstarConsole.Wait); // Deploy through the console; the rail shows the release and its status. await project.DeployVersion.FillAsync("1.0.0"); await project.DeployCommit.FillAsync("abc1234"); await project.DeploySubmit.ClickAsync(); var deployed = project.Deployment("1.0.0"); await deployed.Status.Should.HaveTextAsync("Succeeded", NorthstarConsole.Wait); await environment.Version.Should.HaveTextAsync("1.0.0", NorthstarConsole.Wait); // The screen follows the application's live updates: the WebSocket subscription, or the visible // polling fallback when the socket cannot connect. Either way it settles on live or polling. await project.LiveIndicator.Should.BeVisibleAsync(NorthstarConsole.Wait); await project.LiveIndicator.ShouldNot.HaveTextAsync("Connecting", NorthstarConsole.LiveUpdateWait); // A failing release changes the rail again; the environment keeps the last good version. await project.DeployVersion.FillAsync("1.1.0"); await project.DeployCommit.FillAsync("badc0de"); await project.DeploySubmit.ClickAsync(); var failed = project.Deployment("1.1.0"); await failed.Status.Should.HaveTextAsync("Failed", NorthstarConsole.Wait); await environment.Version.Should.HaveTextAsync("1.0.0", NorthstarConsole.Wait); // Close the billing period so an invoice exists, then pay it on the billing screen. var invoice = await Proto.Context.Data().IssueInvoiceAsync(); await project.NavBilling.ClickAsync(); var billing = Proto.Context.Web().Page(); await billing.Title.Should.HaveTextAsync("Billing", NorthstarConsole.Wait); var invoiceRow = billing.Invoice(invoice.Number); await invoiceRow.Status.Should.HaveTextAsync("Open", NorthstarConsole.Wait); await invoiceRow.Pay.ClickAsync(); await invoiceRow.Status.Should.HaveTextAsync("Paid", NorthstarConsole.Wait); await invoiceRow.LastPayment.Should.HaveTextAsync("Succeeded", NorthstarConsole.Wait); // Download the workbook on the reports screen and verify the browser's own artifact with Sheets. await billing.NavReports.ClickAsync(); var reports = Proto.Context.Web().Page(); await reports.Title.Should.HaveTextAsync("Reports", NorthstarConsole.Wait); // Playwright captures the file the browser saves; a backend without the download capability // keeps the REST fallback, so the same workbook is still verified from its bytes. ProtoWorkbook report; try { var download = await reports.DownloadReportAsync(NorthstarConsole.Wait); Assert.That(download.FileName, Is.EqualTo("monthly.xlsx")); report = Proto.Context.Sheets().Open(new MemoryStream(download.Content.ToArray()), download.FileName); } catch (WebBackendCapabilityException) { await reports.Download.ClickAsync(); using var response = await Proto.Context.Rest().GetAsync("/api/v1/reports/monthly.xlsx"); response.Should.HaveHttpStatus(HttpStatusCode.OK); report = Proto.Context.Sheets().Open(response); } await reports.Status.Should.HaveTextAsync("monthly.xlsx downloaded.", NorthstarConsole.Wait); var reportModel = report.Model(); reportModel.Verify(); var reportRow = reportModel.Row(candidate => candidate.Name == ProjectName); using (Assert.EnterMultipleScope()) { Assert.That(reportRow.Status, Is.EqualTo(ProjectStatuses.Active)); Assert.That(reportRow.Environments, Is.EqualTo(1)); } // Deliberately unverified ending: the not-found screen is visited, never asserted, so coverage // reports a page the console showed but the suite did not cover. await reports.OpenAsync("/console/this-screen-was-never-built"); } }