news 2026/9/25 17:43:44

SvelteKit 2 + Svelte 5 组合下的 Sentry E2E 测试应用:sentry-javascript 如何验证 @sentry/sveltekit SDK

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SvelteKit 2 + Svelte 5 组合下的 Sentry E2E 测试应用:sentry-javascript 如何验证 @sentry/sveltekit SDK
  • 可观测性

【免费下载链接】sentry-javascript

Official Sentry SDKs for JavaScript

项目地址:https://gitcode.com/gh_mirrors/se/sentry-javascript
点击查看免费下载

本文以dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/目录下的 README 为骨架,讲清这个基于 create-svelte 脚手架搭建的 SvelteKit 2 + Svelte 5 项目如何创建、开发与构建,并结合仓库源码深入剖析它在 Sentry JavaScript SDK 仓库中的真实角色——作为@sentry/sveltekit包在最新 Svelte 大版本下的端到端(E2E)回归验证目标。读完本文,你既能复现该应用的运行流程,也能理解其 hooks 初始化、Vite 插件、Playwright 测试与事件代理服务器之间的完整链路。

README 的原貌:一个标准的 create-svelte 脚手架项目

该应用的 README.md 本身是 create-svelte 脚手架生成的标准模板,它声明了这是一个由 create-svelte 驱动的 Svelte 项目,并给出三类基础操作命令。

创建项目

README 中保留的原始创建方式为:

# 在当前目录创建新项目 npm create svelte@latest # 在 my-app 目录中创建新项目 npm create svelte@latest my-app

不过在本仓库中,这个应用并不是每次用脚手架重新生成的,而是已经以固定形态提交在test-applications/sveltekit-2-svelte-5/目录下。它的技术栈组合可以从 package.json 中精确读出:

依赖版本角色
@sveltejs/kit2.69.1SvelteKit 2 框架本体(锁定的具体版本)
svelte^5.0.0-next.115Svelte 5(next 系列预发布版)
@sentry/sveltekitfile:../../packed/sentry-sveltekit-packed.tgz待验证的 Sentry SDK(本地打包产物)
@sveltejs/vite-plugin-svelte^3.0.0Vite 集成插件
vite^5.4.11构建工具
@spotlightjs/spotlight2.0.0-alpha.1开发期 trace 可视化
@playwright/test~1.63.0E2E 测试框架

值得注意的是@sentry/sveltekit的依赖写法是file:../../packed/sentry-sveltekit-packed.tgz——它指向 monorepo 中预先打包好的 Sentry SvelteKit SDK tarball。这说明该应用验证的不是 npm 上发布的 SDK,而是当前仓库源码构建出的打包产物,是典型的"打包含待测包、安装、跑真实浏览器"的 E2E 回归策略。

开发模式

README 给出的开发流程是:安装依赖后启动开发服务器:

npm run dev # 或启动服务器并在新浏览器标签页中打开应用 npm run dev -- --open

对照 package.json 中的 scripts,这里的dev实际映射为vite dev,并且该仓库统一使用 pnpm 管理依赖(clean脚本会删除pnpm-lock.yaml)。完整的脚本清单还包括:

{ "dev": "vite dev", "build": "vite build", "preview": "vite preview", "proxy": "node start-event-proxy.mjs", "clean": "npx rimraf node_modules pnpm-lock.yaml", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "test:prod": "TEST_ENV=production playwright test", "test:build": "pnpm install && pnpm build", "test:assert": "pnpm test:prod" }

其中test:build与test:assert揭示了 CI 的使用方式:先在目标应用里执行pnpm install && pnpm build完成安装与生产构建,再以TEST_ENV=production跑 Playwright 断言。

构建与预览

README 中生产构建与预览的命令同样完整继承自模板:

npm run build # 实际执行 vite build # 构建产物可用 preview 预览: npm run preview

README 末尾还保留了关于部署 adapter 的提示:若要部署应用,可能需要为具体目标环境安装对应的 SvelteKit adapter。这一点在本应用中确实成立——svelte.config.js 使用的是@sveltejs/adapter-auto:

