diff --git a/packages/kit/src/exports/node/index.spec.js b/packages/kit/src/exports/node/index.spec.js index 6d8c751b588f..8327660e7895 100644 --- a/packages/kit/src/exports/node/index.spec.js +++ b/packages/kit/src/exports/node/index.spec.js @@ -1,4 +1,5 @@ import { EventEmitter, once } from 'node:events'; +import { validateHeaderValue } from 'node:http'; import { PassThrough } from 'node:stream'; import { expect, test, vi } from 'vitest'; import { getRequest, setResponse } from './index.js'; @@ -92,11 +93,17 @@ function create_response(req) { res.destroyed = false; res.headers = new Map(); res.chunks = []; - res.setHeader = (/** @type {string} */ name, /** @type {unknown} */ value) => + res.setHeader = (/** @type {string} */ name, /** @type {string} */ value) => { + validateHeaderValue(name, value); res.headers.set(name.toLowerCase(), value); + }; res.hasHeader = (/** @type {string} */ name) => res.headers.has(name.toLowerCase()); res.getHeaderNames = () => [...res.headers.keys()]; - res.writeHead = () => res; + res.removeHeader = (/** @type {string} */ name) => res.headers.delete(name.toLowerCase()); + res.writeHead = (/** @type {number} */ status) => { + res.statusCode = status; + return res; + }; res.write = (/** @type {unknown} */ chunk) => { res.chunks.push(chunk); return true; @@ -255,6 +262,18 @@ test('does not abort the request signal when the response finishes normally', as expect(request.signal.aborted).toBe(false); }); +test('responds with a 500 when Node rejects a header, instead of leaving the request open', async () => { + const res = /** @type {any} */ (create_response()); + const finished = once(res, 'finish'); + + setResponse(res, new Response('{}', { headers: { 'x-test': '\u001f' } })); + + await finished; + expect(res.statusCode).toBe(500); + expect(res.headers.size).toBe(0); + expect(Buffer.concat(res.chunks.map(Buffer.from)).toString()).toMatch('ERR_INVALID_CHAR'); +}); + test('sends fixed response bodies with a content-length', async () => { const res = /** @type {any} */ (create_response()); const finished = once(res, 'finish'); diff --git a/packages/kit/test/apps/basics/package.json b/packages/kit/test/apps/basics/package.json index ee230f92f808..726625db498c 100644 --- a/packages/kit/test/apps/basics/package.json +++ b/packages/kit/test/apps/basics/package.json @@ -8,8 +8,8 @@ "preview": "vite preview", "check": "svelte-kit sync && node node_modules/@typescript/native/bin/tsc && svelte-check --tsgo --incremental", "test": "pnpm test:dev && pnpm test:build", - "test:dev": "node test/setup.js && DEV=true playwright test", - "test:build": "node test/setup.js && PUBLIC_PRERENDERING=false playwright test", + "test:dev": "node test/setup.js && DEV=true playwright test && KIT_TEST_DEV=true vitest run -c vitest.server.config.js", + "test:build": "node test/setup.js && PUBLIC_PRERENDERING=false playwright test && vitest run -c vitest.server.config.js", "test:cross-platform:dev": "pnpm test:client-import && node test/setup.js && DEV=true playwright test test/cross-platform/", "test:cross-platform:build": "node test/setup.js && playwright test test/cross-platform/", "test:server-side-route-resolution:dev": "node test/setup.js && DEV=true ROUTER_RESOLUTION=server playwright test", @@ -25,6 +25,7 @@ "@sveltejs/kit": "workspace:^", "@sveltejs/vite-plugin-svelte": "catalog:", "@vitest/browser-playwright": "catalog:", + "jsdom": "catalog:", "svelte": "catalog:", "svelte-check": "catalog:", "test-redirect-importer": "workspace:*", diff --git a/packages/kit/test/apps/basics/src/routes/endpoint-output/fetch-asset/absolute/+server.js b/packages/kit/test/apps/basics/src/routes/endpoint-output/fetch-asset/absolute/+server.js deleted file mode 100644 index 9f1b32595f58..000000000000 --- a/packages/kit/test/apps/basics/src/routes/endpoint-output/fetch-asset/absolute/+server.js +++ /dev/null @@ -1,14 +0,0 @@ -import file_path from '../test.txt?url'; - -/** @type {import('@sveltejs/kit').RequestHandler} */ -export async function GET({ url }) { - const absolute = new URL(file_path, url.origin); - - const response = await fetch(absolute); - - return new Response(response.body, { - headers: { - 'Content-Type': response.headers.get('Content-Type') ?? '' - } - }); -} diff --git a/packages/kit/test/apps/basics/src/routes/endpoint-output/head-write-error/+server.js b/packages/kit/test/apps/basics/src/routes/endpoint-output/head-write-error/+server.js deleted file mode 100644 index 72664eee7dcc..000000000000 --- a/packages/kit/test/apps/basics/src/routes/endpoint-output/head-write-error/+server.js +++ /dev/null @@ -1,10 +0,0 @@ -import { json } from '@sveltejs/kit'; - -/** @type {import('@sveltejs/kit').RequestHandler} */ -export function GET({ setHeaders }) { - setHeaders({ - 'x-test': '\u001f' - }); - - return json({}); -} diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index 8232717f4d8f..7973f6ad39a2 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -1,10 +1,6 @@ import process from 'node:process'; import { expect } from '@playwright/test'; import { test } from '../../../utils.js'; -import { createHash, randomBytes } from 'node:crypto'; -import fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import path from 'node:path'; /** @typedef {import('@playwright/test').Response} Response */ @@ -12,232 +8,7 @@ test.skip(({ javaScriptEnabled }) => javaScriptEnabled); test.describe.configure({ mode: 'parallel' }); -test.describe('Caching', () => { - test('caches pages', async ({ request }) => { - const response = await request.get('/caching'); - expect(response.headers()['cache-control']).toBe('public, max-age=30'); - }); -}); - -test.describe('Content-Type', () => { - test('sets Content-Type on page', async ({ request }) => { - const response = await request.get('/content-type-header'); - expect(response.headers()['content-type']).toBe('text/html'); - }); -}); - -test.describe('Content-Length', () => { - test('sets Content-Length on page', async ({ request }) => { - const response = await request.get('/content-length-header'); - - // TODO this would ideally be a unit test of `Server`, - // as would most of the tests in this file - if (!response.headers()['content-encoding']) { - expect(+response.headers()['content-length']).toBeGreaterThan(1000); - } - }); -}); - -test.describe('Cookies', () => { - test('does not forward cookies from external domains', async ({ request, start_server }) => { - const { port } = await start_server(async (req, res) => { - if (req.url === '/') { - res.writeHead(200, { - 'set-cookie': 'external=true', - 'access-control-allow-origin': '*' - }); - - res.end('ok'); - } else { - res.writeHead(404); - res.end('not found'); - } - }); - - const response = await request.get(`/load/fetch-external-no-cookies?port=${port}`); - expect(response.headers()['set-cookie']).not.toContain('external=true'); - }); -}); - -test.describe('CSRF', () => { - if (process.env.DEV) { - return; - } - - test('Blocks requests with incorrect origin', async ({ baseURL }) => { - const content_types = [ - 'application/x-www-form-urlencoded', - 'multipart/form-data', - 'text/plain', - 'text/plaiN' - ]; - const methods = ['POST', 'PUT', 'PATCH', 'DELETE']; - for (const method of methods) { - for (const content_type of content_types) { - const res = await fetch(`${baseURL}/csrf`, { - method, - headers: { - 'content-type': content_type - } - }); - const message = `request method: ${method}, content-type: ${content_type}`; - expect(res.status, message).toBe(403); - expect(await res.text(), message).toBe( - `Cross-site ${method} form submissions are forbidden` - ); - } - } - }); - - test('Allows requests from same origin', async ({ baseURL }) => { - const url = new URL(baseURL); - const res = await fetch(`${baseURL}/csrf`, { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - origin: url.origin - } - }); - expect(res.status).toBe(200); - expect(await res.text()).toBe('ok'); - }); - - test('Allows requests from allowed origins', async ({ baseURL }) => { - // Test with trusted.example.com which is in trustedOrigins - const res1 = await fetch(`${baseURL}/csrf`, { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - origin: 'https://trusted.example.com' - } - }); - expect(res1.status).toBe(200); - expect(await res1.text()).toBe('ok'); - - // Test with payment-gateway.test which is also in trustedOrigins - const res2 = await fetch(`${baseURL}/csrf`, { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - origin: 'https://payment-gateway.test' - } - }); - expect(res2.status).toBe(200); - expect(await res2.text()).toBe('ok'); - }); - - test('Blocks requests from non-allowed origins', async ({ baseURL }) => { - // Test with origin not in trustedOrigins list - const res1 = await fetch(`${baseURL}/csrf`, { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - origin: 'https://malicious-site.com' - } - }); - expect(res1.status).toBe(403); - expect(await res1.text()).toBe('Cross-site POST form submissions are forbidden'); - - // Test with similar but not exact origin - const res2 = await fetch(`${baseURL}/csrf`, { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - origin: 'https://trusted.example.com.evil.com' - } - }); - expect(res2.status).toBe(403); - expect(await res2.text()).toBe('Cross-site POST form submissions are forbidden'); - - // Test subdomain attack (should be blocked) - const res3 = await fetch(`${baseURL}/csrf`, { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - origin: 'https://evil.trusted.example.com' - } - }); - expect(res3.status).toBe(403); - expect(await res3.text()).toBe('Cross-site POST form submissions are forbidden'); - }); - - test('Allows GET requests regardless of origin', async ({ baseURL }) => { - const res = await fetch(`${baseURL}/csrf`, { - method: 'GET', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - origin: 'https://any-origin.com' - } - }); - expect(res.status).toBe(200); - }); - - test('Allows non-form content types regardless of origin', async ({ baseURL }) => { - const res = await fetch(`${baseURL}/csrf`, { - method: 'POST', - headers: { - 'content-type': 'application/json', - origin: 'https://any-origin.com' - } - }); - expect(res.status).toBe(200); - }); - - test('Allows all protected HTTP methods from allowed origins', async ({ baseURL }) => { - const methods = ['POST', 'PUT', 'PATCH', 'DELETE']; - for (const method of methods) { - const res = await fetch(`${baseURL}/csrf`, { - method, - headers: { - 'content-type': 'application/x-www-form-urlencoded', - origin: 'https://trusted.example.com' - } - }); - expect(res.status, `Method ${method} should be allowed from trusted origin`).toBe(200); - expect(await res.text(), `Method ${method} should return ok`).toBe('ok'); - } - }); - - test('Handles undefined origin correctly', async ({ baseURL }) => { - // Some requests may have null origin (e.g., from certain mobile apps) - const res = await fetch(`${baseURL}/csrf`, { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded' - } - }); - expect(res.status).toBe(403); - expect(await res.text()).toBe('Cross-site POST form submissions are forbidden'); - }); -}); - test.describe('Endpoints', () => { - test('HEAD with matching headers but without body', async ({ request }) => { - const url = '/endpoint-output/body'; - - const responses = { - head: await request.head(url), - get: await request.get(url) - }; - - const headers = { - head: responses.head.headers(), - get: responses.get.headers() - }; - - expect(responses.head.status()).toBe(200); - expect(responses.get.status()).toBe(200); - expect(await responses.head.text()).toBe(''); - expect(await responses.get.text()).toBe('{}'); - - ['date', 'transfer-encoding'].forEach((name) => { - delete headers.head[name]; - delete headers.get[name]; - }); - - expect(headers.head).toEqual(headers.get); - }); - test('Prerendered +server.js called from a non-prerendered +server.js works', async ({ baseURL }) => { @@ -270,296 +41,13 @@ test.describe('Endpoints', () => { } }); - test('invalid request method returns allow header', async ({ request }) => { - const response = await request.post('/endpoint-output/body', { - headers: { origin: 'https://trusted.example.com' } - }); - - expect(response.status()).toBe(405); - - const allow_header = response.headers()['allow']; - expect(allow_header).toMatch(/\bGET\b/); - expect(allow_header).toMatch(/\bHEAD\b/); - }); - - test('405 allow header has no duplicate methods listed', async ({ request }) => { - const response = await request.post('/endpoint-output/head-handler', { - headers: { origin: 'https://trusted.example.com' } - }); - - expect(response.status()).toBe(405); - - const allow_header = response.headers()['allow']; - const methods = allow_header.split(',').map((m) => m.trim()); - const unique_methods = [...new Set(methods)]; - - expect(methods).toEqual(unique_methods); - }); - - test('serves page for GET request when endpoint has no GET handler', async ({ request }) => { - const response = await request.fetch('/endpoint-output/post-only-with-page', { - headers: { - accept: '*/*' - } - }); - - expect(response.status()).toBe(200); - expect(response.headers()['content-type']).toContain('text/html'); - expect(await response.text()).toContain('POST-only endpoint page'); - }); - - test('serves page for HEAD request when endpoint has no HEAD or GET handler', async ({ - request - }) => { - const response = await request.fetch('/endpoint-output/post-only-with-page', { - method: 'HEAD', - headers: { - accept: '*/*' - } - }); - - expect(response.status()).toBe(200); - expect(response.headers()['content-type']).toContain('text/html'); - expect(response.headers()['x-sveltekit-page']).toBe('true'); - expect(await response.text()).toBe(''); - }); - - test('POST to post-only endpoint with sibling page still hits endpoint', async ({ request }) => { - const response = await request.post('/endpoint-output/post-only-with-page', { - headers: { origin: 'https://trusted.example.com' } - }); - - expect(response.status()).toBe(200); - expect(await response.text()).toBe('ok'); - }); - - test('uses fallback handler instead of page when endpoint has no GET but has fallback', async ({ - request - }) => { - const response = await request.fetch('/endpoint-output/fallback-with-page', { - headers: { - accept: '*/*' - } - }); - - expect(response.status()).toBe(200); - expect(await response.text()).toBe('catch-all'); - }); - - test('content negotiation: API requests hit endpoint, browser requests hit page', async ({ - request - }) => { - // application/json → endpoint - const api_response = await request.fetch('/routing/content-negotiation', { - headers: { - accept: 'application/json' - } - }); - - expect(api_response.status()).toBe(200); - expect(await api_response.text()).toBe('GET'); - - // text/html → page - const html_response = await request.fetch('/routing/content-negotiation', { - headers: { - accept: 'text/html' - } - }); - - expect(html_response.status()).toBe(200); - expect(html_response.headers()['content-type']).toContain('text/html'); - expect(await html_response.text()).toContain('Hi'); - }); - // TODO all the remaining tests in this section are really only testing // setResponse, since we're not otherwise changing anything on the response. // might be worth making these unit tests instead - test('multiple set-cookie on endpoints using GET', async ({ request }) => { - const response = await request.get('/set-cookie'); - - const cookies = response - .headersArray() - .filter((obj) => obj.name === 'set-cookie') - .map((obj) => obj.value); - - expect(cookies).toEqual([ - 'answer=42; HttpOnly', - 'problem=comma, separated, values; HttpOnly', - 'name=SvelteKit; path=/; HttpOnly' - ]); - }); - - // TODO see above - test('body can be a binary ReadableStream', async ({ request }) => { - const interruptedResponse = request.get('/endpoint-output/stream-throw-error'); - await expect(interruptedResponse).rejects.toThrow('socket hang up'); - - const response = await request.get('/endpoint-output/stream'); - const body = await response.body(); - const digest = createHash('sha256').update(body).digest('base64url'); - expect(response.headers()['digest']).toEqual(`sha-256=${digest}`); - }); - - // TODO see above - test('stream can be canceled with TypeError', async ({ request }) => { - const responseBefore = await request.get('/endpoint-output/stream-typeerror?what'); - expect(await responseBefore.text()).toEqual('null'); - - const interruptedResponse = request.get('/endpoint-output/stream-typeerror'); - await expect(interruptedResponse).rejects.toThrow('socket hang up'); - - const responseAfter = await request.get('/endpoint-output/stream-typeerror?what'); - expect(await responseAfter.text()).toEqual('TypeError'); - }); - // TODO see above - test('request body can be read slow', async ({ request }) => { - const data = randomBytes(1024 * 256); - const digest = createHash('sha256').update(data).digest('base64url'); - const response = await request.put('/endpoint-input/sha256', { data }); - expect(await response.text()).toEqual(digest); - }); - - // TODO see above - test('invalid headers return a 500', async ({ request }) => { - const response = await request.get('/endpoint-output/head-write-error'); - expect(response.status()).toBe(500); - expect(await response.text()).toMatch( - 'TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["x-test"]' - ); - }); - - test('OPTIONS handler', async ({ request }) => { - const url = '/endpoint-output'; - - const response = await request.fetch(url, { - method: 'OPTIONS' - }); - - expect(response.status()).toBe(200); - expect(await response.text()).toBe('ok'); - }); - - test('HEAD handler', async ({ request }) => { - const url = '/endpoint-output/head-handler'; - - const page_response = await request.fetch(url, { - method: 'HEAD', - headers: { - accept: 'text/html' - } - }); - - expect(page_response.status()).toBe(200); - expect(await page_response.text()).toBe(''); - expect(page_response.headers()['x-sveltekit-page']).toBe('true'); - - const endpoint_response = await request.fetch(url, { - method: 'HEAD', - headers: { - accept: 'application/json' - } - }); - - expect(endpoint_response.status()).toBe(200); - expect(await endpoint_response.text()).toBe(''); - expect(endpoint_response.headers()['x-sveltekit-head-endpoint']).toBe('true'); - }); - - test('catch-all handler', async ({ request }) => { - const url = '/endpoint-output/fallback'; - - let response = await request.fetch(url, { - method: 'GET' - }); - - expect(response.status()).toBe(200); - expect(await response.text()).toBe('ok'); - - response = await request.fetch(url, { - method: 'MOVE' // also works with arcane methods - }); - - expect(response.status()).toBe(200); - expect(await response.text()).toBe('catch-all'); - - response = await request.fetch(url, { - method: 'OPTIONS' - }); - - expect(response.status()).toBe(200); - expect(await response.text()).toBe('catch-all'); - }); - - test('QUERY handler', async ({ request }) => { - const url = '/endpoint-output/query'; - - let response = await request.fetch(url, { - method: 'QUERY', - data: 'name=world' - }); - - expect(response.status()).toBe(200); - expect(await response.text()).toBe('query: name=world'); - - response = await request.fetch(url, { - method: 'MOVE' - }); - - expect(response.status()).toBe(405); - expect(response.headers()['allow']).toBe('GET, QUERY, HEAD'); - }); - - test('can get assets using absolute path', async ({ request }) => { - const response = await request.get('/endpoint-output/fetch-asset/absolute'); - expect(response.status()).toBe(200); - expect(response.headers()['content-type']).toBe('text/plain'); - expect(await response.text()).toBe('Cos sie konczy, cos zaczyna'); - }); - - test('can get assets using relative path', async ({ request }) => { - const response = await request.get('/endpoint-output/fetch-asset/relative'); - expect(response.status()).toBe(200); - expect(response.headers()['content-type']).toBe('text/plain'); - expect(await response.text()).toBe('Cos sie konczy, cos zaczyna'); - }); }); test.describe('Errors', () => { - test('uses the handleError status for the fallback page served to error-page sub-requests', async ({ - request - }) => { - const response = await request.get('/errors/handle-error-status-fallback', { - headers: { 'x-sveltekit-error': 'true' } - }); - expect(response.status()).toBe(503); - }); - - test('invalid route response is handled', async ({ request }) => { - const response = await request.get('/errors/invalid-route-response'); - - expect(/** @type {import('@playwright/test').APIResponse} */ (response).status()).toBe(500); - expect(await response.text()).toMatch( - 'Invalid response from route /errors/invalid-route-response: handler should return a Response object' - ); - }); - - test('unhandled http method', async ({ request }) => { - const response = await request.put('/errors/invalid-route-response', { - headers: { origin: 'https://trusted.example.com' } - }); - - expect(response.status()).toBe(405); - expect(await response.text()).toMatch('PUT method not allowed'); - }); - - test('error evaluating module', async ({ request }) => { - const response = await request.get('/errors/init-error-endpoint'); - - expect(response.status()).toBe(500); - expect(await response.text()).toMatch('thisvariableisnotdefined is not defined'); - }); - test('returns 400 when accessing a malformed URI', async ({ page }) => { const response = await page.goto('/%c0%ae%c0%ae/etc/passwd'); if (process.env.DEV) { @@ -570,348 +58,9 @@ test.describe('Errors', () => { expect(/** @type {Response} */ (response).status()).toBe(400); } }); - - test('returns a lightweight 404 for subresource requests', async ({ request, read_errors }) => { - // distinct from the path used by the test below, which _does_ invoke the hook - const response = await request.get('/errors/does-not-exist-lightweight-subresource', { - headers: { 'sec-fetch-dest': 'image' } - }); - - expect(response.status()).toBe(404); - expect(await response.text()).toBe('Not Found'); - expect(response.headers()['vary']).toContain('Sec-Fetch-Dest'); - - // lightweight 404s bypass the handleError hook - expect(read_errors('/errors/does-not-exist-lightweight-subresource')).toBe(undefined); - }); - - test('renders the error page for document and fetch requests', async ({ request }) => { - for (const destination of ['document', null, 'empty']) { - const response = await request.get( - '/errors/does-not-exist-subresource', - destination ? { headers: { 'sec-fetch-dest': destination } } : {} - ); - - expect(response.status()).toBe(404); - expect(await response.text()).toContain('This is your custom error page saying:'); - } - }); - - test('stack traces are not fixed twice', async ({ page }) => { - await page.goto('/errors/stack-trace'); - expect(await page.textContent('#message')).toBe( - 'This is your custom error page saying: "Cannot read properties of undefined (reading \'toUpperCase\') (500 Internal Error)"' - ); - - // check the stack wasn't mutated - await page.goto('/errors/stack-trace'); - expect(await page.textContent('#message')).toBe( - 'This is your custom error page saying: "Cannot read properties of undefined (reading \'toUpperCase\') (500 Internal Error)"' - ); - }); - - test('error(...) in endpoint', async ({ request, read_errors }) => { - // HTML - { - const res = await request.get('/errors/endpoint-throw-error', { - headers: { - accept: 'text/html' - } - }); - - expect(read_errors('/errors/endpoint-throw-error')).toEqual({ - kind: 'app', - error: { status: 401, message: 'You shall not pass' } - }); - - expect(res.status()).toBe(401); - expect(await res.text()).toContain( - 'This is the static error page with the following message: You shall not pass' - ); - } - - // JSON (default) - { - const res = await request.get('/errors/endpoint-throw-error'); - - expect(read_errors('/errors/endpoint-throw-error')).toEqual({ - kind: 'app', - error: { status: 401, message: 'You shall not pass' } - }); - - expect(res.status()).toBe(401); - expect(await res.json()).toEqual({ - message: 'You shall not pass', - status: 401 - }); - } - }); - - test('redirect(...) in endpoint', async ({ page, read_errors }) => { - const res = await page.goto('/errors/endpoint-throw-redirect'); - expect(res?.status()).toBe(200); // redirects are opaque to the browser - - const error = read_errors('/errors/endpoint-throw-redirect'); - expect(error).toBe(undefined); - - expect(await page.textContent('h1')).toBe('the answer is 42'); - }); - - test('POST to missing page endpoint', async ({ request }) => { - const res = await request.post('/errors/missing-actions', { - headers: { - accept: 'text/html', - origin: 'https://trusted.example.com' - } - }); - expect(res?.status()).toBe(405); - - const res_json = await request.post('/errors/missing-actions', { - headers: { - accept: 'application/json', - origin: 'https://trusted.example.com' - } - }); - expect(res_json?.status()).toBe(405); - expect(await res_json.json()).toEqual({ - type: 'error', - location: '/errors/missing-actions', - error: { - message: 'Method Not Allowed (405 Method Not Allowed)', - status: 405 - } - }); - }); - - test('error thrown in handle results in a rendered error page or JSON response', async ({ - request - }) => { - // HTML - { - const res = await request.get('/errors/error-in-handle', { - headers: { - accept: 'text/html' - } - }); - - expect(res.status()).toBe(500); - expect(await res.text()).toContain( - 'This is the static error page with the following message: Error in handle' - ); - } - - // JSON (default) - { - const res = await request.get('/errors/error-in-handle'); - - const error = await res.json(); - - expect(error.stack).toBe(undefined); - expect(res.status()).toBe(500); - expect(error).toEqual({ - message: 'Error in handle (500 Internal Error)', - status: 500 - }); - } - }); - - test('expected error thrown in handle results in a rendered error page or JSON response', async ({ - request - }) => { - // HTML - { - const res = await request.get('/errors/expected-error-in-handle', { - headers: { - accept: 'text/html' - } - }); - - expect(res.status()).toBe(500); - expect(await res.text()).toContain( - 'This is the static error page with the following message: Expected error in handle' - ); - } - - // JSON (default) - { - const res = await request.get('/errors/expected-error-in-handle'); - - const error = await res.json(); - - expect(error.stack).toBe(undefined); - expect(res.status()).toBe(500); - expect(error).toEqual({ - message: 'Expected error in handle', - status: 500 - }); - } - }); - - test('error thrown from load on the server respects page options when rendering the error page', async ({ - request - }) => { - const res = await request.get('/errors/load-error-page-options/csr'); - expect(res.status()).toBe(500); - const content = await res.text(); - expect(content).toContain('Crashing now'); - // the hydration script should not be present if the csr page option is respected - expect(content).not.toContain('kit.start(app'); - }); - - test('returns root layout data for a missing route error page data request', async ({ - request - }) => { - const data_response = await request.get( - '/this-route-does-not-exist/__data.json?x-sveltekit-invalidated=1' - ); - expect(data_response.status()).toBe(200); - expect(data_response.headers()['content-type']).toContain('application/json'); - - const data = await data_response.json(); - expect(data.type).toBe('data'); - expect(data.nodes[0].type).toBe('data'); - expect(data.nodes[0].data).toContain('rootlayout'); - - const page_response = await request.get('/this-route-does-not-exist/__data.json'); - expect(page_response.status()).toBe(404); - expect(page_response.headers()['content-type']).toContain('text/html'); - - // a single-node request that invalidates nothing is not an error-page data request - const crafted_response = await request.get( - '/this-route-does-not-exist/__data.json?x-sveltekit-invalidated=0' - ); - expect(crafted_response.status()).toBe(404); - }); - - test('preserves metadata for a streamed missing route error page', async ({ request }) => { - const response = await request.get('/non-existent-streamed-route', { - headers: { cookie: 'defer=true' } - }); - - expect(response.status()).toBe(404); - expect(response.headers()['etag']).toBeUndefined(); - - const body = await response.text(); - expect(body).toMatch(/]*>404<\/h1>/); - expect(body).toContain('This is your custom error page'); - }); -}); - -test.describe('Load', () => { - test('fetching a non-existent resource in root layout fails without hanging', async ({ - request - }) => { - const response = await request.get('/errors/error-in-layout'); - expect(await response.text()).toContain('Error: 404'); - }); - - test('fetch does not load a file with a # character', async ({ request }) => { - const response = await request.get('/load/static-file-with-hash'); - expect(await response.text()).toContain('status: 404'); - }); - - test('fetch reads universal load assets on the server', async ({ page }) => { - await page.goto('/load/fetch-asset'); - await expect(page.locator('p')).toHaveText('1'); - }); - - test('does not forward accept-language to internal fetch when the request has none', async ({ - request - }) => { - // unlike browsers and undici, the `request` fixture sends no accept-language header - const html = await (await request.get('/load/fetch-request-headers')).text(); - const headers = JSON.parse( - /** @type {RegExpMatchArray} */ (/
(.+?)<\/pre>/s.exec(html))[1]
-		);
-		expect(headers.accept).toBe('*/*');
-		expect(headers['accept-language']).toBeUndefined();
-	});
-
-	test('includes origin header on non-GET internal request', async ({ page, baseURL }) => {
-		await page.goto('/load/fetch-origin-internal');
-		expect(await page.textContent('h1')).toBe(`origin: ${new URL(baseURL).origin}`);
-	});
-
-	test('includes origin header on external request', async ({ page, baseURL, start_server }) => {
-		const { port } = await start_server((req, res) => {
-			if (req.url === '/') {
-				res.writeHead(200, {
-					'content-type': 'application/json',
-					'access-control-allow-origin': '*'
-				});
-
-				res.end(JSON.stringify({ origin: req.headers.origin }));
-			} else {
-				res.writeHead(404);
-				res.end('not found');
-			}
-		});
-
-		await page.goto(`/load/fetch-origin-external?port=${port}`);
-		expect(await page.textContent('h1')).toBe(`origin: ${new URL(baseURL).origin}`);
-	});
-
-	test('does not run when using invalid request methods', async ({ request }) => {
-		const load_url = '/load';
-
-		let response = await request.fetch(load_url, {
-			method: 'OPTIONS'
-		});
-
-		expect(response.status()).toBe(204);
-		expect(await response.text()).toBe('');
-		expect(response.headers()['allow']).toBe('GET, HEAD, OPTIONS');
-
-		const actions_url = '/actions/enhance';
-		response = await request.fetch(actions_url, {
-			method: 'OPTIONS'
-		});
-
-		expect(response.status()).toBe(204);
-		expect(await response.text()).toBe('');
-		expect(response.headers()['allow']).toBe('GET, HEAD, OPTIONS, POST');
-	});
-
-	test('allows logging URL search params', async ({ page }) => {
-		await page.goto('/load/server-log-search-param');
-
-		expect(await page.textContent('p')).toBe('hello world');
-	});
 });
 
 test.describe('Routing', () => {
-	test('event.params are available in handle', async ({ request }) => {
-		const response = await request.get('/routing/params-in-handle/banana');
-		expect(await response.json()).toStrictEqual({
-			key: '/routing/params-in-handle/[x]',
-			params: { x: 'banana' }
-		});
-	});
-
-	test('/favicon.ico is a valid route', async ({ request }) => {
-		const response = await request.get('/favicon.ico');
-		expect(response.status()).toBe(200);
-
-		const data = await response.json();
-		expect(data).toEqual({ surprise: 'lol' });
-	});
-
-	test('falls back to page actions if sibling endpoint has no POST handler', async ({
-		baseURL,
-		request
-	}) => {
-		const response = await request.post('/endpoint-output/actions-with-endpoint', {
-			form: {},
-			headers: {
-				accept: 'application/json',
-				origin: new URL(baseURL).origin
-			}
-		});
-
-		expect(response.status()).toBe(200);
-		expect((await response.json()).type).toBe('success');
-	});
-
 	test('Vite trailing slash redirect for prerendered pages retains URL query string', async ({
 		request
 	}) => {
@@ -928,61 +77,6 @@ test.describe('Routing', () => {
 	});
 });
 
-test.describe('Shadowed pages', () => {
-	test('Action can return undefined', async ({ baseURL, request }) => {
-		const response = await request.post('/shadowed/simple/post', {
-			form: {},
-			headers: {
-				accept: 'application/json',
-				origin: new URL(baseURL).origin
-			}
-		});
-
-		expect(response.status()).toBe(200);
-		expect(await response.json()).toEqual({
-			type: 'success',
-			status: 204,
-			location: '/shadowed/simple/post'
-		});
-	});
-
-	test('action response includes the stripped landing location', async ({ baseURL, request }) => {
-		const response = await request.post(
-			'/actions/cross-page/destination?redirectTo=%2Fdashboard&/failure',
-			{
-				form: { username: 'paolo' },
-				headers: {
-					accept: 'application/json',
-					origin: new URL(baseURL).origin
-				}
-			}
-		);
-
-		expect(response.status()).toBe(400);
-		expect(await response.json()).toEqual({
-			type: 'failure',
-			status: 400,
-			location: '/actions/cross-page/destination?redirectTo=%2Fdashboard',
-			data: '[{"problem":1,"username":2},"invalid","paolo"]'
-		});
-	});
-
-	test('Action fail() returns matching HTTP status code', async ({ baseURL, request }) => {
-		const response = await request.post('/actions/form-errors', {
-			form: {},
-			headers: {
-				accept: 'application/json',
-				origin: new URL(baseURL).origin
-			}
-		});
-
-		expect(response.status()).toBe(400);
-		const body = await response.json();
-		expect(body.type).toBe('failure');
-		expect(body.status).toBe(400);
-	});
-});
-
 test.describe('Static files', () => {
 	test('static files', async ({ request }) => {
 		let response = await request.get('/static.json');
@@ -1038,27 +132,6 @@ test.describe('Static files', () => {
 	});
 });
 
-test.describe('setHeaders', () => {
-	test('allows multiple set-cookie headers with different values', async ({ page }) => {
-		const response = await page.goto('/headers/set-cookie/sub');
-		const cookies = response ? (await response.allHeaders())['set-cookie'] : '';
-
-		expect(cookies).toMatch('cookie1=value1');
-		expect(cookies).toMatch('cookie2=value2');
-	});
-});
-
-test.describe('cookies', () => {
-	test('cookie.stringifySetCookie created correct cookie header string', async ({ page }) => {
-		const response = await page.goto('/cookies/serialize');
-		const cookies = response ? await response.headerValue('set-cookie') : '';
-
-		expect(cookies).toMatch('before=before');
-		expect(cookies).toMatch('after=after');
-		expect(cookies).toMatch('endpoint=endpoint');
-	});
-});
-
 test.describe('Miscellaneous', () => {
 	test('does not serve version.json with an immutable cache header', async ({ request }) => {
 		// this isn't actually a great test, because caching behaviour is down to adapters.
@@ -1068,46 +141,13 @@ test.describe('Miscellaneous', () => {
 		expect(headers['cache-control'] || '').not.toContain('immutable');
 	});
 
-	test('handles responses with immutable headers', async ({ request }) => {
-		const response = await request.get('/immutable-headers');
-		expect(response.status()).toBe(200);
-		expect(await response.text()).toBe('foo');
-	});
-
 	test('serves prerendered non-latin pages', async ({ request }) => {
 		const response = await request.get('/prerendering/中文');
 		expect(response.status()).toBe(200);
 	});
-
-	test('does not send x-sveltekit-version header on document responses', async ({ page }) => {
-		const response = await page.goto('/');
-		expect(response?.headers()['x-sveltekit-version']).toBeUndefined();
-	});
-
-	test('sends x-sveltekit-version header on data responses', async ({ request }) => {
-		const response = await request.get('/__data.json');
-		expect(response.headers()['x-sveltekit-version']).toBeTruthy();
-	});
 });
 
 test.describe('reroute', () => {
-	test('Apply reroute when directly accessing a page', async ({ page }) => {
-		await page.goto('/reroute/basic/a');
-		expect(await page.textContent('h1')).toContain(
-			'Successfully rewritten, URL should still show a: /reroute/basic/a'
-		);
-	});
-
-	test('Apply async reroute when directly accessing a page', async ({ page }) => {
-		page
-			.context()
-			.addCookies([{ name: 'reroute-cookie', value: 'yes', path: '/', domain: 'localhost' }]);
-		await page.goto('/reroute/async/a');
-		expect(await page.textContent('h1')).toContain(
-			'Successfully rewritten, URL should still show a: /reroute/async/a'
-		);
-	});
-
 	test('Apply async prerendered reroute when directly accessing a page', async ({ page }) => {
 		await page.goto('/reroute/async/c');
 		expect(await page.textContent('h1')).toContain(
@@ -1119,564 +159,4 @@ test.describe('reroute', () => {
 		await page.goto('/reroute/prerendered/to-destination');
 		expect(await page.textContent('h1')).toContain('reroute that points to prerendered page works');
 	});
-
-	test('Returns a 500 response if reroute throws an error on the server', async ({ page }) => {
-		const response = await page.goto('/reroute/error-handling/server-error');
-		expect(response?.status()).toBe(500);
-	});
-});
-
-test.describe('init', () => {
-	test('init server hook is called once before the load function', async ({ page }) => {
-		await page.goto('/init-hooks');
-		await expect(page.locator('p')).toHaveText('1');
-		await page.reload();
-		await expect(page.locator('p')).toHaveText('1');
-	});
-});
-
-test.describe('getRequestEvent', () => {
-	test('getRequestEvent works in server endpoints', async ({ request }) => {
-		const response = await request.get('/get-request-event/endpoint');
-		expect(await response.text()).toBe('hello from hooks.server.js');
-	});
-});
-
-test.describe('$app/forms', () => {
-	test('deserialize works on the server', async ({ request }) => {
-		const response = await request.get('/serialization-form-non-enhanced/server-deserialize');
-		expect(await response.json()).toEqual({ data: 'It works!' });
-	});
-});
-
-const root = path.resolve(fileURLToPath(import.meta.url), '..', '..');
-
-test.describe('$app/env', () => {
-	test('treeshakes dev check', async () => {
-		test.skip(!!process.env.DEV, 'skip when in dev mode');
-
-		const code = fs.readFileSync(
-			path.join(root, '.svelte-kit/output/server/entries/pages/treeshaking/dev/_page.svelte.js'),
-			'utf-8'
-		);
-		expect(code).not.toContain('not prod');
-	});
-
-	test('treeshakes browser check', async () => {
-		test.skip(!!process.env.DEV, 'skip when in dev mode');
-
-		const code = fs.readFileSync(
-			path.join(
-				root,
-				'.svelte-kit/output/server/entries/pages/treeshaking/browser/_page.svelte.js'
-			),
-			'utf-8'
-		);
-		expect(code).not.toMatch('client');
-	});
-});
-
-test.describe('web workers', () => {
-	test('worker files emitted by the server build are copied to the client output', () => {
-		test.skip(!!process.env.DEV, 'checks the build output');
-
-		const workers = path.join(root, '.svelte-kit/output/client/_app/immutable/workers');
-		expect(fs.readdirSync(workers).some((file) => file.startsWith('worker-'))).toBe(true);
-	});
-});
-
-test.describe('tracing', () => {
-	// Helper function to find the resolve.root span deep in the handle.child chain
-	/**
-	 * @param {import('../../../types.js').SpanTree} span
-	 * @returns {import('../../../types.js').SpanTree | null}
-	 */
-	function find_resolve_root_span(span) {
-		if (span.name === 'sveltekit.resolve') {
-			return span;
-		}
-		for (const child of span.children || []) {
-			const found = find_resolve_root_span(child);
-			if (found) return found;
-		}
-		return null;
-	}
-
-	function rand() {
-		// node 18 doesn't have crypto.randomUUID() and we run tests in node 18
-		return Math.random().toString(36).substring(2, 15);
-	}
-
-	test('correct spans are created for a regular navigation', async ({ page, read_traces }) => {
-		const test_id = rand();
-		await page.goto(`/tracing/one/two/three/four/five?test_id=${test_id}`);
-		const traces = read_traces(test_id);
-		expect(traces.length).toBeGreaterThan(0);
-
-		const trace = traces[0];
-		const trace_id = trace.trace_id;
-
-		// Verify root span structure
-		expect(trace).toEqual({
-			name: 'sveltekit.handle.root',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/one/two/three/[...four]',
-				'http.method': 'GET',
-				'http.url': expect.stringContaining(`/tracing/one/two/three/four/five?test_id=${test_id}`),
-				'sveltekit.is_data_request': false,
-				'sveltekit.is_sub_request': false,
-				test_id
-			},
-			links: [],
-			trace_id,
-			span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
-					attributes: {}
-				})
-			])
-		});
-
-		// Find and verify the resolve.root span
-		const resolve_root_span = find_resolve_root_span(trace);
-		expect(resolve_root_span).not.toBeNull();
-		expect(resolve_root_span).toEqual({
-			name: 'sveltekit.resolve',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/one/two/three/[...four]',
-				'http.response.status_code': 200,
-				'http.response.body.size': expect.stringMatching(/^\d+$/)
-			},
-			links: [],
-			trace_id,
-			span_id: expect.any(String),
-			parent_span_id: expect.any(String),
-			children: [
-				{
-					name: 'sveltekit.load',
-					status: { code: 0 },
-					start_time: [expect.any(Number), expect.any(Number)],
-					end_time: [expect.any(Number), expect.any(Number)],
-					attributes: {
-						'sveltekit.load.node_id': 'src/routes/+layout.server.js',
-						'sveltekit.load.node_type': '+layout.server',
-						'sveltekit.load.environment': 'server',
-						'http.route': '/tracing/one/two/three/[...four]'
-					},
-					links: [],
-					trace_id,
-					span_id: expect.any(String),
-					parent_span_id: expect.any(String),
-					children: []
-				},
-				{
-					name: 'sveltekit.load',
-					status: { code: 0 },
-					start_time: [expect.any(Number), expect.any(Number)],
-					end_time: [expect.any(Number), expect.any(Number)],
-					attributes: {
-						'sveltekit.load.node_id': 'src/routes/+layout.js',
-						'sveltekit.load.node_type': '+layout',
-						'sveltekit.load.environment': 'server',
-						'http.route': '/tracing/one/two/three/[...four]'
-					},
-					links: [],
-					trace_id,
-					span_id: expect.any(String),
-					parent_span_id: expect.any(String),
-					children: []
-				}
-			]
-		});
-	});
-
-	test('correct spans are created for HttpError', async ({ page, read_traces }) => {
-		const test_id = rand();
-		const response = await page.goto(`/tracing/http-error?test_id=${test_id}`);
-		expect(response?.status()).toBe(500);
-
-		const traces = read_traces(test_id);
-		const trace_id = traces[0].trace_id;
-		const trace = traces[0];
-
-		// Verify root span structure
-		expect(trace).toEqual({
-			name: 'sveltekit.handle.root',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/http-error',
-				'http.method': 'GET',
-				'http.url': expect.stringContaining(`/tracing/http-error?test_id=${test_id}`),
-				'sveltekit.is_data_request': false,
-				'sveltekit.is_sub_request': false,
-				test_id
-			},
-			links: [],
-			trace_id,
-			span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
-					attributes: {}
-				})
-			])
-		});
-
-		// Find and verify the resolve.root span
-		const resolve_root_span = find_resolve_root_span(trace);
-		expect(resolve_root_span).not.toBeNull();
-		expect(resolve_root_span).toEqual({
-			name: 'sveltekit.resolve',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/http-error',
-				'http.response.status_code': 500,
-				'http.response.body.size': expect.stringMatching(/^\d+$/)
-			},
-			links: [],
-			trace_id,
-			span_id: expect.any(String),
-			parent_span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.load',
-					status: { code: 2, message: 'Internal server error from tracing test' },
-					attributes: expect.objectContaining({
-						'sveltekit.load.node_id': 'src/routes/tracing/http-error/+page.server.js',
-						'sveltekit.load.result.type': 'known_error',
-						'sveltekit.load.result.status': 500,
-						'sveltekit.load.result.message': 'Internal server error from tracing test'
-					})
-				})
-			])
-		});
-	});
-
-	test('correct spans are created for Redirect', async ({ page, read_traces }) => {
-		const test_id = rand();
-		const response = await page.goto(`/tracing/redirect?test_id=${test_id}`);
-		expect(response?.status()).toBe(200);
-
-		const traces = read_traces(test_id);
-		expect(traces).toHaveLength(2);
-		const redirect_trace_id = traces[0].trace_id;
-		const destination_trace_id = traces[1].trace_id;
-
-		const redirect_trace = traces[0];
-		const destination_trace = traces[1];
-
-		// Verify redirect trace root span structure
-		expect(redirect_trace).toEqual({
-			name: 'sveltekit.handle.root',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/redirect',
-				'http.method': 'GET',
-				'http.url': expect.stringContaining(`/tracing/redirect?test_id=${test_id}`),
-				'sveltekit.is_data_request': false,
-				'sveltekit.is_sub_request': false,
-				test_id
-			},
-			links: [],
-			trace_id: redirect_trace_id,
-			span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
-					attributes: {}
-				})
-			])
-		});
-
-		// Find and verify the redirect resolve.root span
-		const redirect_resolve_root_span = find_resolve_root_span(redirect_trace);
-		expect(redirect_resolve_root_span).not.toBeNull();
-		expect(redirect_resolve_root_span).toEqual({
-			name: 'sveltekit.resolve',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/redirect',
-				'http.response.status_code': 307,
-				'http.response.body.size': expect.stringMatching(/^\d+$|^unknown$/)
-			},
-			links: [],
-			trace_id: redirect_trace_id,
-			span_id: expect.any(String),
-			parent_span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.load',
-					status: { code: 0 },
-					attributes: expect.objectContaining({
-						'sveltekit.load.node_id': 'src/routes/tracing/redirect/+page.server.js',
-						'sveltekit.load.result.type': 'redirect',
-						'sveltekit.load.result.status': 307,
-						'sveltekit.load.result.location': `/tracing/one/two/three/four/five?test_id=${test_id}`
-					})
-				})
-			])
-		});
-
-		// Verify destination trace root span structure
-		expect(destination_trace).toEqual({
-			name: 'sveltekit.handle.root',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/one/two/three/[...four]',
-				'http.method': 'GET',
-				'http.url': expect.stringContaining(`/tracing/one/two/three/four/five?test_id=${test_id}`),
-				'sveltekit.is_data_request': false,
-				'sveltekit.is_sub_request': false,
-				test_id
-			},
-			links: [],
-			trace_id: destination_trace_id,
-			span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
-					attributes: {}
-				})
-			])
-		});
-
-		// Find and verify the destination resolve.root span
-		const destination_resolve_root_span = find_resolve_root_span(destination_trace);
-		expect(destination_resolve_root_span).not.toBeNull();
-		expect(destination_resolve_root_span).toEqual({
-			name: 'sveltekit.resolve',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/one/two/three/[...four]',
-				'http.response.status_code': 200,
-				'http.response.body.size': expect.stringMatching(/^\d+$/)
-			},
-			links: [],
-			trace_id: destination_trace_id,
-			span_id: expect.any(String),
-			parent_span_id: expect.any(String),
-			children: [
-				{
-					name: 'sveltekit.load',
-					status: { code: 0 },
-					start_time: [expect.any(Number), expect.any(Number)],
-					end_time: [expect.any(Number), expect.any(Number)],
-					attributes: {
-						'sveltekit.load.node_id': 'src/routes/+layout.server.js',
-						'sveltekit.load.node_type': '+layout.server',
-						'sveltekit.load.environment': 'server',
-						'http.route': '/tracing/one/two/three/[...four]'
-					},
-					links: [],
-					trace_id: destination_trace_id,
-					span_id: expect.any(String),
-					parent_span_id: expect.any(String),
-					children: []
-				},
-				{
-					name: 'sveltekit.load',
-					status: { code: 0 },
-					start_time: [expect.any(Number), expect.any(Number)],
-					end_time: [expect.any(Number), expect.any(Number)],
-					attributes: {
-						'sveltekit.load.node_id': 'src/routes/+layout.js',
-						'sveltekit.load.node_type': '+layout',
-						'sveltekit.load.environment': 'server',
-						'http.route': '/tracing/one/two/three/[...four]'
-					},
-					links: [],
-					trace_id: destination_trace_id,
-					span_id: expect.any(String),
-					parent_span_id: expect.any(String),
-					children: []
-				}
-			]
-		});
-	});
-
-	test('correct spans are created for regular Error', async ({ page, read_traces }) => {
-		const test_id = rand();
-		const response = await page.goto(`/tracing/regular-error?test_id=${test_id}`);
-		expect(response?.status()).toBe(500);
-
-		const traces = read_traces(test_id);
-		const trace_id = traces[0].trace_id;
-		const trace = traces[0];
-
-		// Verify root span structure
-		expect(trace).toEqual({
-			name: 'sveltekit.handle.root',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/regular-error',
-				'http.method': 'GET',
-				'http.url': expect.stringContaining(`/tracing/regular-error?test_id=${test_id}`),
-				'sveltekit.is_data_request': false,
-				'sveltekit.is_sub_request': false,
-				test_id
-			},
-			links: [],
-			trace_id,
-			span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
-					attributes: {}
-				})
-			])
-		});
-
-		// Find and verify the resolve.root span
-		const resolve_root_span = find_resolve_root_span(trace);
-		expect(resolve_root_span).not.toBeNull();
-		expect(resolve_root_span).toEqual({
-			name: 'sveltekit.resolve',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/regular-error',
-				'http.response.status_code': 500,
-				'http.response.body.size': expect.stringMatching(/^\d+$/)
-			},
-			links: [],
-			trace_id,
-			span_id: expect.any(String),
-			parent_span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.load',
-					status: { code: 2, message: 'Regular error from tracing test' },
-					attributes: expect.objectContaining({
-						'sveltekit.load.node_id': 'src/routes/tracing/regular-error/+page.server.js',
-						'sveltekit.load.result.type': 'unknown_error'
-					})
-				})
-			])
-		});
-	});
-
-	test('correct spans are created for non-error object', async ({ page, read_traces }) => {
-		const test_id = rand();
-		const response = await page.goto(`/tracing/non-error-object?test_id=${test_id}`);
-		expect(response?.status()).toBe(500);
-
-		const traces = read_traces(test_id);
-		const trace_id = traces[0].trace_id;
-		const trace = traces[0];
-
-		// Verify root span structure
-		expect(trace).toEqual({
-			name: 'sveltekit.handle.root',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/non-error-object',
-				'http.method': 'GET',
-				'http.url': expect.stringContaining(`/tracing/non-error-object?test_id=${test_id}`),
-				'sveltekit.is_data_request': false,
-				'sveltekit.is_sub_request': false,
-				test_id
-			},
-			links: [],
-			trace_id,
-			span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
-					attributes: {}
-				})
-			])
-		});
-
-		// Find and verify the resolve.root span
-		const resolve_root_span = find_resolve_root_span(trace);
-		expect(resolve_root_span).not.toBeNull();
-		expect(resolve_root_span).toEqual({
-			name: 'sveltekit.resolve',
-			status: { code: 0 },
-			start_time: [expect.any(Number), expect.any(Number)],
-			end_time: [expect.any(Number), expect.any(Number)],
-			attributes: {
-				'http.route': '/tracing/non-error-object',
-				'http.response.status_code': 500,
-				'http.response.body.size': expect.stringMatching(/^\d+$/)
-			},
-			links: [],
-			trace_id,
-			span_id: expect.any(String),
-			parent_span_id: expect.any(String),
-			children: expect.arrayContaining([
-				expect.objectContaining({
-					name: 'sveltekit.load',
-					status: { code: 2 },
-					attributes: expect.objectContaining({
-						'sveltekit.load.node_id': 'src/routes/tracing/non-error-object/+page.server.js',
-						'sveltekit.load.result.type': 'unknown_error'
-					})
-				})
-			])
-		});
-	});
-});
-
-test.describe('asset preload', () => {
-	test.skip(!!process.env.DEV);
-
-	test('does not inject Link headers', async ({ request }) => {
-		const response = await request.get('/asset-preload');
-
-		const header = response.headers()['link'];
-		expect(header).toBeUndefined();
-	});
-
-	test('injects  tags', async ({ request }) => {
-		const response = await request.get('/asset-preload');
-
-		const body = await response.text();
-
-		expect(body).toContain('rel="modulepreload"');
-		expect(body).toContain('as="font"');
-		expect(body).toMatch(/href="[^"]+\/shlop\.[^".]+\.woff2"/);
-		expect(body).toMatch(/href="[^"]+\/shlop\.var\.[^".]+\.woff2"/);
-		// the emitted file name is sanitized, but the filter matched on the original `shlop+bold.woff2`
-		expect(body).toMatch(/href="[^"]+\/shlop_bold\.[^".]+\.woff2"/);
-	});
-});
-
-test.describe('Streaming', () => {
-	test("Discarded promises from server load functions don't hang SSR request", async ({
-		request
-	}) => {
-		let error;
-		try {
-			await request.get('/streaming/discarded-promise');
-		} catch (e) {
-			error = e;
-		}
-
-		expect(error).toBeUndefined();
-	});
 });
