Skip to content

Commit 411e093

Browse files
authored
test: add user journey e2e tests for main features (#348)
* test: add user journey e2e tests for main features We'll be adding a few more around caching in a subsequent PR and that should be it. * chore: remove multi-browser e2e testing It doesn't provide much value and it triples the runtime.
1 parent 3a2fb99 commit 411e093

File tree

12 files changed

+193
-20
lines changed

12 files changed

+193
-20
lines changed

playwright.config.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,4 @@ export default defineConfig({
2626
'x-nf-debug-logging': '1',
2727
},
2828
},
29-
30-
/* Configure projects for major browsers */
31-
projects: [
32-
{
33-
name: 'chromium',
34-
use: { ...devices['Desktop Chrome'] },
35-
},
36-
37-
{
38-
name: 'firefox',
39-
use: { ...devices['Desktop Firefox'] },
40-
},
41-
42-
{
43-
name: 'webkit',
44-
use: { ...devices['Desktop Safari'] },
45-
},
46-
],
4729
})
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1+
import { Await, defer, useLoaderData } from "@remix-run/react";
2+
import { Suspense } from "react";
3+
4+
export async function loader() {
5+
const messagePromise = new Promise<string>((resolve) => {
6+
setTimeout(() => {
7+
resolve("This is an about page streamed from the server.");
8+
}, 2000);
9+
});
10+
return defer({ message: messagePromise });
11+
}
112
export default function About() {
13+
const { message } = useLoaderData<typeof loader>();
214
return (
315
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
416
<h1>About</h1>
5-
<p>This is an about page.</p>
17+
<Suspense fallback={"Loading..."}>
18+
<Await resolve={message}>{(message) => <p>{message}</p>}</Await>
19+
</Suspense>
620
</div>
721
);
822
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Config } from "@netlify/edge-functions";
2+
3+
const handler = async (): Promise<Response> => {
4+
return new Response("Pong!");
5+
};
6+
7+
export default handler;
8+
9+
export const config: Config = {
10+
path: "/ping",
11+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Config } from "@netlify/functions";
2+
3+
const handler = async (): Promise<Response> => {
4+
return new Response("gurble");
5+
};
6+
7+
export default handler;
8+
9+
export const config: Config = {
10+
path: "/please-blorble",
11+
};
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1+
import { Await, defer, useLoaderData } from "@remix-run/react";
2+
import { Suspense } from "react";
3+
4+
export async function loader() {
5+
const messagePromise = new Promise<string>((resolve) => {
6+
setTimeout(() => {
7+
resolve("This is an about page streamed from the server.");
8+
}, 2000);
9+
});
10+
return defer({ message: messagePromise });
11+
}
112
export default function About() {
13+
const { message } = useLoaderData<typeof loader>();
214
return (
315
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
416
<h1>About</h1>
5-
<p>This is an about page.</p>
17+
<Suspense fallback={"Loading..."}>
18+
<Await resolve={message}>{(message) => <p>{message}</p>}</Await>
19+
</Suspense>
620
</div>
721
);
822
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { json, useLoaderData } from "@remix-run/react";
2+
import { getStore } from "@netlify/blobs";
3+
4+
export async function loader() {
5+
const store = getStore({ name: "favorites", consistency: "strong" });
6+
await store.set("cereal", "Raisin Bran");
7+
const favCereal = await store.get("cereal");
8+
return json({ favCereal });
9+
}
10+
11+
export default function BlobsDemo() {
12+
const { favCereal } = useLoaderData<typeof loader>();
13+
return (
14+
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
15+
<h1>Netlify Blobs demo page</h1>
16+
<p>My favorite breakfast cereal is {favCereal}</p>
17+
</div>
18+
);
19+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function ImagesDemo() {
2+
return (
3+
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
4+
<h1>Netlify Image CDN demo page</h1>
5+
<img
6+
src={`/.netlify/images?url=/camel.jpg&fit=cover&w=300&h=300&position=left`}
7+
alt="A camel"
8+
/>
9+
</div>
10+
);
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Config } from "@netlify/edge-functions";
2+
3+
const handler = async (): Promise<Response> => {
4+
return new Response("Pong!");
5+
};
6+
7+
export default handler;
8+
9+
export const config: Config = {
10+
path: "/ping",
11+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Config } from "@netlify/functions";
2+
3+
const handler = async (): Promise<Response> => {
4+
return new Response("gurble");
5+
};
6+
7+
export default handler;
8+
9+
export const config: Config = {
10+
path: "/please-blorble",
11+
};

tests/e2e/fixtures/serverless-site/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"typecheck": "tsc"
1212
},
1313
"dependencies": {
14+
"@netlify/blobs": "^7.3.0",
1415
"@netlify/remix-adapter": "*",
1516
"@remix-run/node": "^2.9.2",
1617
"@remix-run/react": "^2.9.2",
@@ -20,6 +21,7 @@
2021
"react-dom": "^18.2.0"
2122
},
2223
"devDependencies": {
24+
"@netlify/functions": "^2.7.0",
2325
"@remix-run/dev": "^2.9.2",
2426
"@types/react": "^18.2.20",
2527
"@types/react-dom": "^18.2.7",
Loading

tests/e2e/user-journeys.spec.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,102 @@ test.describe('User journeys', () => {
44
test('serves a response from the origin when using @netlify/remix-adapter', async ({ page, serverlessSite }) => {
55
const response = await page.goto(serverlessSite.url)
66
await expect(page.getByRole('heading', { name: /Welcome to Remix/i })).toBeVisible()
7+
expect(response?.status()).toBe(200)
78
expect(response?.headers()['x-nf-function-type']).toBe('request')
89
})
910

1011
test('serves a response from the edge when using @netlify/remix-edge-adapter', async ({ page, edgeSite }) => {
1112
const response = await page.goto(edgeSite.url)
13+
expect(response?.status()).toBe(200)
1214
await expect(page.getByRole('heading', { name: /Welcome to Remix/i })).toBeVisible()
1315
expect(response?.headers()['x-nf-edge-functions']).toBe('remix-server')
1416
})
1517

18+
test('serves a 404 for a request to a URL matching no routes', async ({ page, serverlessSite }) => {
19+
const response = await page.goto(`${serverlessSite.url}/not-a-real-path`)
20+
expect(response?.status()).toBe(404)
21+
await expect(page.getByRole('heading', { name: /404 Not Found/i })).toBeVisible()
22+
})
23+
24+
test('serves a response from a user-defined Netlify Function on a custom path when using origin SSR', async ({
25+
page,
26+
serverlessSite,
27+
}) => {
28+
const response = await page.goto(`${serverlessSite.url}/please-blorble`)
29+
expect(response?.status()).toBe(200)
30+
await expect(page.getByText('gurble')).toBeVisible()
31+
})
32+
33+
test('serves a response from a user-defined Netlify Function on a custom path when using edge SSR', async ({
34+
page,
35+
edgeSite,
36+
}) => {
37+
const response = await page.goto(`${edgeSite.url}/please-blorble`)
38+
expect(response?.status()).toBe(200)
39+
await expect(page.getByText('gurble')).toBeVisible()
40+
})
41+
42+
test('serves a response from a user-defined Netlify Edge Function on a custom path when using origin SSR', async ({
43+
page,
44+
serverlessSite,
45+
}) => {
46+
const response = await page.goto(`${serverlessSite.url}/ping`)
47+
expect(response?.status()).toBe(200)
48+
await expect(page.getByText('Pong!')).toBeVisible()
49+
})
50+
51+
test('serves a response from a user-defined Netlify Edge Function on a custom path when using edge SSR', async ({
52+
page,
53+
edgeSite,
54+
}) => {
55+
const response = await page.goto(`${edgeSite.url}/ping`)
56+
expect(response?.status()).toBe(200)
57+
await expect(page.getByText('Pong!')).toBeVisible()
58+
})
59+
60+
test('streams a response from the origin as it is rendered and renders postponed nodes afterward', async ({
61+
page,
62+
serverlessSite,
63+
}) => {
64+
const response = await page.goto(`${serverlessSite.url}/about`)
65+
expect(response?.status()).toBe(200)
66+
await expect(page.getByRole('heading', { name: /About/i })).toBeVisible()
67+
// This page has an artificial 2s delay on the server
68+
await expect(page.getByText('This is an about page streamed from the server.')).toBeVisible({
69+
timeout: 3000,
70+
})
71+
})
72+
73+
test('streams a response from the edge as it is rendered and renders postponed nodes afterward', async ({
74+
page,
75+
edgeSite,
76+
}) => {
77+
const response = await page.goto(`${edgeSite.url}/about`)
78+
expect(response?.status()).toBe(200)
79+
await expect(page.getByRole('heading', { name: /About/i })).toBeVisible()
80+
// This page has an artificial 2s delay on the server
81+
await expect(page.getByText('This is an about page streamed from the server.')).toBeVisible({
82+
timeout: 3000,
83+
})
84+
})
85+
86+
test('can use Netlify Blobs in Remix loaders', async ({ page, serverlessSite }) => {
87+
const response = await page.goto(`${serverlessSite.url}/blobs`)
88+
expect(response?.status()).toBe(200)
89+
await expect(page.getByRole('heading', { name: /Netlify Blobs/i })).toBeVisible()
90+
await expect(page.getByText('My favorite breakfast cereal is Raisin Bran')).toBeVisible()
91+
})
92+
93+
test('can use the Netlify Image CDN with manually constructed URLs', async ({ page, serverlessSite }) => {
94+
const response = await page.goto(`${serverlessSite.url}/images`)
95+
expect(response?.status()).toBe(200)
96+
await expect(page.getByRole('heading', { name: /Netlify Image CDN/i })).toBeVisible()
97+
await expect(page.getByRole('img')).toBeVisible()
98+
// We've dynamically requested these dimensions from the Image CDN, so this proves that it works
99+
await expect(page.getByRole('img')).toHaveJSProperty('width', 300)
100+
await expect(page.getByRole('img')).toHaveJSProperty('height', 300)
101+
})
102+
16103
test.describe('classic Remix compiler', () => {
17104
test('serves a response from the origin when using @netlify/remix-adapter', async ({
18105
page,

0 commit comments

Comments
 (0)