Skip to content

Commit a078d07

Browse files
committed
add e2e for both
1 parent 02e6d04 commit a078d07

File tree

4 files changed

+118
-2
lines changed

4 files changed

+118
-2
lines changed

examples/app-router/app/streaming/route.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ const encoder = new TextEncoder();
2323

2424
async function* makeIterator() {
2525
for (let i = 1; i <= 10; i++) {
26-
yield encoder.encode(`<p data-testid="iteratorCount">${i}</p>`);
26+
const timestamp = Date.now();
27+
yield encoder.encode(
28+
`<p data-testid="iteratorCount" data-timestamp="${timestamp}">${i}</p>`,
29+
);
2730
await sleep(1000);
2831
}
2932
}

examples/pages-router/src/pages/api/streaming/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ const encoder = new TextEncoder();
2525

2626
async function* makeIterator() {
2727
for (let i = 1; i <= 10; i++) {
28-
yield encoder.encode(`<p data-testid="iteratorCount">${i}</p>`);
28+
const timestamp = Date.now();
29+
yield encoder.encode(
30+
`<p data-testid="iteratorCount" data-timestamp="${timestamp}">${i}</p>`,
31+
);
2932
await sleep(1000);
3033
}
3134
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test("streaming should work in route handler", async ({ page }) => {
4+
const ITERATOR_LENGTH = 10;
5+
6+
const res = await page.goto("/streaming", {
7+
// we set waitUntil: "commit" to ensure that the response is streamed
8+
// without this option, the response would be buffered and sent all at once
9+
// we could also drop the `await` aswell, but then we can't see the headers first.
10+
waitUntil: "commit",
11+
});
12+
13+
expect(res?.headers()["content-type"]).toBe("text/html; charset=utf-8");
14+
expect(res?.headers()["cache-control"]).toBe("no-cache, no-transform");
15+
// AWS API Gateway remaps the connection header to `x-amzn-remapped-connection`
16+
expect(res?.headers()["x-amzn-remapped-connection"]).toBe("keep-alive");
17+
18+
// wait for first number to be present
19+
await page.getByTestId("iteratorCount").first().waitFor();
20+
21+
const seenNumbers: Array<{ number: string; time: number }> = [];
22+
const startTime = Date.now();
23+
24+
const initialParagraphs = await page.getByTestId("iteratorCount").count();
25+
// fail if all paragraphs appear at once
26+
// this is a safeguard to ensure that the response is streamed and not buffered all at once
27+
expect(initialParagraphs).toBe(1);
28+
29+
while (
30+
seenNumbers.length < ITERATOR_LENGTH &&
31+
Date.now() - startTime < 11000
32+
) {
33+
const elements = await page.getByTestId("iteratorCount").all();
34+
if (elements.length > seenNumbers.length) {
35+
expect(elements.length).toBe(seenNumbers.length + 1);
36+
const newElement = elements[elements.length - 1];
37+
const timestamp = await newElement.getAttribute("data-timestamp");
38+
seenNumbers.push({
39+
number: await newElement.innerText(),
40+
time: Number.parseInt(timestamp || "0", 10),
41+
});
42+
}
43+
await page.waitForTimeout(100);
44+
}
45+
46+
expect(seenNumbers.map((n) => n.number)).toEqual(
47+
[...Array(ITERATOR_LENGTH)].map((_, i) => String(i + 1)),
48+
);
49+
50+
// verify streaming timing using server timestamps
51+
for (let i = 1; i < seenNumbers.length; i++) {
52+
const timeDiff = seenNumbers[i].time - seenNumbers[i - 1].time;
53+
expect(timeDiff).toBeGreaterThanOrEqual(900);
54+
}
55+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test("streaming should work in api route", async ({ page }) => {
4+
const ITERATOR_LENGTH = 10;
5+
6+
const res = await page.goto("/api/streaming", {
7+
// we set waitUntil: "commit" to ensure that the response is streamed
8+
// without this option, the response would be buffered and sent all at once
9+
// we could also drop the `await` aswell, but then we can't see the headers first.
10+
waitUntil: "commit",
11+
});
12+
13+
expect(res?.headers()["content-type"]).toBe("text/html; charset=utf-8");
14+
expect(res?.headers()["cache-control"]).toBe("no-cache, no-transform");
15+
// AWS API Gateway remaps the connection header to `x-amzn-remapped-connection`
16+
expect(res?.headers()["x-amzn-remapped-connection"]).toBe("keep-alive");
17+
18+
// wait for first number to be present
19+
await page.getByTestId("iteratorCount").first().waitFor();
20+
21+
const seenNumbers: Array<{ number: string; time: number }> = [];
22+
const startTime = Date.now();
23+
24+
const initialParagraphs = await page.getByTestId("iteratorCount").count();
25+
// fail if all paragraphs appear at once
26+
// this is a safeguard to ensure that the response is streamed and not buffered all at once
27+
expect(initialParagraphs).toBe(1);
28+
29+
while (
30+
seenNumbers.length < ITERATOR_LENGTH &&
31+
Date.now() - startTime < 11000
32+
) {
33+
const elements = await page.getByTestId("iteratorCount").all();
34+
if (elements.length > seenNumbers.length) {
35+
expect(elements.length).toBe(seenNumbers.length + 1);
36+
const newElement = elements[elements.length - 1];
37+
const timestamp = await newElement.getAttribute("data-timestamp");
38+
seenNumbers.push({
39+
number: await newElement.innerText(),
40+
time: Number.parseInt(timestamp || "0", 10),
41+
});
42+
}
43+
await page.waitForTimeout(100);
44+
}
45+
46+
expect(seenNumbers.map((n) => n.number)).toEqual(
47+
[...Array(ITERATOR_LENGTH)].map((_, i) => String(i + 1)),
48+
);
49+
50+
// verify streaming timing using server timestamps
51+
for (let i = 1; i < seenNumbers.length; i++) {
52+
const timeDiff = seenNumbers[i].time - seenNumbers[i - 1].time;
53+
expect(timeDiff).toBeGreaterThanOrEqual(800);
54+
}
55+
});

0 commit comments

Comments
 (0)