Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# debian so we can re-use!
FROM oven/bun:1.3.2-debian AS base
FROM oven/bun:1.4.4-debian AS base

WORKDIR /app

Expand All @@ -11,16 +11,19 @@ RUN bun install --frozen-lockfile --production && \
rm -rf ~/.bun/install/cache /tmp/*

# well-known OSS docker image
FROM chromedp/headless-shell:143.0.7445.3 AS final
FROM chromedp/headless-shell:157.0.8103.15 AS final

# install fonts only
# install fonts, and the patched Brotli decoder
RUN apt-get update && \
apt-get install -y --no-install-recommends \
tini \
ca-certificates \
fonts-liberation \
fonts-noto-color-emoji \
fonts-wqy-zenhei && \
fonts-wqy-zenhei \
libbrotli1 && \
# CVE-2026-111497: refuse to build on a Brotli decoder older than the Debian fix
dpkg --compare-versions "$(dpkg-query -W -f='${Version}' libbrotli1)" ge 1.1.0-2+deb13u1 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/cache/apt/archives/*

Expand Down
48 changes: 22 additions & 26 deletions tests/e2e/endpoints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,32 +101,28 @@ describe("E2E Tests - /v1/screenshots", () => {
}, 15000);

test("should capture full page screenshot", async () => {
const partialResponse = await fetch(`${BASE_URL}/v1/screenshots`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://itznotabug.dev",
fullPage: false,
}),
});

const partialBuffer = await partialResponse.arrayBuffer();

const fullPageResponse = await fetch(`${BASE_URL}/v1/screenshots`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://itznotabug.dev",
fullPage: true,
}),
});

expect(fullPageResponse.status).toBe(200);
const fullPageBuffer = await fullPageResponse.arrayBuffer();

// Full page screenshot should be larger than partial
expect(fullPageBuffer.byteLength).toBeGreaterThan(partialBuffer.byteLength);
}, 15000);
// A PNG's height is stored big-endian at bytes 20-23 of its IHDR chunk.
const height = (buffer: ArrayBuffer) => new DataView(buffer).getUint32(20);
const capture = async (fullPage: boolean) => {
const response = await fetch(`${BASE_URL}/v1/screenshots`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://en.wikipedia.org/wiki/Brotli",
fullPage,
}),
});
expect(response.status).toBe(200);
return response.arrayBuffer();
};

const partial = await capture(false);
const fullPage = await capture(true);

// Compressed size depends on Chrome's PNG encoder; the captured height does not.
expect(height(partial)).toBe(720);
expect(height(fullPage)).toBeGreaterThan(height(partial));
}, 30000);

test("should capture clipped region", async () => {
const fullResponse = await fetch(`${BASE_URL}/v1/screenshots`, {
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/egress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ beforeAll(async () => {
: { channel: "chrome" }),
};
browser = await chromium.launch(options);
});
// Launching the runner's Chrome can take longer than bun's 5s default hook timeout.
}, 30_000);

afterAll(async () => {
await browser?.close();
Expand Down
Loading