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

Fix flakey firefox tests #4912

Merged
merged 3 commits into from
Dec 19, 2022
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
239 changes: 239 additions & 0 deletions integration/error-boundary-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect } from "@playwright/test";
import { ServerMode } from "@remix-run/server-runtime/mode";

import { createAppFixture, createFixture, js } from "./helpers/create-fixture";
import type { Fixture, AppFixture } from "./helpers/create-fixture";
Expand Down Expand Up @@ -636,3 +637,241 @@ test.describe("ErrorBoundary", () => {
});
});
});

test.describe("loaderData in ErrorBoundary", () => {
let fixture: Fixture;
let appFixture: AppFixture;
let consoleErrors: string[];
let oldConsoleError: () => void;

test.beforeAll(async () => {
fixture = await createFixture({
files: {
"app/root.jsx": js`
import { Links, Meta, Outlet, Scripts } from "@remix-run/react";

export default function Root() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<main>
<Outlet />
</main>
<Scripts />
</body>
</html>
);
}
`,

"app/routes/parent.jsx": js`
import { Outlet, useLoaderData, useMatches } from "@remix-run/react";

export function loader() {
return "PARENT";
}

export default function () {
return (
<div>
<p id="parent-data">{useLoaderData()}</p>
<Outlet />
</div>
)
}

export function ErrorBoundary({ error }) {
return (
<>
<p id="parent-data">{useLoaderData()}</p>
<p id="parent-matches-data">
{useMatches().find(m => m.id === 'routes/parent').data}
</p>
<p id="parent-error">{error.message}</p>
</>
);
}
`,

"app/routes/parent/child-with-boundary.jsx": js`
import { Form, useLoaderData } from "@remix-run/react";

export function loader() {
return "CHILD";
}

export function action() {
throw new Error("Broken!");
}

export default function () {
return (
<>
<p id="child-data">{useLoaderData()}</p>
<Form method="post">
<button type="submit" name="key" value="value">
Submit
</button>
</Form>
</>
)
}

export function ErrorBoundary({ error }) {
return (
<>
<p id="child-data">{useLoaderData()}</p>
<p id="child-error">{error.message}</p>
</>
);
}
`,

"app/routes/parent/child-without-boundary.jsx": js`
import { Form, useLoaderData } from "@remix-run/react";

export function loader() {
return "CHILD";
}

export function action() {
throw new Error("Broken!");
}

export default function () {
return (
<>
<p id="child-data">{useLoaderData()}</p>
<Form method="post">
<button type="submit" name="key" value="value">
Submit
</button>
</Form>
</>
)
}
`,
},
});

appFixture = await createAppFixture(fixture, ServerMode.Development);
});

test.afterAll(() => {
appFixture.close();
});

test.beforeEach(({ page }) => {
oldConsoleError = console.error;
console.error = () => {};
consoleErrors = [];
// Listen for all console events and handle errors
page.on("console", (msg) => {
if (msg.type() === "error") {
consoleErrors.push(msg.text());
}
});
});

test.afterEach(() => {
console.error = oldConsoleError;
});

test.describe("without JavaScript", () => {
test.use({ javaScriptEnabled: false });
runBoundaryTests();
});

test.describe("with JavaScript", () => {
test.use({ javaScriptEnabled: true });
runBoundaryTests();
});

function runBoundaryTests() {
test("Prevents useLoaderData in self ErrorBoundary", async ({
page,
javaScriptEnabled,
}) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/parent/child-with-boundary");

expect(await app.getHtml("#parent-data")).toEqual(
'<p id="parent-data">PARENT</p>'
);
expect(await app.getHtml("#child-data")).toEqual(
'<p id="child-data">CHILD</p>'
);
expect(consoleErrors).toEqual([]);

await app.clickSubmitButton("/parent/child-with-boundary");
await page.waitForSelector("#child-error");

expect(await app.getHtml("#child-error")).toEqual(
'<p id="child-error">Broken!</p>'
);
expect(await app.getHtml("#parent-data")).toEqual(
'<p id="parent-data">PARENT</p>'
);
expect(await app.getHtml("#child-data")).toEqual(
'<p id="child-data"></p>'
);

// Only look for this message. Chromium browsers will also log the
// network error but firefox does not
// "Failed to load resource: the server responded with a status of 500 (Internal Server Error)",
let msg =
"You cannot `useLoaderData` in an errorElement (routeId: routes/parent/child-with-boundary)";
if (javaScriptEnabled) {
expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]);
} else {
// We don't get the useLoaderData message in the client when JS is disabled
expect(consoleErrors.filter((m) => m === msg)).toEqual([]);
}
});

