Skip to content

Commit

Permalink
test: import app-router e2e tests from aws (#289)
Browse files Browse the repository at this point in the history
  • Loading branch information
vicb authored Jan 28, 2025
1 parent 3b20bc6 commit e4285a7
Show file tree
Hide file tree
Showing 26 changed files with 734 additions and 14 deletions.
7 changes: 7 additions & 0 deletions examples/e2e/app-router/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,10 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

29 changes: 29 additions & 0 deletions examples/e2e/app-router/e2e/after.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect, test } from "@playwright/test";

test("Next after", async ({ request }) => {
const initialSSG = await request.get("/api/after/ssg");
expect(initialSSG.status()).toEqual(200);
const initialSSGJson = await initialSSG.json();

// We then fire a post request that will revalidate the SSG page 5 seconds after, but should respond immediately
const dateNow = Date.now();
const revalidateSSG = await request.post("/api/after/revalidate");
expect(revalidateSSG.status()).toEqual(200);
const revalidateSSGJson = await revalidateSSG.json();
expect(revalidateSSGJson.success).toEqual(true);
// This request should take less than 5 seconds to respond
expect(Date.now() - dateNow).toBeLessThan(5000);

// We want to immediately check if the SSG page has been revalidated, it should not have been
const notRevalidatedSSG = await request.get("/api/after/ssg");
expect(notRevalidatedSSG.status()).toEqual(200);
const notRevalidatedSSGJson = await notRevalidatedSSG.json();
expect(notRevalidatedSSGJson.date).toEqual(initialSSGJson.date);

// We then wait for 5 seconds to ensure the SSG page has been revalidated
await new Promise((resolve) => setTimeout(resolve, 5000));
const revalidatedSSG = await request.get("/api/after/ssg");
expect(revalidatedSSG.status()).toEqual(200);
const revalidatedSSGJson = await revalidatedSSG.json();
expect(revalidatedSSGJson.date).not.toEqual(initialSSGJson.date);
});
29 changes: 29 additions & 0 deletions examples/e2e/app-router/e2e/api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect, test } from "@playwright/test";

test("API call from client", async ({ page }) => {
await page.goto("/");
await page.getByRole("link", { name: "/API" }).click();

await page.waitForURL("/api");

let el = page.getByText("API: N/A");
await expect(el).toBeVisible();

await page.getByRole("button", { name: "Call /api/client" }).click();
el = page.getByText('API: { "hello": "client" }');
await expect(el).toBeVisible();
});

test("API call from middleware", async ({ page }) => {
await page.goto("/");
await page.getByRole("link", { name: "/API" }).click();

await page.waitForURL("/api");

let el = page.getByText("API: N/A");
await expect(el).toBeVisible();

await page.getByRole("button", { name: "Call /api/middleware" }).click();
el = page.getByText('API: { "hello": "middleware" }');
await expect(el).toBeVisible();
});
75 changes: 75 additions & 0 deletions examples/e2e/app-router/e2e/config.redirect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { expect, test } from "@playwright/test";
/**
* This tests that the "redirect" config in next.config.js works
*
* redirects: () => {
return [
{
source: "/next-config-redirect",
destination: "/config-redirect",
permanent: true,
missing: [{ type: "cookie", key: "missing-cookie" }],
},
];
},
*/
test.describe("Next Config Redirect", () => {
test("Missing cookies", async ({ page }) => {
await page.goto("/");
await page.goto("/next-config-redirect-missing");

await page.waitForURL("/config-redirect?missing=true");

const el = page.getByText("I was redirected from next.config.js", {
exact: true,
});
await expect(el).toBeVisible();
});
test("Not missing cookies", async ({ page }) => {
await page.goto("/");
await page.goto("/next-config-redirect-not-missing");

// the cookie was not missing, so no redirects
await page.waitForURL("/next-config-redirect-not-missing");

const el = page.getByText("This page could not be found.", {
exact: true,
});
await expect(el).toBeVisible();
});
test("Has cookies", async ({ page }) => {
await page.goto("/");
await page.goto("/next-config-redirect-has");

await page.waitForURL("/config-redirect?has=true");

const el = page.getByText("I was redirected from next.config.js", {
exact: true,
});
await expect(el).toBeVisible();
});
test("Has cookies with value", async ({ page }) => {
await page.goto("/");
await page.goto("/next-config-redirect-has-with-value");

await page.waitForURL("/config-redirect?hasWithValue=true");

const el = page.getByText("I was redirected from next.config.js", {
exact: true,
});
await expect(el).toBeVisible();
});
test("Has cookies with bad value", async ({ page }) => {
await page.goto("/");
await page.goto("/next-config-redirect-has-with-bad-value");

// did not redirect
await page.waitForURL("/next-config-redirect-has-with-bad-value");

// 404 not found
const el = page.getByText("This page could not be found.", {
exact: true,
});
await expect(el).toBeVisible();
});
});
27 changes: 27 additions & 0 deletions examples/e2e/app-router/e2e/headers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, test } from "@playwright/test";