diff --git a/packages/kit/test/apps/basics/unit-test/server.spec.js b/packages/kit/test/apps/basics/unit-test/server.spec.js
new file mode 100644
index 000000000000..246935153895
--- /dev/null
+++ b/packages/kit/test/apps/basics/unit-test/server.spec.js
@@ -0,0 +1,1598 @@
+/** @import { Server } from '@sveltejs/kit' */
+import { createHash, randomBytes } from 'node:crypto';
+import { once } from 'node:events';
+import fs from 'node:fs';
+import http from 'node:http';
+import path from 'node:path';
+import { pathToFileURL } from 'node:url';
+import { createReadableStream, getRequest, setResponse } from '@sveltejs/kit/node';
+import { createServer, loadEnv } from 'vite';
+import { afterAll, afterEach, beforeAll, describe, expect, test } from 'vitest';
+import * as records from '../../../records.js';
+
+// the server half of the old no-js Playwright project, against the built server directly or,
+// with KIT_TEST_DEV set, Vite's dev server in middleware mode
+
+const root = path.resolve(import.meta.dirname, '..');
+const out = path.join(root, '.svelte-kit/output/server');
+const dev = !!process.env.KIT_TEST_DEV;
+
+/** @type {string} */
+let origin;
+
+/** @type {Server} */
+let server;
+
+/** @type {import('vite').ViteDevServer} */
+let vite;
+
+/** @type {http.Server} */
+let listener;
+
+beforeAll(async () => {
+	// the app writes test/errors.jsonl and test/spans.jsonl relative to cwd
+	process.chdir(root);
+
+	if (dev) {
+		vite = await createServer({ root, server: { middlewareMode: true }, logLevel: 'warn' });
+		listener = http.createServer(vite.middlewares);
+	} else {
+		// the preview server gets these from Vite's .env loading
+		Object.assign(process.env, loadEnv('production', root, ''));
+
+		const { manifest } = await import(pathToFileURL(path.join(out, 'manifest.js')).href);
+		const { Server } = await import(pathToFileURL(path.join(out, 'index.js')).href);
+
+		server = new Server(manifest);
+		await server.init({
+			env: process.env,
+			read: (file) =>
+				createReadableStream(
+					path.join(fs.existsSync(path.join(out, file)) ? out : `${root}/static`, file)
+				)
+		});
+
+		// the app's own fetches of unmatched paths leave the server, so give the origin a listener
+		listener = http.createServer(async (req, res) => {
+			const request = getRequest({ request: req, response: res, base: origin });
+			setResponse(res, await server.respond(request, { getClientAddress: () => '' }));
+		});
+	}
+
+	await once(listener.listen(0, 'localhost'), 'listening');
+	origin = `http://localhost:${/** @type {import('net').AddressInfo} */ (listener.address()).port}`;
+}, 30_000);
+
+afterAll(async () => {
+	await vite?.close();
+	await new Promise((resolve) => listener.close(resolve));
+});
+
+/**
+ * One request, redirects not followed; `load` follows them
+ * @param {string} pathname
+ * @param {RequestInit} [init]
+ */
+const get = async (pathname, init) => {
+	if (dev) return fetch(origin + pathname, { redirect: 'manual', ...init });
+
+	const request = new Request(origin + pathname, init);
+
+	// `fetch` implies this
+	if (!request.headers.has('accept')) {
+		request.headers.set('accept', '*/*');
+	}
+
+	return server.respond(request, { getClientAddress: () => '' });
+};
+
+/**
+ * Request a page, following redirects, and parse it
+ * @param {string} pathname
+ * @param {RequestInit} [init]
+ */
+async function load(pathname, init) {
+	let response = await get(pathname, init);
+
+	while (response.status >= 300 && response.status < 400) {
+		const location = /** @type {string} */ (response.headers.get('location'));
+		const redirected = new URL(location, origin + pathname);
+
+		response = await get(redirected.pathname + redirected.search, init);
+	}
+
+	const document = new DOMParser().parseFromString(await response.text(), 'text/html');
+	return { response, document };
+}
+
+const read_errors = (/** @type {string} */ pathname) =>
+	records.read_errors(path.join(root, 'test/errors.jsonl'), pathname);
+
+const read_traces = (/** @type {string} */ test_id) =>
+	records.read_traces(path.join(root, 'test/spans.jsonl'), test_id);
+
+/** @type {http.Server[]} */
+const servers = [];
+
+/**
+ * An external server for the tests that fetch across origins
+ * @param {(req: http.IncomingMessage, res: http.ServerResponse) => void} handler
+ */
+async function start_server(handler) {
+	const server = http.createServer(handler);
+	servers.push(server);
+	await once(server.listen(0, 'localhost'), 'listening');
+	return { port: /** @type {import('net').AddressInfo} */ (server.address()).port };
+}
+
+afterEach(async () => {
+	await Promise.all(servers.splice(0).map((server) => new Promise((r) => server.close(r))));
+});
+
+describe('Caching', () => {
+	test('caches pages', async () => {
+		const response = await get('/caching');
+		expect(response.headers.get('cache-control')).toBe('public, max-age=30');
+	});
+});
+
+describe('Content-Type', () => {
+	test('sets Content-Type on page', async () => {
+		const response = await get('/content-type-header');
+		expect(response.headers.get('content-type')).toBe('text/html');
+	});
+});
+
+describe('Content-Length', () => {
+	test('sets Content-Length on page', async () => {
+		const response = await get('/content-length-header');
+
+		expect(Number(response.headers.get('content-length'))).toBeGreaterThan(1000);
+	});
+});
+
+describe('Cookies', () => {
+	test('does not forward cookies from external domains', async () => {
+		const { port } = await start_server(async (req, res) => {
+			if (req.url === '/') {
+				res.writeHead(200, {
+					'set-cookie': 'external=true',
+					'access-control-allow-origin': '*'
+				});
+
+				res.end('ok');
+			} else {
+				res.writeHead(404);
+				res.end('not found');
+			}
+		});
+
+		const response = await get(`/load/fetch-external-no-cookies?port=${port}`);
+		expect(response.headers.getSetCookie().join('\n')).not.toContain('external=true');
+	});
+});
+
+describe.skipIf(dev)('CSRF', () => {
+	test('Blocks requests with incorrect origin', async () => {
+		const content_types = [
+			'application/x-www-form-urlencoded',
+			'multipart/form-data',
+			'text/plain',
+			'text/plaiN'
+		];
+		const methods = ['POST', 'PUT', 'PATCH', 'DELETE'];
+		for (const method of methods) {
+			for (const content_type of content_types) {
+				const res = await get('/csrf', { method, headers: { 'content-type': content_type } });
+				const message = `request method: ${method}, content-type: ${content_type}`;
+				expect(res.status, message).toBe(403);
+				expect(await res.text(), message).toBe(
+					`Cross-site ${method} form submissions are forbidden`
+				);
+			}
+		}
+	});
+
+	test('Allows requests from same origin', async () => {
+		const res = await get('/csrf', {
+			method: 'POST',
+			headers: { 'content-type': 'application/x-www-form-urlencoded', origin }
+		});
+		expect(res.status).toBe(200);
+		expect(await res.text()).toBe('ok');
+	});
+
+	test('Allows requests from allowed origins', async () => {
+		// Test with trusted.example.com which is in trustedOrigins
+		const res1 = await get('/csrf', {
+			method: 'POST',
+			headers: {
+				'content-type': 'application/x-www-form-urlencoded',
+				origin: 'https://trusted.example.com'
+			}
+		});
+		expect(res1.status).toBe(200);
+		expect(await res1.text()).toBe('ok');
+
+		// Test with payment-gateway.test which is also in trustedOrigins
+		const res2 = await get('/csrf', {
+			method: 'POST',
+			headers: {
+				'content-type': 'application/x-www-form-urlencoded',
+				origin: 'https://payment-gateway.test'
+			}
+		});
+		expect(res2.status).toBe(200);
+		expect(await res2.text()).toBe('ok');
+	});
+
+	test('Blocks requests from non-allowed origins', async () => {
+		// Test with origin not in trustedOrigins list
+		const res1 = await get('/csrf', {
+			method: 'POST',
+			headers: {
+				'content-type': 'application/x-www-form-urlencoded',
+				origin: 'https://malicious-site.com'
+			}
+		});
+		expect(res1.status).toBe(403);
+		expect(await res1.text()).toBe('Cross-site POST form submissions are forbidden');
+
+		// Test with similar but not exact origin
+		const res2 = await get('/csrf', {
+			method: 'POST',
+			headers: {
+				'content-type': 'application/x-www-form-urlencoded',
+				origin: 'https://trusted.example.com.evil.com'
+			}
+		});
+		expect(res2.status).toBe(403);
+		expect(await res2.text()).toBe('Cross-site POST form submissions are forbidden');
+
+		// Test subdomain attack (should be blocked)
+		const res3 = await get('/csrf', {
+			method: 'POST',
+			headers: {
+				'content-type': 'application/x-www-form-urlencoded',
+				origin: 'https://evil.trusted.example.com'
+			}
+		});
+		expect(res3.status).toBe(403);
+		expect(await res3.text()).toBe('Cross-site POST form submissions are forbidden');
+	});
+
+	test('Allows GET requests regardless of origin', async () => {
+		const res = await get('/csrf', {
+			method: 'GET',
+			headers: {
+				'content-type': 'application/x-www-form-urlencoded',
+				origin: 'https://any-origin.com'
+			}
+		});
+		expect(res.status).toBe(200);
+	});
+
+	test('Allows non-form content types regardless of origin', async () => {
+		const res = await get('/csrf', {
+			method: 'POST',
+			headers: {
+				'content-type': 'application/json',
+				origin: 'https://any-origin.com'
+			}
+		});
+		expect(res.status).toBe(200);
+	});
+
+	test('Allows all protected HTTP methods from allowed origins', async () => {
+		const methods = ['POST', 'PUT', 'PATCH', 'DELETE'];
+		for (const method of methods) {
+			const res = await get('/csrf', {
+				method,
+				headers: {
+					'content-type': 'application/x-www-form-urlencoded',
+					origin: 'https://trusted.example.com'
+				}
+			});
+			expect(res.status, `Method ${method} should be allowed from trusted origin`).toBe(200);
+			expect(await res.text(), `Method ${method} should return ok`).toBe('ok');
+		}
+	});
+
+	test('Handles undefined origin correctly', async () => {
+		// Some requests may have null origin (e.g., from certain mobile apps)
+		const res = await get('/csrf', {
+			method: 'POST',
+			headers: {
+				'content-type': 'application/x-www-form-urlencoded'
+			}
+		});
+		expect(res.status).toBe(403);
+		expect(await res.text()).toBe('Cross-site POST form submissions are forbidden');
+	});
+});
+
+describe('Endpoints', () => {
+	// the client goes away mid-stream, which nothing does to a direct call
+	test.skipIf(!dev)('stream can be canceled with TypeError', async () => {
+		const responseBefore = await get('/endpoint-output/stream-typeerror?what');
+		expect(await responseBefore.text()).toEqual('null');
+
+		await expect(get('/endpoint-output/stream-typeerror')).rejects.toThrow('fetch failed');
+
+		const responseAfter = await get('/endpoint-output/stream-typeerror?what');
+		expect(await responseAfter.text()).toEqual('TypeError');
+	});
+
+	test('HEAD with matching headers but without body', async () => {
+		const url = '/endpoint-output/body';
+
+		const responses = {
+			head: await get(url, { method: 'HEAD' }),
+			get: await get(url)
+		};
+
+		const headers = {
+			head: Object.fromEntries(responses.head.headers),
+			get: Object.fromEntries(responses.get.headers)
+		};
+
+		expect(responses.head.status).toBe(200);
+		expect(responses.get.status).toBe(200);
+		expect(await responses.head.text()).toBe('');
+		expect(await responses.get.text()).toBe('{}');
+
+		// the dev server's http layer adds these per connection
+		for (const name of ['connection', 'date', 'keep-alive']) {
+			delete headers.head[name];
+			delete headers.get[name];
+		}
+
+		expect(headers.head).toEqual(headers.get);
+	});
+
+	test('invalid request method returns allow header', async () => {
+		const response = await get('/endpoint-output/body', {
+			method: 'POST',
+			headers: { origin: 'https://trusted.example.com' }
+		});
+
+		expect(response.status).toBe(405);
+
+		const allow_header = response.headers.get('allow');
+		expect(allow_header).toMatch(/\bGET\b/);
+		expect(allow_header).toMatch(/\bHEAD\b/);
+	});
+
+	test('405 allow header has no duplicate methods listed', async () => {
+		const response = await get('/endpoint-output/head-handler', {
+			method: 'POST',
+			headers: { origin: 'https://trusted.example.com' }
+		});
+
+		expect(response.status).toBe(405);
+
+		const allow_header = /** @type {string} */ (response.headers.get('allow'));
+		const methods = allow_header.split(',').map((m) => m.trim());
+		const unique_methods = [...new Set(methods)];
+
+		expect(methods).toEqual(unique_methods);
+	});
+
+	test('serves page for GET request when endpoint has no GET handler', async () => {
+		const response = await get('/endpoint-output/post-only-with-page');
+
+		expect(response.status).toBe(200);
+		expect(response.headers.get('content-type')).toContain('text/html');
+		expect(await response.text()).toContain('POST-only endpoint page');
+	});
+
+	test('serves page for HEAD request when endpoint has no HEAD or GET handler', async () => {
+		const response = await get('/endpoint-output/post-only-with-page', { method: 'HEAD' });
+
+		expect(response.status).toBe(200);
+		expect(response.headers.get('content-type')).toContain('text/html');
+		expect(response.headers.get('x-sveltekit-page')).toBe('true');
+		expect(await response.text()).toBe('');
+	});
+
+	test('POST to post-only endpoint with sibling page still hits endpoint', async () => {
+		const response = await get('/endpoint-output/post-only-with-page', {
+			method: 'POST',
+			headers: { origin: 'https://trusted.example.com' }
+		});
+
+		expect(response.status).toBe(200);
+		expect(await response.text()).toBe('ok');
+	});
+
+	test('uses fallback handler instead of page when endpoint has no GET but has fallback', async () => {
+		const response = await get('/endpoint-output/fallback-with-page');
+
+		expect(response.status).toBe(200);
+		expect(await response.text()).toBe('catch-all');
+	});
+
+	test('content negotiation: API requests hit endpoint, browser requests hit page', async () => {
+		// application/json → endpoint
+		const api_response = await get('/routing/content-negotiation', {
+			headers: {
+				accept: 'application/json'
+			}
+		});
+
+		expect(api_response.status).toBe(200);
+		expect(await api_response.text()).toBe('GET');
+
+		// text/html → page
+		const html_response = await get('/routing/content-negotiation', {
+			headers: {
+				accept: 'text/html'
+			}
+		});
+
+		expect(html_response.status).toBe(200);
+		expect(html_response.headers.get('content-type')).toContain('text/html');
+		expect(html_response.headers.get('vary')).toBe('Accept');
+		expect(await html_response.text()).toContain('Hi');
+	});
+
+	test('multiple set-cookie on endpoints using GET', async () => {
+		const response = await get('/set-cookie');
+
+		const cookies = response.headers.getSetCookie();
+
+		expect(cookies).toEqual([
+			'answer=42; HttpOnly',
+			'problem=comma, separated, values; HttpOnly',
+			'name=SvelteKit; path=/; HttpOnly'
+		]);
+	});
+
+	test('body can be a binary ReadableStream', async () => {
+		await expect(
+			get('/endpoint-output/stream-throw-error').then((r) => r.arrayBuffer())
+		).rejects.toThrow(dev ? 'fetch failed' : 'simulate error');
+
+		const response = await get('/endpoint-output/stream');
+		const body = Buffer.from(await response.arrayBuffer());
+		const digest = createHash('sha256').update(body).digest('base64url');
+		expect(response.headers.get('digest')).toEqual(`sha-256=${digest}`);
+	});
+
+	test('request body can be read slow', async () => {
+		const data = randomBytes(1024 * 256);
+		const digest = createHash('sha256').update(data).digest('base64url');
+		const response = await get('/endpoint-input/sha256', {
+			method: 'PUT',
+			headers: { 'content-type': 'application/octet-stream' },
+			body: data
+		});
+		expect(await response.text()).toEqual(digest);
+	});
+
+	test('OPTIONS handler', async () => {
+		const url = '/endpoint-output';
+
+		const response = await get(url, {
+			method: 'OPTIONS'
+		});
+
+		expect(response.status).toBe(200);
+		expect(await response.text()).toBe('ok');
+	});
+
+	test('HEAD handler', async () => {
+		const url = '/endpoint-output/head-handler';
+
+		const page_response = await get(url, {
+			method: 'HEAD',
+			headers: {
+				accept: 'text/html'
+			}
+		});
+
+		expect(page_response.status).toBe(200);
+		expect(await page_response.text()).toBe('');
+		expect(page_response.headers.get('x-sveltekit-page')).toBe('true');
+
+		const endpoint_response = await get(url, {
+			method: 'HEAD',
+			headers: {
+				accept: 'application/json'
+			}
+		});
+
+		expect(endpoint_response.status).toBe(200);
+		expect(await endpoint_response.text()).toBe('');
+		expect(endpoint_response.headers.get('x-sveltekit-head-endpoint')).toBe('true');
+	});
+
+	test('catch-all handler', async () => {
+		const url = '/endpoint-output/fallback';
+
+		let response = await get(url, {
+			method: 'GET'
+		});
+
+		expect(response.status).toBe(200);
+		expect(await response.text()).toBe('ok');
+
+		response = await get(url, {
+			method: 'MOVE' // also works with arcane methods
+		});
+
+		expect(response.status).toBe(200);
+		expect(await response.text()).toBe('catch-all');
+
+		response = await get(url, {
+			method: 'OPTIONS'
+		});
+
+		expect(response.status).toBe(200);
+		expect(await response.text()).toBe('catch-all');
+	});
+
+	test('QUERY handler', async () => {
+		const url = '/endpoint-output/query';
+
+		let response = await get(url, {
+			method: 'QUERY',
+			body: 'name=world'
+		});
+
+		expect(response.status).toBe(200);
+		expect(await response.text()).toBe('query: name=world');
+
+		response = await get(url, {
+			method: 'MOVE'
+		});
+
+		expect(response.status).toBe(405);
+		expect(response.headers.get('allow')).toBe('GET, QUERY, HEAD');
+	});
+
+	test('can get assets using relative path', async () => {
+		const response = await get('/endpoint-output/fetch-asset/relative');
+		expect(response.status).toBe(200);
+		expect(response.headers.get('content-type')).toBe('text/plain');
+		expect(await response.text()).toBe('Cos sie konczy, cos zaczyna');
+	});
+});
+
+describe('Errors', () => {
+	test('uses the handleError status for the fallback page served to error-page sub-requests', async () => {
+		const response = await get('/errors/handle-error-status-fallback', {
+			headers: { 'x-sveltekit-error': 'true' }
+		});
+		expect(response.status).toBe(503);
+	});
+
+	test('invalid route response is handled', async () => {
+		const response = await get('/errors/invalid-route-response');
+
+		expect(response.status).toBe(500);
+		expect(await response.text()).toMatch(
+			'Invalid response from route /errors/invalid-route-response: handler should return a Response object'
+		);
+	});
+
+	test('unhandled http method', async () => {
+		const response = await get('/errors/invalid-route-response', {
+			method: 'PUT',
+			headers: { origin: 'https://trusted.example.com' }
+		});
+
+		expect(response.status).toBe(405);
+		expect(await response.text()).toMatch('PUT method not allowed');
+	});
+
+	test('error evaluating module', async () => {
+		const response = await get('/errors/init-error-endpoint');
+
+		expect(response.status).toBe(500);
+		expect(await response.text()).toMatch('thisvariableisnotdefined is not defined');
+	});
+
+	test('returns a lightweight 404 for subresource requests', async () => {
+		// distinct from the path used by the test below, which _does_ invoke the hook
+		const response = await get('/errors/does-not-exist-lightweight-subresource', {
+			headers: { 'sec-fetch-dest': 'image' }
+		});
+
+		expect(response.status).toBe(404);
+		expect(await response.text()).toBe('Not Found');
+		expect(response.headers.get('vary')).toContain('Sec-Fetch-Dest');
+
+		// lightweight 404s bypass the handleError hook
+		expect(read_errors('/errors/does-not-exist-lightweight-subresource')).toBe(undefined);
+	});
+
+	test('renders the error page for document and fetch requests', async () => {
+		for (const destination of ['document', null, 'empty']) {
+			const response = await get(
+				'/errors/does-not-exist-subresource',
+				destination ? { headers: { 'sec-fetch-dest': destination } } : {}
+			);
+
+			expect(response.status).toBe(404);
+			expect(await response.text()).toContain('This is your custom error page saying:');
+		}
+	});
+
+	test('stack traces are not fixed twice', async () => {
+		let { document } = await load('/errors/stack-trace');
+		expect(document.querySelector('#message')?.textContent).toBe(
+			'This is your custom error page saying: "Cannot read properties of undefined (reading \'toUpperCase\') (500 Internal Error)"'
+		);
+
+		// check the stack wasn't mutated
+		({ document } = await load('/errors/stack-trace'));
+		expect(document.querySelector('#message')?.textContent).toBe(
+			'This is your custom error page saying: "Cannot read properties of undefined (reading \'toUpperCase\') (500 Internal Error)"'
+		);
+	});
+
+	test('error(...) in endpoint', async () => {
+		// HTML
+		{
+			const res = await get('/errors/endpoint-throw-error', {
+				headers: {
+					accept: 'text/html'
+				}
+			});
+
+			expect(read_errors('/errors/endpoint-throw-error')).toEqual({
+				kind: 'app',
+				error: { status: 401, message: 'You shall not pass' }
+			});
+
+			expect(res.status).toBe(401);
+			expect(await res.text()).toContain(
+				'This is the static error page with the following message: You shall not pass'
+			);
+		}
+
+		// JSON (default)
+		{
+			const res = await get('/errors/endpoint-throw-error');
+
+			expect(read_errors('/errors/endpoint-throw-error')).toEqual({
+				kind: 'app',
+				error: { status: 401, message: 'You shall not pass' }
+			});
+
+			expect(res.status).toBe(401);
+			expect(await res.json()).toEqual({
+				message: 'You shall not pass',
+				status: 401
+			});
+		}
+	});
+
+	test('redirect(...) in endpoint', async () => {
+		const redirect = await get('/errors/endpoint-throw-redirect');
+		expect(redirect.status).toBe(302);
+		expect(redirect.headers.get('location')).toBe('/');
+
+		const { response, document } = await load('/errors/endpoint-throw-redirect');
+		expect(response.status).toBe(200);
+
+		const error = read_errors('/errors/endpoint-throw-redirect');
+		expect(error).toBe(undefined);
+
+		expect(document.querySelector('h1')?.textContent).toBe('the answer is 42');
+	});
+
+	test('POST to missing page endpoint', async () => {
+		const res = await get('/errors/missing-actions', {
+			method: 'POST',
+			headers: {
+				accept: 'text/html',
+				origin: 'https://trusted.example.com'
+			}
+		});
+		expect(res.status).toBe(405);
+
+		const res_json = await get('/errors/missing-actions', {
+			method: 'POST',
+			headers: {
+				accept: 'application/json',
+				origin: 'https://trusted.example.com'
+			}
+		});
+		expect(res_json.status).toBe(405);
+		expect(await res_json.json()).toEqual({
+			type: 'error',
+			location: '/errors/missing-actions',
+			error: {
+				message: 'Method Not Allowed (405 Method Not Allowed)',
+				status: 405
+			}
+		});
+	});
+
+	test('error thrown in handle results in a rendered error page or JSON response', async () => {
+		// HTML
+		{
+			const res = await get('/errors/error-in-handle', {
+				headers: {
+					accept: 'text/html'
+				}
+			});
+
+			expect(res.status).toBe(500);
+			expect(await res.text()).toContain(
+				'This is the static error page with the following message: Error in handle'
+			);
+		}
+
+		// JSON (default)
+		{
+			const res = await get('/errors/error-in-handle');
+
+			const error = await res.json();
+
+			expect(error.stack).toBe(undefined);
+			expect(res.status).toBe(500);
+			expect(error).toEqual({
+				message: 'Error in handle (500 Internal Error)',
+				status: 500
+			});
+		}
+	});
+
+	test('expected error thrown in handle results in a rendered error page or JSON response', async () => {
+		// HTML
+		{
+			const res = await get('/errors/expected-error-in-handle', {
+				headers: {
+					accept: 'text/html'
+				}
+			});
+
+			expect(res.status).toBe(500);
+			expect(await res.text()).toContain(
+				'This is the static error page with the following message: Expected error in handle'
+			);
+		}
+
+		// JSON (default)
+		{
+			const res = await get('/errors/expected-error-in-handle');
+
+			const error = await res.json();
+
+			expect(error.stack).toBe(undefined);
+			expect(res.status).toBe(500);
+			expect(error).toEqual({
+				message: 'Expected error in handle',
+				status: 500
+			});
+		}
+	});
+
+	test('error thrown from load on the server respects page options when rendering the error page', async () => {
+		const res = await get('/errors/load-error-page-options/csr');
+		expect(res.status).toBe(500);
+		const content = await res.text();
+		expect(content).toContain('Crashing now');
+		// the hydration script should not be present if the csr page option is respected
+		expect(content).not.toContain('kit.start(app');
+	});
+
+	test('returns root layout data for a missing route error page data request', async () => {
+		const data_response = await get(
+			'/this-route-does-not-exist/__data.json?x-sveltekit-invalidated=1'
+		);
+		expect(data_response.status).toBe(200);
+		expect(data_response.headers.get('content-type')).toContain('application/json');
+
+		const data = await data_response.json();
+		expect(data.type).toBe('data');
+		expect(data.nodes[0].type).toBe('data');
+		expect(data.nodes[0].data).toContain('rootlayout');
+
+		const page_response = await get('/this-route-does-not-exist/__data.json');
+		expect(page_response.status).toBe(404);
+		expect(page_response.headers.get('content-type')).toContain('text/html');
+
+		// a single-node request that invalidates nothing is not an error-page data request
+		const crafted_response = await get(
+			'/this-route-does-not-exist/__data.json?x-sveltekit-invalidated=0'
+		);
+		expect(crafted_response.status).toBe(404);
+	});
+
+	test('preserves metadata for a streamed missing route error page', async () => {
+		const response = await get('/non-existent-streamed-route', {
+			headers: { cookie: 'defer=true' }
+		});
+
+		expect(response.status).toBe(404);
+		expect(response.headers.get('etag')).toBeNull();
+
+		const body = await response.text();
+		expect(body).toMatch(/]*>404<\/h1>/);
+		expect(body).toContain('This is your custom error page');
+	});
+});
+
+describe('Load', () => {
+	test('fetch does not load a file with a # character', async () => {
+		const response = await get('/load/static-file-with-hash');
+		expect(await response.text()).toContain('status: 404');
+	});
+
+	test('fetching a non-existent resource in root layout fails without hanging', async () => {
+		const response = await get('/errors/error-in-layout');
+		expect(await response.text()).toContain('Error: 404');
+	});
+
+	test('fetch reads universal load assets on the server', async () => {
+		const { document } = await load('/load/fetch-asset');
+		expect(document.querySelector('p')?.textContent).toBe('1');
+	});
+
+	test('does not forward accept-language to internal fetch when the request has none', async () => {
+		// unlike browsers and fetch, a bare http client sends no accept-language header
+		const html = await new Promise((fulfil) => {
+			http.get(
+				`${origin}/load/fetch-request-headers`,
+				{ headers: { accept: '*/*', 'user-agent': 'node' } },
+				(res) => {
+					let body = '';
+					res.on('data', (chunk) => (body += chunk));
+					res.on('end', () => fulfil(body));
+				}
+			);
+		});
+		const headers = JSON.parse(
+			/** @type {RegExpMatchArray} */ (/
(.+?)<\/pre>/s.exec(html))[1]
+		);
+		expect(headers.accept).toBe('*/*');
+		expect(headers['accept-language']).toBeUndefined();
+	});
+
+	test('includes origin header on non-GET internal request', async () => {
+		const { document } = await load('/load/fetch-origin-internal');
+		expect(document.querySelector('h1')?.textContent).toBe(`origin: ${origin}`);
+	});
+
+	test('includes origin header on external request', async () => {
+		const { port } = await start_server((req, res) => {
+			if (req.url === '/') {
+				res.writeHead(200, {
+					'content-type': 'application/json',
+					'access-control-allow-origin': '*'
+				});
+
+				res.end(JSON.stringify({ origin: req.headers.origin }));
+			} else {
+				res.writeHead(404);
+				res.end('not found');
+			}
+		});
+
+		const { document } = await load(`/load/fetch-origin-external?port=${port}`);
+		expect(document.querySelector('h1')?.textContent).toBe(`origin: ${origin}`);
+	});
+
+	test('does not run when using invalid request methods', async () => {
+		const load_url = '/load';
+
+		let response = await get(load_url, {
+			method: 'OPTIONS'
+		});
+
+		expect(response.status).toBe(204);
+		expect(await response.text()).toBe('');
+		expect(response.headers.get('allow')).toBe('GET, HEAD, OPTIONS');
+
+		const actions_url = '/actions/enhance';
+		response = await get(actions_url, {
+			method: 'OPTIONS'
+		});
+
+		expect(response.status).toBe(204);
+		expect(await response.text()).toBe('');
+		expect(response.headers.get('allow')).toBe('GET, HEAD, OPTIONS, POST');
+	});
+
+	test('allows logging URL search params', async () => {
+		const { document } = await load('/load/server-log-search-param');
+
+		expect(document.querySelector('p')?.textContent).toBe('hello world');
+	});
+});
+
+describe('Routing', () => {
+	test('event.params are available in handle', async () => {
+		const response = await get('/routing/params-in-handle/banana');
+		expect(await response.json()).toStrictEqual({
+			key: '/routing/params-in-handle/[x]',
+			params: { x: 'banana' }
+		});
+	});
+
+	test('/favicon.ico is a valid route', async () => {
+		const response = await get('/favicon.ico');
+		expect(response.status).toBe(200);
+
+		const data = await response.json();
+		expect(data).toEqual({ surprise: 'lol' });
+	});
+
+	test('falls back to page actions if sibling endpoint has no POST handler', async () => {
+		const response = await get('/endpoint-output/actions-with-endpoint', {
+			method: 'POST',
+			body: new URLSearchParams(),
+			headers: {
+				accept: 'application/json',
+				origin
+			}
+		});
+
+		expect(response.status).toBe(200);
+		expect((await response.json()).type).toBe('success');
+	});
+});
+
+describe('Shadowed pages', () => {
+	test('Action can return undefined', async () => {
+		const response = await get('/shadowed/simple/post', {
+			method: 'POST',
+			body: new URLSearchParams(),
+			headers: {
+				accept: 'application/json',
+				origin
+			}
+		});
+
+		expect(response.status).toBe(200);
+		expect(await response.json()).toEqual({
+			type: 'success',
+			status: 204,
+			location: '/shadowed/simple/post'
+		});
+	});
+
+	test('action response includes the stripped landing location', async () => {
+		const response = await get('/actions/cross-page/destination?redirectTo=%2Fdashboard&/failure', {
+			method: 'POST',
+			body: new URLSearchParams({ username: 'paolo' }),
+			headers: {
+				accept: 'application/json',
+				origin
+			}
+		});
+
+		expect(response.status).toBe(400);
+		expect(await response.json()).toEqual({
+			type: 'failure',
+			status: 400,
+			location: '/actions/cross-page/destination?redirectTo=%2Fdashboard',
+			data: '[{"problem":1,"username":2},"invalid","paolo"]'
+		});
+	});
+
+	test('Action fail() returns matching HTTP status code', async () => {
+		const response = await get('/actions/form-errors', {
+			method: 'POST',
+			body: new URLSearchParams(),
+			headers: {
+				accept: 'application/json',
+				origin
+			}
+		});
+
+		expect(response.status).toBe(400);
+		const body = await response.json();
+		expect(body.type).toBe('failure');
+		expect(body.status).toBe(400);
+	});
+});
+
+describe('setHeaders', () => {
+	test('allows multiple set-cookie headers with different values', async () => {
+		const response = await get('/headers/set-cookie/sub');
+		const cookies = response.headers.getSetCookie().join('\n');
+
+		expect(cookies).toMatch('cookie1=value1');
+		expect(cookies).toMatch('cookie2=value2');
+	});
+});
+
+describe('cookies', () => {
+	test('cookie.stringifySetCookie created correct cookie header string', async () => {
+		const response = await get('/cookies/serialize');
+		const cookies = response.headers.getSetCookie().join('\n');
+
+		expect(cookies).toMatch('before=before');
+		expect(cookies).toMatch('after=after');
+		expect(cookies).toMatch('endpoint=endpoint');
+	});
+});
+
+describe('Miscellaneous', () => {
+	test('handles responses with immutable headers', async () => {
+		const response = await get('/immutable-headers');
+		expect(response.status).toBe(200);
+		expect(await response.text()).toBe('foo');
+	});
+
+	test('does not send x-sveltekit-version header on document responses', async () => {
+		const response = await get('/');
+		expect(response.headers.get('x-sveltekit-version')).toBeNull();
+	});
+
+	test('sends x-sveltekit-version header on data responses', async () => {
+		const response = await get('/__data.json');
+		expect(response.headers.get('x-sveltekit-version')).toBeTruthy();
+	});
+});
+
+describe('reroute', () => {
+	test('Apply reroute when directly accessing a page', async () => {
+		const { document } = await load('/reroute/basic/a');
+		expect(document.querySelector('h1')?.textContent).toContain(
+			'Successfully rewritten, URL should still show a: /reroute/basic/a'
+		);
+	});
+
+	test('Apply async reroute when directly accessing a page', async () => {
+		const { document } = await load('/reroute/async/a', {
+			headers: { cookie: 'reroute-cookie=yes' }
+		});
+		expect(document.querySelector('h1')?.textContent).toContain(
+			'Successfully rewritten, URL should still show a: /reroute/async/a'
+		);
+	});
+
+	test('Returns a 500 response if reroute throws an error on the server', async () => {
+		const response = await get('/reroute/error-handling/server-error');
+		expect(response.status).toBe(500);
+	});
+});
+
+describe('init', () => {
+	test('init server hook is called once before the load function', async () => {
+		let { document } = await load('/init-hooks');
+		expect(document.querySelector('p')?.textContent).toBe('1');
+		({ document } = await load('/init-hooks'));
+		expect(document.querySelector('p')?.textContent).toBe('1');
+	});
+});
+
+describe('getRequestEvent', () => {
+	test('getRequestEvent works in server endpoints', async () => {
+		const response = await get('/get-request-event/endpoint');
+		expect(await response.text()).toBe('hello from hooks.server.js');
+	});
+});
+
+describe('$app/forms', () => {
+	test('deserialize works on the server', async () => {
+		const response = await get('/serialization-form-non-enhanced/server-deserialize');
+		expect(await response.json()).toEqual({ data: 'It works!' });
+	});
+});
+
+describe('$app/env', () => {
+	test.skipIf(dev)('treeshakes dev check', async () => {
+		const code = fs.readFileSync(
+			path.join(root, '.svelte-kit/output/server/entries/pages/treeshaking/dev/_page.svelte.js'),
+			'utf-8'
+		);
+		expect(code).not.toContain('not prod');
+	});
+
+	test.skipIf(dev)('treeshakes browser check', async () => {
+		const code = fs.readFileSync(
+			path.join(
+				root,
+				'.svelte-kit/output/server/entries/pages/treeshaking/browser/_page.svelte.js'
+			),
+			'utf-8'
+		);
+		expect(code).not.toMatch('client');
+	});
+});
+
+describe('web workers', () => {
+	test.skipIf(dev)(
+		'worker files emitted by the server build are copied to the client output',
+		() => {
+			const workers = path.join(root, '.svelte-kit/output/client/_app/immutable/workers');
+			expect(fs.readdirSync(workers).some((file) => file.startsWith('worker-'))).toBe(true);
+		}
+	);
+});
+
+describe('tracing', () => {
+	/**
+	 * @param {import('../../../types.js').SpanTree} span
+	 * @returns {import('../../../types.js').SpanTree | null}
+	 */
+	function find_resolve_root_span(span) {
+		if (span.name === 'sveltekit.resolve') {
+			return span;
+		}
+		for (const child of span.children || []) {
+			const found = find_resolve_root_span(child);
+			if (found) return found;
+		}
+		return null;
+	}
+
+	test('correct spans are created for a regular navigation', async () => {
+		const test_id = randomBytes(8).toString('hex');
+		await get(`/tracing/one/two/three/four/five?test_id=${test_id}`);
+		const traces = read_traces(test_id);
+		expect(traces.length).toBeGreaterThan(0);
+
+		const trace = traces[0];
+		const trace_id = trace.trace_id;
+
+		// Verify root span structure
+		expect(trace).toEqual({
+			name: 'sveltekit.handle.root',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/one/two/three/[...four]',
+				'http.method': 'GET',
+				'http.url': expect.stringContaining(`/tracing/one/two/three/four/five?test_id=${test_id}`),
+				'sveltekit.is_data_request': false,
+				'sveltekit.is_sub_request': false,
+				test_id
+			},
+			links: [],
+			trace_id,
+			span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
+					attributes: {}
+				})
+			])
+		});
+
+		// Find and verify the resolve.root span
+		const resolve_root_span = find_resolve_root_span(trace);
+		expect(resolve_root_span).not.toBeNull();
+		expect(resolve_root_span).toEqual({
+			name: 'sveltekit.resolve',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/one/two/three/[...four]',
+				'http.response.status_code': 200,
+				'http.response.body.size': expect.stringMatching(/^\d+$/)
+			},
+			links: [],
+			trace_id,
+			span_id: expect.any(String),
+			parent_span_id: expect.any(String),
+			children: [
+				{
+					name: 'sveltekit.load',
+					status: { code: 0 },
+					start_time: [expect.any(Number), expect.any(Number)],
+					end_time: [expect.any(Number), expect.any(Number)],
+					attributes: {
+						'sveltekit.load.node_id': 'src/routes/+layout.server.js',
+						'sveltekit.load.node_type': '+layout.server',
+						'sveltekit.load.environment': 'server',
+						'http.route': '/tracing/one/two/three/[...four]'
+					},
+					links: [],
+					trace_id,
+					span_id: expect.any(String),
+					parent_span_id: expect.any(String),
+					children: []
+				},
+				{
+					name: 'sveltekit.load',
+					status: { code: 0 },
+					start_time: [expect.any(Number), expect.any(Number)],
+					end_time: [expect.any(Number), expect.any(Number)],
+					attributes: {
+						'sveltekit.load.node_id': 'src/routes/+layout.js',
+						'sveltekit.load.node_type': '+layout',
+						'sveltekit.load.environment': 'server',
+						'http.route': '/tracing/one/two/three/[...four]'
+					},
+					links: [],
+					trace_id,
+					span_id: expect.any(String),
+					parent_span_id: expect.any(String),
+					children: []
+				}
+			]
+		});
+	});
+
+	test('correct spans are created for HttpError', async () => {
+		const test_id = randomBytes(8).toString('hex');
+		const response = await get(`/tracing/http-error?test_id=${test_id}`);
+		expect(response.status).toBe(500);
+
+		const traces = read_traces(test_id);
+		const trace_id = traces[0].trace_id;
+		const trace = traces[0];
+
+		// Verify root span structure
+		expect(trace).toEqual({
+			name: 'sveltekit.handle.root',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/http-error',
+				'http.method': 'GET',
+				'http.url': expect.stringContaining(`/tracing/http-error?test_id=${test_id}`),
+				'sveltekit.is_data_request': false,
+				'sveltekit.is_sub_request': false,
+				test_id
+			},
+			links: [],
+			trace_id,
+			span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
+					attributes: {}
+				})
+			])
+		});
+
+		// Find and verify the resolve.root span
+		const resolve_root_span = find_resolve_root_span(trace);
+		expect(resolve_root_span).not.toBeNull();
+		expect(resolve_root_span).toEqual({
+			name: 'sveltekit.resolve',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/http-error',
+				'http.response.status_code': 500,
+				'http.response.body.size': expect.stringMatching(/^\d+$/)
+			},
+			links: [],
+			trace_id,
+			span_id: expect.any(String),
+			parent_span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.load',
+					status: { code: 2, message: 'Internal server error from tracing test' },
+					attributes: expect.objectContaining({
+						'sveltekit.load.node_id': 'src/routes/tracing/http-error/+page.server.js',
+						'sveltekit.load.result.type': 'known_error',
+						'sveltekit.load.result.status': 500,
+						'sveltekit.load.result.message': 'Internal server error from tracing test'
+					})
+				})
+			])
+		});
+	});
+
+	test('correct spans are created for Redirect', async () => {
+		const test_id = randomBytes(8).toString('hex');
+		const r1 = await get(`/tracing/redirect?test_id=${test_id}`);
+		expect(r1.status).toBe(307);
+
+		const r2 = await get(/** @type {string} */ (r1.headers.get('location')));
+		expect(r2.status).toBe(200);
+
+		const traces = read_traces(test_id);
+		expect(traces).toHaveLength(2);
+		const redirect_trace_id = traces[0].trace_id;
+		const destination_trace_id = traces[1].trace_id;
+
+		const redirect_trace = traces[0];
+		const destination_trace = traces[1];
+
+		// Verify redirect trace root span structure
+		expect(redirect_trace).toEqual({
+			name: 'sveltekit.handle.root',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/redirect',
+				'http.method': 'GET',
+				'http.url': expect.stringContaining(`/tracing/redirect?test_id=${test_id}`),
+				'sveltekit.is_data_request': false,
+				'sveltekit.is_sub_request': false,
+				test_id
+			},
+			links: [],
+			trace_id: redirect_trace_id,
+			span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
+					attributes: {}
+				})
+			])
+		});
+
+		// Find and verify the redirect resolve.root span
+		const redirect_resolve_root_span = find_resolve_root_span(redirect_trace);
+		expect(redirect_resolve_root_span).not.toBeNull();
+		expect(redirect_resolve_root_span).toEqual({
+			name: 'sveltekit.resolve',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/redirect',
+				'http.response.status_code': 307,
+				'http.response.body.size': expect.stringMatching(/^\d+$|^unknown$/)
+			},
+			links: [],
+			trace_id: redirect_trace_id,
+			span_id: expect.any(String),
+			parent_span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.load',
+					status: { code: 0 },
+					attributes: expect.objectContaining({
+						'sveltekit.load.node_id': 'src/routes/tracing/redirect/+page.server.js',
+						'sveltekit.load.result.type': 'redirect',
+						'sveltekit.load.result.status': 307,
+						'sveltekit.load.result.location': `/tracing/one/two/three/four/five?test_id=${test_id}`
+					})
+				})
+			])
+		});
+
+		// Verify destination trace root span structure
+		expect(destination_trace).toEqual({
+			name: 'sveltekit.handle.root',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/one/two/three/[...four]',
+				'http.method': 'GET',
+				'http.url': expect.stringContaining(`/tracing/one/two/three/four/five?test_id=${test_id}`),
+				'sveltekit.is_data_request': false,
+				'sveltekit.is_sub_request': false,
+				test_id
+			},
+			links: [],
+			trace_id: destination_trace_id,
+			span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
+					attributes: {}
+				})
+			])
+		});
+
+		// Find and verify the destination resolve.root span
+		const destination_resolve_root_span = find_resolve_root_span(destination_trace);
+		expect(destination_resolve_root_span).not.toBeNull();
+		expect(destination_resolve_root_span).toEqual({
+			name: 'sveltekit.resolve',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/one/two/three/[...four]',
+				'http.response.status_code': 200,
+				'http.response.body.size': expect.stringMatching(/^\d+$/)
+			},
+			links: [],
+			trace_id: destination_trace_id,
+			span_id: expect.any(String),
+			parent_span_id: expect.any(String),
+			children: [
+				{
+					name: 'sveltekit.load',
+					status: { code: 0 },
+					start_time: [expect.any(Number), expect.any(Number)],
+					end_time: [expect.any(Number), expect.any(Number)],
+					attributes: {
+						'sveltekit.load.node_id': 'src/routes/+layout.server.js',
+						'sveltekit.load.node_type': '+layout.server',
+						'sveltekit.load.environment': 'server',
+						'http.route': '/tracing/one/two/three/[...four]'
+					},
+					links: [],
+					trace_id: destination_trace_id,
+					span_id: expect.any(String),
+					parent_span_id: expect.any(String),
+					children: []
+				},
+				{
+					name: 'sveltekit.load',
+					status: { code: 0 },
+					start_time: [expect.any(Number), expect.any(Number)],
+					end_time: [expect.any(Number), expect.any(Number)],
+					attributes: {
+						'sveltekit.load.node_id': 'src/routes/+layout.js',
+						'sveltekit.load.node_type': '+layout',
+						'sveltekit.load.environment': 'server',
+						'http.route': '/tracing/one/two/three/[...four]'
+					},
+					links: [],
+					trace_id: destination_trace_id,
+					span_id: expect.any(String),
+					parent_span_id: expect.any(String),
+					children: []
+				}
+			]
+		});
+	});
+
+	test('correct spans are created for regular Error', async () => {
+		const test_id = randomBytes(8).toString('hex');
+		const response = await get(`/tracing/regular-error?test_id=${test_id}`);
+		expect(response.status).toBe(500);
+
+		const traces = read_traces(test_id);
+		const trace_id = traces[0].trace_id;
+		const trace = traces[0];
+
+		// Verify root span structure
+		expect(trace).toEqual({
+			name: 'sveltekit.handle.root',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/regular-error',
+				'http.method': 'GET',
+				'http.url': expect.stringContaining(`/tracing/regular-error?test_id=${test_id}`),
+				'sveltekit.is_data_request': false,
+				'sveltekit.is_sub_request': false,
+				test_id
+			},
+			links: [],
+			trace_id,
+			span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
+					attributes: {}
+				})
+			])
+		});
+
+		// Find and verify the resolve.root span
+		const resolve_root_span = find_resolve_root_span(trace);
+		expect(resolve_root_span).not.toBeNull();
+		expect(resolve_root_span).toEqual({
+			name: 'sveltekit.resolve',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/regular-error',
+				'http.response.status_code': 500,
+				'http.response.body.size': expect.stringMatching(/^\d+$/)
+			},
+			links: [],
+			trace_id,
+			span_id: expect.any(String),
+			parent_span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.load',
+					status: { code: 2, message: 'Regular error from tracing test' },
+					attributes: expect.objectContaining({
+						'sveltekit.load.node_id': 'src/routes/tracing/regular-error/+page.server.js',
+						'sveltekit.load.result.type': 'unknown_error'
+					})
+				})
+			])
+		});
+	});
+
+	test('correct spans are created for non-error object', async () => {
+		const test_id = randomBytes(8).toString('hex');
+		const response = await get(`/tracing/non-error-object?test_id=${test_id}`);
+		expect(response.status).toBe(500);
+
+		const traces = read_traces(test_id);
+		const trace_id = traces[0].trace_id;
+		const trace = traces[0];
+
+		// Verify root span structure
+		expect(trace).toEqual({
+			name: 'sveltekit.handle.root',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/non-error-object',
+				'http.method': 'GET',
+				'http.url': expect.stringContaining(`/tracing/non-error-object?test_id=${test_id}`),
+				'sveltekit.is_data_request': false,
+				'sveltekit.is_sub_request': false,
+				test_id
+			},
+			links: [],
+			trace_id,
+			span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.handle.sequenced.set_tracing_test_id',
+					attributes: {}
+				})
+			])
+		});
+
+		// Find and verify the resolve.root span
+		const resolve_root_span = find_resolve_root_span(trace);
+		expect(resolve_root_span).not.toBeNull();
+		expect(resolve_root_span).toEqual({
+			name: 'sveltekit.resolve',
+			status: { code: 0 },
+			start_time: [expect.any(Number), expect.any(Number)],
+			end_time: [expect.any(Number), expect.any(Number)],
+			attributes: {
+				'http.route': '/tracing/non-error-object',
+				'http.response.status_code': 500,
+				'http.response.body.size': expect.stringMatching(/^\d+$/)
+			},
+			links: [],
+			trace_id,
+			span_id: expect.any(String),
+			parent_span_id: expect.any(String),
+			children: expect.arrayContaining([
+				expect.objectContaining({
+					name: 'sveltekit.load',
+					status: { code: 2 },
+					attributes: expect.objectContaining({
+						'sveltekit.load.node_id': 'src/routes/tracing/non-error-object/+page.server.js',
+						'sveltekit.load.result.type': 'unknown_error'
+					})
+				})
+			])
+		});
+	});
+});
+
+describe.skipIf(dev)('asset preload', () => {
+	test('does not inject Link headers', async () => {
+		const response = await get('/asset-preload');
+
+		const header = response.headers.get('link');
+		expect(header).toBeNull();
+	});
+
+	test('injects  tags', async () => {
+		const response = await get('/asset-preload');
+
+		const body = await response.text();
+
+		expect(body).toContain('rel="modulepreload"');
+		expect(body).toContain('as="font"');
+		expect(body).toMatch(/href="[^"]+\/shlop\.[^".]+\.woff2"/);
+		expect(body).toMatch(/href="[^"]+\/shlop\.var\.[^".]+\.woff2"/);
+		// the emitted file name is sanitized, but the filter matched on the original `shlop+bold.woff2`
+		expect(body).toMatch(/href="[^"]+\/shlop_bold\.[^".]+\.woff2"/);
+	});
+});
+
+describe('Streaming', () => {
+	test("Discarded promises from server load functions don't hang SSR request", async () => {
+		let error;
+		try {
+			await (await get('/streaming/discarded-promise')).text();
+		} catch (e) {
+			error = e;
+		}
+
+		expect(error).toBeUndefined();
+	});
+});
diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js
index 2f6ab39f9af3..fd8aed33a0b5 100644
--- a/packages/kit/test/apps/basics/vite.config.js
+++ b/packages/kit/test/apps/basics/vite.config.js
@@ -119,6 +119,6 @@ export default defineConfig({
 			],
 			headless: true
 		},
