Skip to content

Commit fc5b75e

Browse files
authored
Fix flakey firefox tests (#4912)
* Fix flakey firefox tests * Fix flakey upload tests * Add integration tests for useLoaderData/ErrorBoundary
1 parent aa0aa37 commit fc5b75e

File tree

4 files changed

+266
-11
lines changed

4 files changed

+266
-11
lines changed

integration/error-boundary-test.ts

+239
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { test, expect } from "@playwright/test";
2+
import { ServerMode } from "@remix-run/server-runtime/mode";
23

34
import { createAppFixture, createFixture, js } from "./helpers/create-fixture";
45
import type { Fixture, AppFixture } from "./helpers/create-fixture";
@@ -636,3 +637,241 @@ test.describe("ErrorBoundary", () => {
636637
});
637638
});
638639
});
640+
641+
test.describe("loaderData in ErrorBoundary", () => {
642+
let fixture: Fixture;
643+
let appFixture: AppFixture;
644+
let consoleErrors: string[];
645+
let oldConsoleError: () => void;
646+
647+
test.beforeAll(async () => {
648+
fixture = await createFixture({
649+
files: {
650+
"app/root.jsx": js`
651+
import { Links, Meta, Outlet, Scripts } from "@remix-run/react";
652+
653+
export default function Root() {
654+
return (
655+
<html lang="en">
656+
<head>
657+
<Meta />
658+
<Links />
659+
</head>
660+
<body>
661+
<main>
662+
<Outlet />
663+
</main>
664+
<Scripts />
665+
</body>
666+
</html>
667+
);
668+
}
669+
`,
670+
671+
"app/routes/parent.jsx": js`
672+
import { Outlet, useLoaderData, useMatches } from "@remix-run/react";
673+
674+
export function loader() {
675+
return "PARENT";
676+
}
677+
678+
export default function () {
679+
return (
680+
<div>
681+
<p id="parent-data">{useLoaderData()}</p>
682+
<Outlet />
683+
</div>
684+
)
685+
}
686+
687+
export function ErrorBoundary({ error }) {
688+
return (
689+
<>
690+
<p id="parent-data">{useLoaderData()}</p>
691+
<p id="parent-matches-data">
692+
{useMatches().find(m => m.id === 'routes/parent').data}
693+
</p>
694+
<p id="parent-error">{error.message}</p>
695+
</>
696+
);
697+
}
698+
`,
699+
700+
"app/routes/parent/child-with-boundary.jsx": js`
701+
import { Form, useLoaderData } from "@remix-run/react";
702+
703+
export function loader() {
704+
return "CHILD";
705+
}
706+
707+
export function action() {
708+
throw new Error("Broken!");
709+
}
710+
711+
export default function () {
712+
return (
713+
<>
714+
<p id="child-data">{useLoaderData()}</p>
715+
<Form method="post">
716+
<button type="submit" name="key" value="value">
717+
Submit
718+
</button>
719+
</Form>
720+
</>
721+
)
722+
}
723+
724+
export function ErrorBoundary({ error }) {
725+
return (
726+
<>
727+
<p id="child-data">{useLoaderData()}</p>
728+
<p id="child-error">{error.message}</p>
729+
</>
730+
);
731+
}
732+
`,
733+
734+
"app/routes/parent/child-without-boundary.jsx": js`
735+
import { Form, useLoaderData } from "@remix-run/react";
736+
737+
export function loader() {
738+
return "CHILD";
739+
}
740+
741+
export function action() {
742+
throw new Error("Broken!");
743+
}
744+
745+
export default function () {
746+
return (
747+
<>
748+
<p id="child-data">{useLoaderData()}</p>
749+
<Form method="post">
750+
<button type="submit" name="key" value="value">
751+
Submit
752+
</button>
753+
</Form>
754+
</>
755+
)
756+
}
757+
`,
758+
},
759+
});
760+
761+
appFixture = await createAppFixture(fixture, ServerMode.Development);
762+
});
763+
764+
test.afterAll(() => {
765+
appFixture.close();
766+
});
767+
768+
test.beforeEach(({ page }) => {
769+
oldConsoleError = console.error;
770+
console.error = () => {};
771+
consoleErrors = [];
772+
// Listen for all console events and handle errors
773+
page.on("console", (msg) => {
774+
if (msg.type() === "error") {
775+
consoleErrors.push(msg.text());
776+
}
777+
});
778+
});
779+
780+
test.afterEach(() => {
781+
console.error = oldConsoleError;
782+
});
783+
784+
test.describe("without JavaScript", () => {
785+
test.use({ javaScriptEnabled: false });
786+
runBoundaryTests();
787+
});
788+
789+
test.describe("with JavaScript", () => {
790+
test.use({ javaScriptEnabled: true });
791+
runBoundaryTests();
792+
});
793+
794+
function runBoundaryTests() {
795+
test("Prevents useLoaderData in self ErrorBoundary", async ({
796+
page,
797+
javaScriptEnabled,
798+
}) => {
799+
let app = new PlaywrightFixture(appFixture, page);
800+
await app.goto("/parent/child-with-boundary");
801+
802+
expect(await app.getHtml("#parent-data")).toEqual(
803+
'<p id="parent-data">PARENT</p>'
804+
);
805+
expect(await app.getHtml("#child-data")).toEqual(
806+
'<p id="child-data">CHILD</p>'
807+
);
808+
expect(consoleErrors).toEqual([]);
809+
810+
await app.clickSubmitButton("/parent/child-with-boundary");
811+
await page.waitForSelector("#child-error");
812+
813+
expect(await app.getHtml("#child-error")).toEqual(
814+
'<p id="child-error">Broken!</p>'
815+
);
816+
expect(await app.getHtml("#parent-data")).toEqual(
817+
'<p id="parent-data">PARENT</p>'
818+
);
819+
expect(await app.getHtml("#child-data")).toEqual(
820+
'<p id="child-data"></p>'
821+
);
822+
823+
// Only look for this message. Chromium browsers will also log the
824+
// network error but firefox does not
825+
// "Failed to load resource: the server responded with a status of 500 (Internal Server Error)",
826+
let msg =
827+
"You cannot `useLoaderData` in an errorElement (routeId: routes/parent/child-with-boundary)";
828+
if (javaScriptEnabled) {
829+
expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]);
830+
} else {
831+
// We don't get the useLoaderData message in the client when JS is disabled
832+
expect(consoleErrors.filter((m) => m === msg)).toEqual([]);
833+
}
834+
});
835+
836+
test("Prevents useLoaderData in bubbled ErrorBoundary", async ({
837+
page,
838+
javaScriptEnabled,
839+
}) => {
840+
let app = new PlaywrightFixture(appFixture, page);
841+
await app.goto("/parent/child-without-boundary");
842+
843+
expect(await app.getHtml("#parent-data")).toEqual(
844+
'<p id="parent-data">PARENT</p>'
845+
);
846+
expect(await app.getHtml("#child-data")).toEqual(
847+
'<p id="child-data">CHILD</p>'
848+
);
849+
expect(consoleErrors).toEqual([]);
850+
851+
await app.clickSubmitButton("/parent/child-without-boundary");
852+
await page.waitForSelector("#parent-error");
853+
854+
expect(await app.getHtml("#parent-error")).toEqual(
855+
'<p id="parent-error">Broken!</p>'
856+
);
857+
expect(await app.getHtml("#parent-matches-data")).toEqual(
858+
'<p id="parent-matches-data"></p>'
859+
);
860+
expect(await app.getHtml("#parent-data")).toEqual(
861+
'<p id="parent-data"></p>'
862+
);
863+
864+
// Only look for this message. Chromium browsers will also log the
865+
// network error but firefox does not
866+
// "Failed to load resource: the server responded with a status of 500 (Internal Server Error)",
867+
let msg =
868+
"You cannot `useLoaderData` in an errorElement (routeId: routes/parent)";
869+
if (javaScriptEnabled) {
870+
expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]);
871+
} else {
872+
// We don't get the useLoaderData message in the client when JS is disabled
873+
expect(consoleErrors.filter((m) => m === msg)).toEqual([]);
874+
}
875+
});
876+
}
877+
});