import adapter from '@sveltejs/adapter-auto'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: vitePreprocess(), kit: { // adapter-auto 只支持部分环境,如果目标环境不被支持, // 需要切换为具体的 adapter adapter: adapter(), }, }; export default config;

这也解释了为什么 Playwright 配置里选择vite preview而非部署到某个平台:本地预览即可完整覆盖 SvelteKit 的服务端渲染与 hydration 行为。

目录的真实身份:Sentry SvelteKit SDK 的 Svelte 5 E2E 测试目标

从目录命名(sveltekit-2-svelte-5)与同级目录下并列存在的sveltekit-2、sveltekit-2-static、sveltekit-3、sveltekit-3-cloudflare-workers等应用可以推断,该仓库为@sentry/sveltekit维护了一组覆盖不同 SvelteKit/Svelte 大版本组合的 E2E 应用,而本文主角专门锁定"SvelteKit 2 + Svelte 5"这一前沿组合,用于尽早暴露 Svelte 5 新运行时(runes 模式)下的兼容性问题。

客户端埋点:hooks.client.ts

Sentry 在 SvelteKit 中的推荐接入点是hooks.client.ts与hooks.server.ts。本应用的 hooks.client.ts 完整展示了 SDK 初始化:

import { env } from '$env/dynamic/public'; import * as Sentry from '@sentry/sveltekit'; import * as Spotlight from '@spotlightjs/spotlight'; Sentry.init({ environment: 'qa', // 通过 dynamic sampling bias 保留事务 dsn: env.PUBLIC_E2E_TEST_DSN, debug: !!env.PUBLIC_DEBUG, tunnel: `http://localhost:3031/`, // 代理服务器 tracesSampleRate: 1.0, }); const myErrorHandler = ({ error, event }: any) => { console.error('An error occurred on the client side:', error, event); }; export const handleError = Sentry.handleErrorWithSentry(myErrorHandler); if (import.meta.env.DEV) { Spotlight.init({ injectImmediately: true, }); }

几个关键配置值得注意:

  • dsn来自$env/dynamic/public:客户端代码只能读取PUBLIC_前缀的公开环境变量,即PUBLIC_E2E_TEST_DSN;
  • tunnel指向http://localhost:3031/:事件并不直接发往真实 Sentry 服务器,而是经本地代理转发(后文详述),这使 CI 环境无需公网 DSN;
  • tracesSampleRate: 1.0:全量采样,保证 E2E 断言总能收到性能事件;
  • Sentry.handleErrorWithSentry:把 SvelteKit 的错误处理钩子包装进 Sentry,路由级错误(如 load 抛错)会被自动捕获并上报;
  • Spotlight:仅在开发模式(import.meta.env.DEV)注入,用于人工调试 trace,与测试无关。

服务端埋点:hooks.server.ts

hooks.server.ts 与客户端对称,但多了 SvelteKit 特有的请求处理钩子:

import { E2E_TEST_DSN } from '$env/static/private'; import * as Sentry from '@sentry/sveltekit'; import { setupSidecar } from '@spotlightjs/spotlight/sidecar'; Sentry.init({ environment: 'qa', dsn: E2E_TEST_DSN, debug: !!process.env.DEBUG, tunnel: `http://localhost:3031/`, tracesSampleRate: 1.0, spotlight: import.meta.env.DEV, }); // 不向控制台输出,避免测试日志噪音 export const handleError = Sentry.handleErrorWithSentry(() => {}); export const handle = Sentry.sentryHandle(); if (import.meta.env.DEV) { setupSidecar(); }
  • 服务端 DSN 通过$env/static/private静态读取(构建期内联,不会暴露到客户端);
  • export const handle = Sentry.sentryHandle()是 SvelteKit 服务端请求处理钩子,负责创建每个请求的 HTTP 事务,并处理 trace 的跨端串联;
  • 服务端handleError被有意配置为不打印日志(源码注释说明是为了避免测试输出噪音),错误是否上报完全由 Playwright 侧断言验证。

Vite 插件:sentrySvelteKit

vite.config.ts 展示了 SDK 的构建期集成:

import { sentrySvelteKit } from '@sentry/sveltekit/vite'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ sentrySvelteKit({ autoUploadSourceMaps: false, }), sveltekit(), ], });

sentrySvelteKit插件来自@sentry/sveltekit/vite子导出,负责 source map 处理与 release/debugId 注入;这里显式设置autoUploadSourceMaps: false,因为 E2E 场景不需要把 source map 上传到 Sentry 服务器。

E2E 测试基础设施:Playwright + 本地事件代理

事件代理服务器

应用目录下的 start-event-proxy.mjs 只有寥寥几行,却解释了tunnel: http://localhost:3031/的来历:

import { startEventProxyServer } from '@sentry-internal/test-utils'; startEventProxyServer({ port: 3031, proxyServerName: 'sveltekit-2-svelte-5', });

@sentry-internal/test-utils(对应仓库中的 dev-packages/test-utils 包)提供了一个事件代理:SDK 上报的 error/transaction 事件先打到这个本地服务器,测试代码再按proxyServerName(即'sveltekit-2-svelte-5')订阅、过滤并断言这些事件。package.json中的proxy脚本(node start-event-proxy.mjs)就是启动它的入口。

Playwright 配置

playwright.config.mjs 同样复用 test-utils 的统一工厂:

import { getPlaywrightConfig } from '@sentry-internal/test-utils'; const config = getPlaywrightConfig({ startCommand: 'pnpm preview --port 3030', port: 3030, }); export default config;

startCommand: 'pnpm preview --port 3030'与package.json中test:build的先决条件呼应:E2E 断言跑在生产构建 +vite preview(3030 端口)之上,而事件代理监听 3031 端口,两者互不冲突。

稳定性关键:waitForInitialPageload

tests/utils.ts 提供了一个消除测试抖动的核心辅助函数,其注释直接说明了设计动机:

export async function waitForInitialPageload( page: Page, opts?: { route?: string; parameterizedRoute?: string; debug?: boolean }, ) { const route = opts?.route ?? '/'; const spanName = opts?.parameterizedRoute ?? route; const debug = opts?.debug ?? false; const clientPageloadSpanPromise = waitForStreamedSpan('sveltekit-2-svelte-5', span => { return span.name === spanName && getSpanOp(span) === 'pageload' && span.is_segment; }); await Promise.all([ page.goto(route), // 测试应用在 hydration 完成时会向 body 添加 "hydrated" class page.waitForSelector('body.hydrated'), // 同时等待初始 pageload span,避免后续导航与其竞争 clientPageloadSpanPromise, ]); }

这里有两个协作点:

  1. hydration 信号:根布局 src/routes/+layout.svelte 在onMount时给body添加hydratedclass:

    <script lang="ts"> import { onMount } from 'svelte'; onMount(() => { // 标记 SvelteKit 应用已完成 hydration document.body.classList.add('hydrated'); }); </script>

    测试端用page.waitForSelector('body.hydrated')精确等待客户端接管完成。

  2. 等待 pageload segment span:同时通过代理等待op === 'pageload'且is_segment的 span 被发送。注释指出,如果导航发生得太快、pageload 空闲 span 仍处于活跃状态,routing span 可能被挂到 pageload span 之下,从而引入大量 flaky;先等 pageload 事件落地再发起导航即可排除这类竞态。

测试覆盖:错误捕获与性能链路

tests 目录下共有 5 个测试文件(errors.client.test.ts、errors.server.test.ts、performance.client.test.ts、performance.server.test.ts、performance.test.ts),与src/routes/下精心布置的"故障路由"一一对应:client-error、server-load-error、server-route-error、universal-load-error、universal-load-fetch、users/[id]等。

客户端错误:errors.client.test.ts

errors.client.test.ts 的第一个用例验证点击抛错的完整上报链路:

test('captures error thrown on click', async ({ page }) => { await waitForInitialPageload(page, { route: '/client-error' }); const errorEventPromise = waitForError('sveltekit-2-svelte-5', errorEvent => { return errorEvent?.exception?.values?.[0]?.value === 'Click Error'; }); await page.getByText('Throw error').click(); await expect(errorEventPromise).resolves.toBeDefined(); const errorEvent = await errorEventPromise; const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( expect.objectContaining({ function: expect.stringContaining('HTMLButtonElement'), lineno: 1, in_app: true, }), ); expect(errorEvent.transaction).toEqual('/client-error'); });

断言覆盖了三个层面:异常值本身(Click Error)、调用栈末帧特征(按钮事件处理器、in_app: true)、以及事件关联的事务名(/client-error)。第二个用例则验证"universal load"中抛出的浏览器侧 load 错误(Universal Load Error (browser))同样被捕获并正确关联事务。

性能:pageload / navigation 与分布式 trace

performance.test.ts 中的用例展示了 SDK 自动埋点应产生的 span 结构。以分布式 pageload trace 为例:

test('capture a distributed pageload trace', async ({ page }) => { const traceSpansPromise = collectStreamedSpans('sveltekit-2-svelte-5', spansOfTrace => { const hasClientSegment = spansOfTrace.some(span => span.name === '/users/[id]' && span.is_segment); const hasServerSegment = spansOfTrace.some(span => span.name === 'GET /users/[id]' && span.is_segment); return hasClientSegment && hasServerSegment; }); const [_, traceSpans] = await Promise.all([ page.goto('/users/123xyz'), traceSpansPromise, expect(page.getByText('User id: 123xyz')).toBeVisible(), ]); const clientSpan = traceSpans.find(span => span.name === '/users/[id]' && span.is_segment)!; const serverSpan = traceSpans.find(span => span.name === 'GET /users/[id]' && span.is_segment)!; expect(clientSpan.attributes).toMatchObject({ 'sentry.op': { value: 'pageload', type: 'string' }, 'sentry.origin': { value: 'auto.pageload.sveltekit', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.path': { value: '/users/123xyz', type: 'string' }, 'url.template': { value: '/users/[id]', type: 'string' }, }); expect(serverSpan.attributes).toMatchObject({ 'sentry.op': { value: 'http.server', type: 'string' }, 'sentry.origin': { value: 'auto.http.sveltekit', type: 'string' }, }); // 客户端与服务端 span 同属一条 trace expect(clientSpan.trace_id).toBe(serverSpan.trace_id); // 服务端 span 是客户端 span 的 parent expect(clientSpan.parent_span_id).toBe(serverSpan.span_id); });

这个用例对@sentry/sveltekit的行为提出了精确契约:

  • 客户端 pageload segment 的 op 必须是pageload、origin 为auto.pageload.sveltekit,且 span 名采用路由模板/users/[id](sentry.segment.name.source为route);
  • 服务端 segment 的 op 为http.server、origin 为auto.http.sveltekit;
  • 两端trace_id相同(分布式 trace 连通),且服务端 span 是客户端 span 的父节点——这是 SvelteKit 服务端渲染场景下 SDK 的 trace 组织约定。

同类用例还覆盖了 SPA 导航(navigationop +auto.navigation.sveltekitorigin,见/users路由)以及 universal load 中 fetch 调用派生的服务端 span(GET /api/users),说明测试同时验证了 src/routes/universal-load-fetch/+page.ts 与 src/routes/api/users/+server.ts 构成的跨层调用链。

小结:一个应用承载的完整验证矩阵

层面机制关键文件
脚手架与运行create-svelte 标准流程,vite dev/vite build/vite previewREADME.md、package.json
适配器@sveltejs/adapter-auto,本地vite preview验证svelte.config.js
客户端埋点Sentry.init+handleErrorWithSentry,tunnel 走本地代理src/hooks.client.ts
服务端埋点sentryHandle()+handleErrorWithSentrysrc/hooks.server.ts
构建期集成sentrySvelteKitVite 插件(关闭自动上传)vite.config.ts
事件采集3031 端口事件代理 +proxyServerName过滤start-event-proxy.mjs
测试驱动Playwright 启动pnpm preview --port 3030playwright.config.mjs
稳定性保障等待body.hydrated+ pageload segment 落地tests/utils.ts、+layout.svelte
行为断言错误捕获、pageload/navigation/http.server span 契约、跨端 trace 连通tests 目录

整体来看,这个应用把 create-svelte 模板中最基础的"创建—开发—构建"流程,扩展为一套针对 SvelteKit 2 + Svelte 5 前沿组合的 SDK 回归验证装置:README 描述的是脚手架通用操作,而真正的工程价值在于它锁定了 Svelte 5 版本、打包安装了本地@sentry/sveltekit产物,并用 Playwright + 事件代理对客户端错误、服务端错误、自动性能埋点与跨端 trace 串联做出了可执行的验收标准。

  • 可观测性

【免费下载链接】sentry-javascript

Official Sentry SDKs for JavaScript

项目地址:https://gitcode.com/gh_mirrors/se/sentry-javascript
点击查看免费下载
上一篇:深入解析 Bokeh CodeRunner:Bokeh 服务器应用代码编译与执行引擎
下一篇:KernelSU Metamodule 架构指南:从模块挂载原理到自定义挂载实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 17:37:48

基于YOLOv8与CRNN的轮胎字符识别方案:从数据标注到模型部署

简介&#xff1a;一份面向计算机、通信、人工智能、自动化等相关专业师生与从业者的机器学习期末大作业项目&#xff0c;基于机器学习完成轮胎字符识别&#xff0c;配套完整源码、预训练模型和使用说明&#xff0c;适合作为课程设计、期末大作业或毕业设计参考&#xff0c;也适…

作者头像 李华
网站建设 2026/9/25 17:32:58

基于语义地图的激光雷达定位:动态车间高精度匹配实战

简介&#xff1a;这是一份面向机器人定位与自动驾驶方向学习者的技术文档&#xff0c;围绕「基于语义地图的激光雷达定位方法」展开&#xff0c;适合具备一定SLAM与点云处理基础的研究生、算法工程师参考。文档系统梳理了语义地图、SLAM、LiDAR、语义分割、形态学滤波与全局定位…

作者头像 李华
网站建设 2026/9/25 17:32:37

工程五金选型实战手册:从螺栓强度等级到扭矩控制与表面处理

1. 为什么每位工程人都该有一本五金手册干了十几年项目&#xff0c;我最大的体会是&#xff1a;工地上百分之八十的返工和维修&#xff0c;根源不在图纸&#xff0c;不在结构计算&#xff0c;而在一颗螺栓选错了、一根钉子打偏了、一种表面处理没匹配对环境。甲方催工期的时候没…

作者头像 李华