-		include: ['unit-test/**/*.spec.js']
+		include: ['unit-test/client.spec.js']
 	}
 });
diff --git a/packages/kit/test/apps/basics/vitest.server.config.js b/packages/kit/test/apps/basics/vitest.server.config.js
new file mode 100644
index 000000000000..9bf6e9d0a0bd
--- /dev/null
+++ b/packages/kit/test/apps/basics/vitest.server.config.js
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+	test: {
+		name: 'kit-basics-server',
+		// for DOMParser
+		environment: 'jsdom',
+		include: ['unit-test/server.spec.js']
+	}
+});
diff --git a/packages/kit/test/records.js b/packages/kit/test/records.js
new file mode 100644
index 000000000000..bb6416cc9948
--- /dev/null
+++ b/packages/kit/test/records.js
@@ -0,0 +1,51 @@
+/** @import { SpanData, SpanTree } from './types.js' */
+import fs from 'node:fs';
+
+/**
+ * The most recent error a test app's `handleError` hook appended for `path`
+ * @param {string} file the app's errors.jsonl
+ * @param {string} path
+ */
+export function read_errors(file, path) {
+	if (!fs.existsSync(file)) return;
+
+	const records = fs.readFileSync(file, 'utf8').split('\n');
+	records.pop(); // ignore a trailing partial record if this races an append
+
+	const match = records.map((line) => JSON.parse(line)).findLast((error) => error.path === path);
+
+	if (match) {
+		const { path: _, ...error } = match;
+		return error;
+	}
+}
+
+/**
+ * The span trees a test app's instrumentation exported for `test_id`
+ * @param {string} file the app's spans.jsonl
+ * @param {string} test_id
+ */
+export function read_traces(file, test_id) {
+	const raw = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
+	const traces = /** @type {SpanData[]} */ (raw.map((line) => JSON.parse(line)));
+
+	return traces
+		.filter((t) => t.parent_span_id === undefined && t.attributes.test_id === test_id)
+		.map((root_trace) => {
+			const child_traces = traces.filter((span) => span.trace_id === root_trace.trace_id);
+			return build_span_tree(root_trace, child_traces);
+		});
+}
+
+/**
+ * @param {SpanData} current_span
+ * @param {SpanData[]} child_spans
+ * @returns {SpanTree}
+ */
+function build_span_tree(current_span, child_spans) {
+	const children = child_spans.filter((span) => span.parent_span_id === current_span.span_id);
+	return {
+		...current_span,
+		children: children.map((child) => build_span_tree(child, child_spans))
+	};
+}
diff --git a/packages/kit/test/utils.js b/packages/kit/test/utils.js
index c1dcf6acd143..5241602291d8 100644
--- a/packages/kit/test/utils.js
+++ b/packages/kit/test/utils.js
@@ -1,4 +1,3 @@
-/** @import { SpanData, SpanTree } from './types.js' */
 // This helps `pnpm check` pass in the test apps without having to include
 // the ambient.d.ts file in each of their tsconfig.json files.
 /** @import {} from './ambient.js' */