test("Prevents useLoaderData in bubbled ErrorBoundary", async ({
page,
javaScriptEnabled,
}) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/parent/child-without-boundary");

expect(await app.getHtml("#parent-data")).toEqual(
'<p id="parent-data">PARENT</p>'
);
expect(await app.getHtml("#child-data")).toEqual(
'<p id="child-data">CHILD</p>'
);
expect(consoleErrors).toEqual([]);

await app.clickSubmitButton("/parent/child-without-boundary");
await page.waitForSelector("#parent-error");

expect(await app.getHtml("#parent-error")).toEqual(
'<p id="parent-error">Broken!</p>'
);
expect(await app.getHtml("#parent-matches-data")).toEqual(
'<p id="parent-matches-data"></p>'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I think this is a bug in TM today that is fixed in the new router. When JS is enabled, TM doesn't clear out the prior parent routeData so we still get a now-stale PARENT here even though the loader was not re-run

);
expect(await app.getHtml("#parent-data")).toEqual(
'<p id="parent-data"></p>'
);

// Only look for this message. Chromium browsers will also log the
// network error but firefox does not
// "Failed to load resource: the server responded with a status of 500 (Internal Server Error)",
let msg =
"You cannot `useLoaderData` in an errorElement (routeId: routes/parent)";
if (javaScriptEnabled) {
expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]);
} else {
// We don't get the useLoaderData message in the client when JS is disabled
expect(consoleErrors.filter((m) => m === msg)).toEqual([]);
}
});
}
});
8 changes: 8 additions & 0 deletions integration/fetcher-layout-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ test("fetcher calls layout route action when at index route", async ({
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-action");
await app.clickElement("button");
await page.waitForSelector("#layout-fetcher-data");
let dataElement = await app.getElement("#layout-fetcher-data");
expect(dataElement.text()).toBe("layout action data");
dataElement = await app.getElement("#child-data");
Expand All @@ -203,6 +204,7 @@ test("fetcher calls layout route loader when at index route", async ({
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-loader");
await app.clickElement("button");
await page.waitForSelector("#layout-fetcher-data");
let dataElement = await app.getElement("#layout-fetcher-data");
expect(dataElement.text()).toBe("layout loader data");
});
Expand All @@ -213,6 +215,7 @@ test("fetcher calls index route action when at index route", async ({
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-action");
await app.clickElement("#index-fetcher");
await page.waitForSelector("#index-fetcher-data");
let dataElement = await app.getElement("#index-fetcher-data");
expect(dataElement.text()).toBe("index action data");
dataElement = await app.getElement("#child-data");
Expand All @@ -225,6 +228,7 @@ test("fetcher calls index route loader when at index route", async ({
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-loader");
await app.clickElement("#index-fetcher");
await page.waitForSelector("#index-fetcher-data");
let dataElement = await app.getElement("#index-fetcher-data");
expect(dataElement.text()).toBe("index data");
});
Expand All @@ -235,6 +239,7 @@ test("fetcher calls layout route action when at paramaterized route", async ({
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-action/foo");
await app.clickElement("button");
await page.waitForSelector("#layout-fetcher-data");
let dataElement = await app.getElement("#layout-fetcher-data");
expect(dataElement.text()).toBe("layout action data");
dataElement = await app.getElement("#child-data");
Expand All @@ -247,6 +252,7 @@ test("fetcher calls layout route loader when at paramaterized route", async ({
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-loader/foo");
await app.clickElement("button");
await page.waitForSelector("#layout-fetcher-data");
let dataElement = await app.getElement("#layout-fetcher-data");
expect(dataElement.text()).toBe("layout loader data");
});
Expand All @@ -255,6 +261,7 @@ test("fetcher calls paramaterized route route action", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-action/foo");
await app.clickElement("#param-fetcher");
await page.waitForSelector("#param-fetcher-data");
let dataElement = await app.getElement("#param-fetcher-data");
expect(dataElement.text()).toBe("param action data");
dataElement = await app.getElement("#child-data");
Expand All @@ -265,6 +272,7 @@ test("fetcher calls paramaterized route route loader", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/layout-loader/foo");
await app.clickElement("#param-fetcher");
await page.waitForSelector("#param-fetcher-data");
let dataElement = await app.getElement("#param-fetcher-data");
expect(dataElement.text()).toBe("foo");
});
5 changes: 4 additions & 1 deletion integration/file-uploads-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ test.describe("file-uploads", () => {
};

export default function Upload() {
let actionData = useActionData();
return (
<>
<Form method="post" encType="multipart/form-data">
Expand All @@ -69,7 +70,7 @@ test.describe("file-uploads", () => {
<input type="hidden" name="test" value="hidden" />
<button type="submit">Submit</button>
</Form>
<pre>{JSON.stringify(useActionData(), null, 2)}</pre>
{actionData ? <pre>{JSON.stringify(actionData, null, 2)}</pre> : null}
</>
);
}
Expand Down Expand Up @@ -100,6 +101,7 @@ test.describe("file-uploads", () => {
await app.goto("/file-upload");
await app.uploadFile("#file", uploadFile);
await app.clickSubmitButton("/file-upload");
await page.waitForSelector("pre");
expect(await app.getHtml("pre")).toBe(`<pre>
{
"name": "underLimit.txt",
Expand All @@ -126,6 +128,7 @@ test.describe("file-uploads", () => {
await app.goto("/file-upload");
await app.uploadFile("#file", uploadFile);
await app.clickSubmitButton("/file-upload");
await page.waitForSelector("pre");
expect(await app.getHtml("pre")).toBe(`<pre>
{
"errorMessage": "Field \\"file\\" exceeded upload size of 10000 bytes."
Expand Down
Loading