Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test: 348 make Playwright detect 500s #358

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions backend/Testing/Browser/Base/PageTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public class PageTest : IAsyncLifetime
public IPage Page => _fixture.Page;
public IBrowser Browser => _fixture.Browser;
public IBrowserContext Context => _fixture.Context;
/// <summary>
/// Exceptions that are deferred until the end of the test, because they can't
/// be cleanly thrown in sub-threads.
/// </summary>
private List<UnexpectedResponseException> DeferredExceptions { get; } = new();

public PageTest()
{
Expand All @@ -26,6 +31,21 @@ public PageTest()
public ILocatorAssertions Expect(ILocator locator) => Assertions.Expect(locator);
public IPageAssertions Expect(IPage page) => Assertions.Expect(page);
public IAPIResponseAssertions Expect(IAPIResponse response) => Assertions.Expect(response);
/// <summary>
/// Consumes a deferred exception that was "thrown" in a sub-thread, and returns it
/// or throws if no exception of the given type is found.
/// </summary>
public UnexpectedResponseException ExpectDeferredException()
{
var exception = DeferredExceptions.ShouldHaveSingleItem();
DeferredExceptions.Clear();
return exception;
}

public void ExpectNoDeferredExceptions()
{
DeferredExceptions.ShouldBeEmpty();
}

public virtual async Task InitializeAsync()
{
Expand All @@ -39,6 +59,14 @@ await Context.Tracing.StartAsync(new()
Sources = true
});
}

Context.Response += (_, response) =>
{
if (response.Status >= (int)HttpStatusCode.InternalServerError)
{
DeferredExceptions.Add(new UnexpectedResponseException(response));
}
};
}

public virtual async Task DisposeAsync()
Expand All @@ -52,6 +80,11 @@ public virtual async Task DisposeAsync()
}

await _fixture.DisposeAsync();

if (DeferredExceptions.Any())
{
throw new AggregateException(DeferredExceptions);
}
}

static readonly HttpClient HttpClient = new HttpClient();
Expand Down
23 changes: 23 additions & 0 deletions backend/Testing/Browser/Base/UnexpectedResponseException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Text.RegularExpressions;
using Microsoft.Playwright;

namespace Testing.Browser.Base;

public partial class UnexpectedResponseException : SystemException
{
public static string MaskUrl(string url)
{
return JwtRegex().Replace(url, "*****");
}

public UnexpectedResponseException(IResponse response)
: this(response.StatusText, response.Status, response.Url)
{
}

public UnexpectedResponseException(string statusText, int statusCode, string url)
: base($"Unexpected response: {statusText} ({statusCode}). URL: {MaskUrl(url)}.") { }

[GeneratedRegex("[A-Za-z0-9-_]{10,}\\.[A-Za-z0-9-_]{20,}\\.[A-Za-z0-9-_]{10,}")]
private static partial Regex JwtRegex();
myieye marked this conversation as resolved.
Show resolved Hide resolved
}
51 changes: 43 additions & 8 deletions backend/Testing/Browser/SandboxPageTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Shouldly;
using Microsoft.Playwright;
using Testing.Browser.Base;
using Testing.Browser.Page;

Expand All @@ -8,16 +8,51 @@ namespace Testing.Browser;
public class SandboxPageTests : PageTest
{
[Fact]
public async Task Goto500Works()
public async Task CatchGoto500InSameTab()
{

await new SandboxPage(Page).Goto();
await Page.RunAndWaitForResponseAsync(async () =>
{
await Page.GetByText("Goto 500 page").ClickAsync();
}, "/api/testing/test500NoException");
ExpectDeferredException();
}

[Fact]
public async Task CatchGoto500InNewTab()
{
await new SandboxPage(Page).Goto();
var request = await Page.RunAndWaitForRequestFinishedAsync(async () =>
await Context.RunAndWaitForPageAsync(async () =>
{
await Page.GetByText("goto 500 page").ClickAsync();
await Page.GetByText("goto 500 new tab").ClickAsync();
});
var response = await request.ResponseAsync();
response.ShouldNotBeNull();
response.Ok.ShouldBeFalse();
response.Status.ShouldBe(500);
ExpectDeferredException();
}

[Fact(Skip = "Playwright doesn't catch the document load request of pages opened with Ctrl+Click")]
public async Task CatchGoto500InNewTabWithCtrl()
{
await new SandboxPage(Page).Goto();
await Context.RunAndWaitForPageAsync(async () =>
{
await Page.GetByText("Goto 500 page").ClickAsync(new()
{
Modifiers = new[] { KeyboardModifier.Control },
});
});
ExpectDeferredException();
}

[Fact]
public async Task CatchFetch500()
{
await new SandboxPage(Page).Goto();
await Page.RunAndWaitForResponseAsync(async () =>
{
await Page.GetByText("Fetch 500").ClickAsync();
}, "/api/testing/test500NoException");
ExpectDeferredException();
await Expect(Page.Locator(".modal-box.bg-error:has-text('Internal Server Error (500)')")).ToBeVisibleAsync();
}
}
7 changes: 7 additions & 0 deletions frontend/src/hooks.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,18 @@ function shouldTryAutoReload(updateDetected: boolean): boolean {
*/
handleFetch(async ({ fetch, args }) => {
const response = await traceFetch(() => fetch(...args));

if (response.status === 401 && location.pathname !== '/login') {
throw redirect(307, '/logout');
}

if (response.status >= 500) {
throw new Error(`Unexpected response: ${response.statusText} (${response.status}). URL: ${response.url}.`);
}

if (response.headers.get('lexbox-refresh-jwt') == 'true') {
await invalidate(USER_LOAD_KEY);
}

return response;
});
6 changes: 6 additions & 0 deletions frontend/src/routes/(unauthenticated)/sandbox/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
function uploadFinished(): void {
alert('upload done!');
}

async function fetch500(): Promise<Response> {
return fetch('/api/testing/test500NoException');
}
</script>
<div class="grid gap-2">
<h2>Sandbox</h2>
<div class="card w-96 bg-base-200 shadow-lg">
<a rel="external" class="btn" href="/api/testing/test500NoException">Goto 500 page</a>
<a rel="external" target="_blank" class="btn" href="/api/testing/test500NoException">Goto 500 new tab</a>
<button class="btn" on:click={fetch500}>Fetch 500</button>
</div>
<div class="card w-96 bg-base-200 shadow-lg">
<div class="card-body">
Expand Down