@@ -10,6 +9,7 @@ import process from 'node:process';
 import { fileURLToPath } from 'node:url';
 import { defineConfig, test as base, devices } from '@playwright/test';
 import { number_from_env } from '../../../test-utils/index.js';
+import { read_errors, read_traces } from './records.js';
 
 /** @type {typeof import('./types.js')['test']} */
 export const test = base.extend({
@@ -146,42 +146,12 @@ export const test = base.extend({
 
 	// eslint-disable-next-line no-empty-pattern -- Playwright doesn't let us use `_` as a parameter name. It must be a destructured object
 	read_errors: async ({}, use) => {
-		/** @param {string} path */
-		function read_errors(path) {
-			if (!fs.existsSync('test/errors.jsonl')) return;
-
-			const records = fs.readFileSync('test/errors.jsonl', 'utf8').split('\n');
-			records.pop(); // ignore a trailing partial record if this races an append
-
-			const match = records
-				.map((line) => JSON.parse(line))
-				.findLast((error) => error.path === path);
-
-			if (match) {
-				const { path: _, ...error } = match;
-				return error;
-			}
-		}
-
-		await use(read_errors);
+		await use((/** @type {string} */ path) => read_errors('test/errors.jsonl', path));
 	},
 
 	// eslint-disable-next-line no-empty-pattern -- Playwright doesn't let us use `_` as a parameter name. It must be a destructured object
 	read_traces: async ({}, use) => {
-		/** @param {string} test_id */
-		function read_traces(test_id) {
-			const raw = fs.readFileSync('test/spans.jsonl', 'utf8').split('\n').filter(Boolean);
-			const traces = /** @type {SpanData[]} */ (raw.map((line) => JSON.parse(line)));
-
-			return traces
-				.filter((t) => t.parent_span_id === undefined && t.attributes.test_id === test_id)
-				.map((root_trace) => {
-					const child_traces = traces.filter((span) => span.trace_id === root_trace.trace_id);
-					return build_span_tree(root_trace, child_traces);
-				});
-		}
-
-		await use(read_traces);
+		await use((/** @type {string} */ test_id) => read_traces('test/spans.jsonl', test_id));
 	},
 
 	// eslint-disable-next-line no-empty-pattern -- Playwright doesn't let us use `_` as a parameter name. It must be a destructured object
@@ -383,16 +353,3 @@ export const config = defineConfig({
 	testDir: 'test',
 	testMatch: /(.+\.)?(test|spec)\.[jt]s/
 });
-
-/**
- * @param {SpanData} current_span
- * @param {SpanData[]} child_spans
- * @returns {SpanTree}
- */
-function build_span_tree(current_span, child_spans) {
-	const children = child_spans.filter((span) => span.parent_span_id === current_span.span_id);
-	return {
-		...current_span,
-		children: children.map((child) => build_span_tree(child, child_spans))
-	};
-}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0ef72ca60118..c3b2f2d798a7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -888,6 +888,9 @@ importers:
       '@vitest/browser-playwright':
         specifier: 'catalog:'
         version: 4.1.10(playwright@1.62.1)(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0))(vitest@4.1.10)
+      jsdom:
+        specifier: 'catalog:'
+        version: 30.0.1
       svelte:
         specifier: 'catalog:'
         version: 5.57.0(@typescript-eslint/types@8.61.1)