/**
* Tests that the headers are available in RSC and response headers
*/
test("Headers", async ({ page }) => {
const responsePromise = page.waitForResponse((response) => {
return response.status() === 200;
});
await page.goto("/headers");

const response = await responsePromise;
// Response header should be set
const headers = response.headers();
expect(headers["response-header"]).toEqual("response-header");

// The next.config.js headers should be also set in response
expect(headers["e2e-headers"]).toEqual("next.config.js");

// Request header should be available in RSC
const el = page.getByText("request-header");
await expect(el).toBeVisible();

// Both these headers should not be present cause poweredByHeader is false in appRouter
expect(headers["x-powered-by"]).toBeFalsy();
expect(headers["x-opennext"]).toBeFalsy();
});
11 changes: 11 additions & 0 deletions examples/e2e/app-router/e2e/host.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { expect, test } from "@playwright/test";

/**
* Tests that the request.url is the deployed host and not localhost
*/
test("Request.url is host", async ({ baseURL, page }) => {
await page.goto("/api/host");

const el = page.getByText(`{"url":"${baseURL}/api/host"}`);
await expect(el).toBeVisible();
});
18 changes: 18 additions & 0 deletions examples/e2e/app-router/e2e/image-optimization.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expect, test } from "@playwright/test";

test("Image Optimization", async ({ page }) => {
await page.goto("/");

const imageResponsePromise = page.waitForResponse(/https%3A%2F%2Fopennext.js.org%2Farchitecture.png/);
await page.locator('[href="/image-optimization"]').click();
const imageResponse = await imageResponsePromise;

await page.waitForURL("/image-optimization");

const imageContentType = imageResponse.headers()["content-type"];
expect(imageContentType).toBe("image/webp");

const el = page.locator("img");
await expect(el).toHaveJSProperty("complete", true);
await expect(el).not.toHaveJSProperty("naturalWidth", 0);
});
13 changes: 13 additions & 0 deletions examples/e2e/app-router/e2e/isr.revalidate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expect, test } from "@playwright/test";

test("Test revalidate", async ({ request }) => {
const result = await request.get("/api/isr");

expect(result.status()).toEqual(200);
const json = await result.json();
const body = json.body;

expect(json.status).toEqual(200);
expect(body.result).toEqual(true);
expect(body.cacheControl).toEqual("private, no-cache, no-store, max-age=0, must-revalidate");
});
63 changes: 63 additions & 0 deletions examples/e2e/app-router/e2e/isr.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect, test } from "@playwright/test";

test("Incremental Static Regeneration", async ({ page }) => {
test.setTimeout(45000);
await page.goto("/");
await page.locator("[href='/isr']").click();
// Load the page a couple times to regenerate ISR

let el = page.getByText("Time:");
// Track the static time
let time = await el.textContent();
let newTime: typeof time;
let tempTime = time;
do {
await page.waitForTimeout(1000);
await page.reload();
time = tempTime;
el = page.getByText("Time:");
newTime = await el.textContent();
tempTime = newTime;
} while (time !== newTime);
await page.reload();

await page.waitForTimeout(1000);
el = page.getByText("Time:");
const midTime = await el.textContent();
// Expect that the time is still stale
expect(midTime).toEqual(newTime);

// Wait 10 + 1 seconds for ISR to regenerate time
await page.waitForTimeout(11000);
let finalTime = newTime;
do {
await page.waitForTimeout(2000);
el = page.getByText("Time:");
finalTime = await el.textContent();
await page.reload();
} while (newTime === finalTime);

expect(newTime).not.toEqual(finalTime);
});

test("headers", async ({ page }) => {
let responsePromise = page.waitForResponse((response) => {
return response.status() === 200;
});
await page.goto("/isr");

while (true) {
const response = await responsePromise;
const headers = response.headers();

// this was set in middleware
if (headers["cache-control"] === "max-age=10, stale-while-revalidate=999") {
break;
}
await page.waitForTimeout(1000);
responsePromise = page.waitForResponse((response) => {
return response.status() === 200;
});
await page.reload();
}
});
12 changes: 12 additions & 0 deletions examples/e2e/app-router/e2e/middleware.cookies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect, test } from "@playwright/test";

