Skip to content

Commit 8530f7c

Browse files
authored
e2e: add open-graph image tests (opennextjs#692)
* add og to app router * add e2e * md5 checksum * review
1 parent e8f6dc8 commit 8530f7c

File tree

4 files changed

+171
-0
lines changed

4 files changed

+171
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { ImageResponse } from "next/og";
2+
// App router includes @vercel/og.
3+
// No need to install it.
4+
// ?title=<title>
5+
6+
export async function GET(request: Request) {
7+
try {
8+
const { searchParams } = new URL(request.url);
9+
10+
// ?title=<title>
11+
const hasTitle = searchParams.has("title");
12+
const title = hasTitle
13+
? searchParams.get("title")?.slice(0, 100)
14+
: "My default title";
15+
16+
return new ImageResponse(
17+
<div
18+
style={{
19+
backgroundColor: "black",
20+
backgroundSize: "150px 150px",
21+
height: "100%",
22+
width: "100%",
23+
display: "flex",
24+
textAlign: "center",
25+
alignItems: "center",
26+
justifyContent: "center",
27+
flexDirection: "column",
28+
flexWrap: "nowrap",
29+
}}
30+
>
31+
<div
32+
style={{
33+
display: "flex",
34+
alignItems: "center",
35+
justifyContent: "center",
36+
justifyItems: "center",
37+
}}
38+
>
39+
<img
40+
alt="Vercel"
41+
height={200}
42+
src="data:image/svg+xml,%3Csvg width='116' height='100' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M57.5 0L115 100H0L57.5 0z' /%3E%3C/svg%3E"
43+
style={{ margin: "0 30px" }}
44+
width={232}
45+
/>
46+
</div>
47+
<div
48+
style={{
49+
fontSize: 60,
50+
fontStyle: "normal",
51+
letterSpacing: "-0.025em",
52+
color: "white",
53+
marginTop: 30,
54+
padding: "0 120px",
55+
lineHeight: 1.4,
56+
whiteSpace: "pre-wrap",
57+
}}
58+
>
59+
{title}
60+
</div>
61+
</div>,
62+
{
63+
width: 1200,
64+
height: 630,
65+
},
66+
);
67+
} catch (e: any) {
68+
return new Response("Failed to generate the image", {
69+
status: 500,
70+
});
71+
}
72+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { ImageResponse } from "next/og";
2+
3+
// Image metadata
4+
export const alt = "OpenNext";
5+
export const size = {
6+
width: 1200,
7+
height: 630,
8+
};
9+
10+
export const contentType = "image/png";
11+
12+
// Image generation
13+
export default async function Image() {
14+
return new ImageResponse(
15+
// ImageResponse JSX element
16+
<div
17+
style={{
18+
fontSize: 128,
19+
background: "white",
20+
width: "100%",
21+
height: "100%",
22+
display: "flex",
23+
alignItems: "center",
24+
justifyContent: "center",
25+
}}
26+
>
27+
OpenNext
28+
</div>,
29+
// ImageResponse options
30+
{
31+
// For convenience, we can re-use the exported opengraph-image
32+
// size config to also set the ImageResponse's width and height.
33+
...size,
34+
},
35+
);
36+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div></div>;
3+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { createHash } from "node:crypto";
2+
import { expect, test } from "@playwright/test";
3+
4+
// This is the md5sums of the expected PNGs generated with `md5sum <file>`
5+
const OG_MD5 = "6e5e794ac0c27598a331690f96f05d00";
6+
const API_OG_MD5 = "cac95fc3e2d4d52870c0536bb18ba85b";
7+
8+
function validateMd5(data: Buffer, expectedHash: string) {
9+
return createHash("md5").update(data).digest("hex") === expectedHash;
10+
}
11+
12+
test("Open-graph image to be in metatags and present", async ({
13+
page,
14+
request,
15+
}) => {
16+
await page.goto("/og");
17+
18+
// Wait for meta tags to be present
19+
const ogImageSrc = await page
20+
.locator('meta[property="og:image"]')
21+
.getAttribute("content");
22+
const ogImageAlt = await page
23+
.locator('meta[property="og:image:alt"]')
24+
.getAttribute("content");
25+
const ogImageType = await page
26+
.locator('meta[property="og:image:type"]')
27+
.getAttribute("content");
28+
const ogImageWidth = await page
29+
.locator('meta[property="og:image:width"]')
30+
.getAttribute("content");
31+
const ogImageHeight = await page
32+
.locator('meta[property="og:image:height"]')
33+
.getAttribute("content");
34+
35+
// Verify meta tag exists and is the correct values
36+
expect(ogImageSrc).not.toBe(null);
37+
expect(ogImageAlt).toBe("OpenNext");
38+
expect(ogImageType).toBe("image/png");
39+
expect(ogImageWidth).toBe("1200");
40+
expect(ogImageHeight).toBe("630");
41+
42+
// Check if the image source is working
43+
const response = await request.get(`/og/${ogImageSrc?.split("/").at(-1)}`);
44+
expect(response.status()).toBe(200);
45+
expect(response.headers()["content-type"]).toBe("image/png");
46+
expect(response.headers()["cache-control"]).toBe(
47+
"public, immutable, no-transform, max-age=31536000",
48+
);
49+
expect(validateMd5(await response.body(), OG_MD5)).toBe(true);
50+
});
51+
52+
test("next/og (vercel/og) to work in API route", async ({ request }) => {
53+
const response = await request.get("api/og?title=opennext");
54+
expect(response.status()).toBe(200);
55+
expect(response.headers()["content-type"]).toBe("image/png");
56+
expect(response.headers()["cache-control"]).toBe(
57+
"public, immutable, no-transform, max-age=31536000",
58+
);
59+
expect(validateMd5(await response.body(), API_OG_MD5)).toBe(true);
60+
});

0 commit comments

Comments
 (0)