using Microsoft.Playwright; namespace decePubClient.E2E.Support { // Signs a town persona in through the real login page and persona chooser, once per persona per run; later sessions // start from the saved browser state (the AuthData decePub keeps in localStorage). public static class SignIn { static readonly string StateDir = Path.Combine(AppContext.BaseDirectory, ".state"); public static async Task As(Browser browser, Pasture.Persona persona, bool fresh = false) { var state = Path.Combine(StateDir, persona.UserName + ".json"); if (!fresh && File.Exists(state)) { var known = await browser.Open(state); await known.Page.GotoAsync(Pasture.Site + "/"); await known.Page.Locator("article[data-uri], [data-feed-empty]").First.WaitForAsync(new() { Timeout = 30000 }); return known; } var session = await browser.Open(); await Through(session.Page, persona); Directory.CreateDirectory(StateDir); await session.Context.StorageStateAsync(new() { Path = state }); return session; } public static async Task Through(IPage page, Pasture.Persona persona) { try { await Steps(page, persona); } catch { await Evidence(page, "signin-" + persona.UserName); throw; } } // a screenshot and the page's text next to the test output, for a failure seen only in a headless browser public static async Task Evidence(IPage page, string name) { Directory.CreateDirectory(StateDir); await page.ScreenshotAsync(new() { Path = Path.Combine(StateDir, name + ".png"), FullPage = true }); await File.WriteAllTextAsync(Path.Combine(StateDir, name + ".txt"), page.Url + "\n" + await page.InnerTextAsync("body")); } static async Task Steps(IPage page, Pasture.Persona persona) { await page.GotoAsync(Pasture.Site + "/login"); await page.Locator("input[autocomplete=username]").FillAsync(persona.Root); await page.Locator("input[autocomplete=current-password]").FillAsync(persona.Password); await page.Locator("button[type=submit]").ClickAsync(); // a root with several personas picks one; one with a single persona goes on by itself, its button disabled while it // does. Only the login page's chooser is clicked: the feed's persona switcher names the persona too var chooser = page.Locator("section button:enabled").Filter(new() { HasText = persona.UserName + "@privapub.test" }); var until = DateTime.UtcNow.AddSeconds(30); while (new Uri(page.Url).AbsolutePath != "/" && DateTime.UtcNow < until) { if (await chooser.CountAsync() > 0) try { await chooser.First.ClickAsync(new() { Timeout = 2000 }); } catch (TimeoutException) { } await page.WaitForTimeoutAsync(250); } await page.WaitForURLAsync(url => new Uri(url).AbsolutePath == "/", new() { Timeout = 30000 }); // (a new persona's feed is empty) await page.Locator("article[data-uri], [data-feed-empty]").First.WaitForAsync(new() { Timeout = 30000 }); } } }