test("Cookies", async ({ page, context }) => {
await page.goto("/");

const cookies = await context.cookies();
const from = cookies.find(({ name }) => name === "from");
expect(from?.value).toEqual("middleware");

const love = cookies.find(({ name }) => name === "with");
expect(love?.value).toEqual("love");
});
20 changes: 20 additions & 0 deletions examples/e2e/app-router/e2e/middleware.redirect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, test } from "@playwright/test";

test("Middleware Redirect", async ({ page, context }) => {
await page.goto("/");
await page.getByRole("link", { name: "/Redirect" }).click();

// URL is immediately redirected
await page.waitForURL("/redirect-destination");
let el = page.getByText("Redirect Destination", { exact: true });
await expect(el).toBeVisible();

// Loading page should also redirect
await page.goto("/redirect");
await page.waitForURL("/redirect-destination");
expect(await context.cookies().then((res) => res.find((cookie) => cookie.name === "test")?.value)).toBe(
"success"
);
el = page.getByText("Redirect Destination", { exact: true });
await expect(el).toBeVisible();
});
16 changes: 16 additions & 0 deletions examples/e2e/app-router/e2e/middleware.rewrite.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { expect, test } from "@playwright/test";

test("Middleware Rewrite", async ({ page }) => {
await page.goto("/");
await page.getByRole("link", { name: "/Rewrite" }).click();

await page.waitForURL("/rewrite");
let el = page.getByText("Rewritten Destination", { exact: true });
await expect(el).toBeVisible();

// Loading page should also rewrite
await page.goto("/rewrite");
await page.waitForURL("/rewrite");
el = page.getByText("Rewritten Destination", { exact: true });
await expect(el).toBeVisible();
});
18 changes: 18 additions & 0 deletions examples/e2e/app-router/e2e/modals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expect, test } from "@playwright/test";

test("Route modal and interception", async ({ page }) => {
await page.goto("/");
await page.getByRole("link", { name: "Albums" }).click();
await page.getByRole("link", { name: "Song: I'm never gonna give you up Year: 1965" }).click();

await page.waitForURL(`/albums/Hold%20Me%20In%20Your%20Arms/I'm%20never%20gonna%20give%20you%20up`);

const modal = page.getByText("Modal", { exact: true });
await expect(modal).toBeVisible();

// Reload the page to load non intercepted modal
await page.reload();
await page.waitForURL(`/albums/Hold%20Me%20In%20Your%20Arms/I'm%20never%20gonna%20give%20you%20up`);
const notModal = page.getByText("Not Modal", { exact: true });
await expect(notModal).toBeVisible();
});
39 changes: 39 additions & 0 deletions examples/e2e/app-router/e2e/og.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect, test } from "@playwright/test";
import { validateMd5 } from "../../utils";

// This is the md5sums of the expected PNGs generated with `md5sum <file>`
const OG_MD5 = "6e5e794ac0c27598a331690f96f05d00";
const API_OG_MD5 = "cac95fc3e2d4d52870c0536bb18ba85b";

test("Open-graph image to be in metatags and present", async ({ page, request }) => {
await page.goto("/og");

// Wait for meta tags to be present
const ogImageSrc = await page.locator('meta[property="og:image"]').getAttribute("content");
const ogImageAlt = await page.locator('meta[property="og:image:alt"]').getAttribute("content");
const ogImageType = await page.locator('meta[property="og:image:type"]').getAttribute("content");
const ogImageWidth = await page.locator('meta[property="og:image:width"]').getAttribute("content");
const ogImageHeight = await page.locator('meta[property="og:image:height"]').getAttribute("content");

// Verify meta tag exists and is the correct values
expect(ogImageSrc).not.toBe(null);
expect(ogImageAlt).toBe("OpenNext");
expect(ogImageType).toBe("image/png");
expect(ogImageWidth).toBe("1200");
expect(ogImageHeight).toBe("630");

// Check if the image source is working
const response = await request.get(`/og/${ogImageSrc?.split("/").at(-1)}`);
expect(response.status()).toBe(200);
expect(response.headers()["content-type"]).toBe("image/png");
expect(response.headers()["cache-control"]).toBe("public, immutable, no-transform, max-age=31536000");
expect(validateMd5(await response.body(), OG_MD5)).toBe(true);
});

test("next/og (vercel/og) to work in API route", async ({ request }) => {
const response = await request.get("api/og?title=opennext");
expect(response.status()).toBe(200);
expect(response.headers()["content-type"]).toBe("image/png");
expect(response.headers()["cache-control"]).toBe("public, immutable, no-transform, max-age=31536000");
expect(validateMd5(await response.body(), API_OG_MD5)).toBe(true);
});
Loading

0 comments on commit e4285a7

Please sign in to comment.