integration/fetcher-layout-test.ts

+8
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ test("fetcher calls layout route action when at index route", async ({
191191
let app = new PlaywrightFixture(appFixture, page);
192192
await app.goto("/layout-action");
193193
await app.clickElement("button");
194+
await page.waitForSelector("#layout-fetcher-data");
194195
let dataElement = await app.getElement("#layout-fetcher-data");
195196
expect(dataElement.text()).toBe("layout action data");
196197
dataElement = await app.getElement("#child-data");
@@ -203,6 +204,7 @@ test("fetcher calls layout route loader when at index route", async ({
203204
let app = new PlaywrightFixture(appFixture, page);
204205
await app.goto("/layout-loader");
205206
await app.clickElement("button");
207+
await page.waitForSelector("#layout-fetcher-data");
206208
let dataElement = await app.getElement("#layout-fetcher-data");
207209
expect(dataElement.text()).toBe("layout loader data");
208210
});
@@ -213,6 +215,7 @@ test("fetcher calls index route action when at index route", async ({
213215
let app = new PlaywrightFixture(appFixture, page);
214216
await app.goto("/layout-action");
215217
await app.clickElement("#index-fetcher");
218+
await page.waitForSelector("#index-fetcher-data");
216219
let dataElement = await app.getElement("#index-fetcher-data");
217220
expect(dataElement.text()).toBe("index action data");
218221
dataElement = await app.getElement("#child-data");
@@ -225,6 +228,7 @@ test("fetcher calls index route loader when at index route", async ({
225228
let app = new PlaywrightFixture(appFixture, page);
226229
await app.goto("/layout-loader");
227230
await app.clickElement("#index-fetcher");
231+
await page.waitForSelector("#index-fetcher-data");
228232
let dataElement = await app.getElement("#index-fetcher-data");
229233
expect(dataElement.text()).toBe("index data");
230234
});
@@ -235,6 +239,7 @@ test("fetcher calls layout route action when at paramaterized route", async ({
235239
let app = new PlaywrightFixture(appFixture, page);
236240
await app.goto("/layout-action/foo");
237241
await app.clickElement("button");
242+
await page.waitForSelector("#layout-fetcher-data");
238243
let dataElement = await app.getElement("#layout-fetcher-data");
239244
expect(dataElement.text()).toBe("layout action data");
240245
dataElement = await app.getElement("#child-data");
@@ -247,6 +252,7 @@ test("fetcher calls layout route loader when at paramaterized route", async ({
247252
let app = new PlaywrightFixture(appFixture, page);
248253
await app.goto("/layout-loader/foo");
249254
await app.clickElement("button");
255+
await page.waitForSelector("#layout-fetcher-data");
250256
let dataElement = await app.getElement("#layout-fetcher-data");
251257
expect(dataElement.text()).toBe("layout loader data");
252258
});
@@ -255,6 +261,7 @@ test("fetcher calls paramaterized route route action", async ({ page }) => {
255261
let app = new PlaywrightFixture(appFixture, page);
256262
await app.goto("/layout-action/foo");
257263
await app.clickElement("#param-fetcher");
264+
await page.waitForSelector("#param-fetcher-data");
258265
let dataElement = await app.getElement("#param-fetcher-data");
259266
expect(dataElement.text()).toBe("param action data");
260267
dataElement = await app.getElement("#child-data");
@@ -265,6 +272,7 @@ test("fetcher calls paramaterized route route loader", async ({ page }) => {
265272
let app = new PlaywrightFixture(appFixture, page);
266273
await app.goto("/layout-loader/foo");
267274
await app.clickElement("#param-fetcher");
275+
await page.waitForSelector("#param-fetcher-data");
268276
let dataElement = await app.getElement("#param-fetcher-data");
269277
expect(dataElement.text()).toBe("foo");
270278
});

integration/file-uploads-test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ test.describe("file-uploads", () => {
6161
};
6262
6363
export default function Upload() {
64+
let actionData = useActionData();
6465
return (
6566
<>
6667
<Form method="post" encType="multipart/form-data">
@@ -69,7 +70,7 @@ test.describe("file-uploads", () => {
6970
<input type="hidden" name="test" value="hidden" />
7071
<button type="submit">Submit</button>
7172
</Form>
72-
<pre>{JSON.stringify(useActionData(), null, 2)}</pre>
73+
{actionData ? <pre>{JSON.stringify(actionData, null, 2)}</pre> : null}
7374
</>
7475
);
7576
}
@@ -100,6 +101,7 @@ test.describe("file-uploads", () => {
100101
await app.goto("/file-upload");
101102
await app.uploadFile("#file", uploadFile);
102103
await app.clickSubmitButton("/file-upload");
104+
await page.waitForSelector("pre");
103105
expect(await app.getHtml("pre")).toBe(`<pre>
104106
{
105107
"name": "underLimit.txt",
@@ -126,6 +128,7 @@ test.describe("file-uploads", () => {
126128
await app.goto("/file-upload");
127129
await app.uploadFile("#file", uploadFile);
128130
await app.clickSubmitButton("/file-upload");
131+
await page.waitForSelector("pre");
129132
expect(await app.getHtml("pre")).toBe(`<pre>
130133
{
131134
"errorMessage": "Field \\"file\\" exceeded upload size of 10000 bytes."

0 commit comments

Comments
 (0)