From d7cfc95ef805ee15d3e0170958bda4500b648230 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 7 May 2024 13:38:39 +0200 Subject: [PATCH 01/13] feat(koa): Warn if koa is not instrumented (#11931) This prints a warning if we detect that the koa app passed to setupKoaErrorHandler is not instrumented. This happens if you import koa before calling Sentry.init. --- packages/node/src/integrations/tracing/koa.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/koa.ts b/packages/node/src/integrations/tracing/koa.ts index 04c2991ba7dc..163f59990965 100644 --- a/packages/node/src/integrations/tracing/koa.ts +++ b/packages/node/src/integrations/tracing/koa.ts @@ -1,3 +1,4 @@ +import { isWrapped } from '@opentelemetry/core'; import { KoaInstrumentation } from '@opentelemetry/instrumentation-koa'; import { SEMATTRS_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; import { @@ -9,7 +10,7 @@ import { } from '@sentry/core'; import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; import type { IntegrationFn } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { consoleSandbox, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../debug-build'; const _koaIntegration = (() => { @@ -48,4 +49,13 @@ export const setupKoaErrorHandler = (app: { use: (arg0: (ctx: any, next: any) => captureException(error); } }); + + if (!isWrapped(app.use)) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[Sentry] Koa is not instrumented. This is likely because you required/imported koa before calling `Sentry.init()`.', + ); + }); + } }; From d64d4588dd53a2aebdc202dd67adc5d02e4c52ca Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 7 May 2024 13:38:56 +0200 Subject: [PATCH 02/13] feat(express): Warn if express is not instrumented (#11930) This prints a warning if we detect that the express `app` passed to `setupExpressErrorHandler` is not instrumented. This happens if you import express _before_ calling Sentry.init: image --- packages/node/src/integrations/tracing/express.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/express.ts b/packages/node/src/integrations/tracing/express.ts index 8f0e93771403..d9ef43f42d0e 100644 --- a/packages/node/src/integrations/tracing/express.ts +++ b/packages/node/src/integrations/tracing/express.ts @@ -5,7 +5,8 @@ import { captureException, getClient, getIsolationScope } from '@sentry/core'; import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; import type { IntegrationFn } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { isWrapped } from '@opentelemetry/core'; +import { consoleSandbox, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../debug-build'; import type { NodeClient } from '../../sdk/client'; import { addOriginToSpan } from '../../utils/addOriginToSpan'; @@ -117,6 +118,15 @@ export function expressErrorHandler(options?: { */ export function setupExpressErrorHandler(app: { use: (middleware: ExpressMiddleware) => unknown }): void { app.use(expressErrorHandler()); + + if (!isWrapped(app.use)) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[Sentry] Express is not instrumented. This is likely because you required/imported express before calling `Sentry.init()`.', + ); + }); + } } function getStatusCodeFromResponse(error: MiddlewareError): number { From 7cf3ecd3176007ebaf499a3bb5bf30e3f9972dd7 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 7 May 2024 15:04:16 +0200 Subject: [PATCH 03/13] feat(hapi): Warn if hapi is not instrumented (#11937) Aaand another one... --- .../node/src/integrations/tracing/hapi/index.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/hapi/index.ts b/packages/node/src/integrations/tracing/hapi/index.ts index e87ff70a0412..595c23cfda4c 100644 --- a/packages/node/src/integrations/tracing/hapi/index.ts +++ b/packages/node/src/integrations/tracing/hapi/index.ts @@ -1,3 +1,4 @@ +import { isWrapped } from '@opentelemetry/core'; import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi'; import { SDK_VERSION, @@ -11,7 +12,7 @@ import { } from '@sentry/core'; import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; import type { IntegrationFn } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { consoleSandbox, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../../debug-build'; import type { Boom, RequestEvent, ResponseObject, Server } from './types'; @@ -92,4 +93,14 @@ export const hapiErrorPlugin = { */ export async function setupHapiErrorHandler(server: Server): Promise { await server.register(hapiErrorPlugin); + + // eslint-disable-next-line @typescript-eslint/unbound-method + if (!isWrapped(server.register)) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[Sentry] Hapi is not instrumented. This is likely because you required/imported hapi before calling `Sentry.init()`.', + ); + }); + } } From 9eeb1486413c0a50afa433a2b4108cb416520d22 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 7 May 2024 15:04:28 +0200 Subject: [PATCH 04/13] feat(connect): Warn if connect is not instrumented (#11936) Another one...! --- packages/node/src/integrations/tracing/connect.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/node/src/integrations/tracing/connect.ts b/packages/node/src/integrations/tracing/connect.ts index 7a65f655679b..1711073361e1 100644 --- a/packages/node/src/integrations/tracing/connect.ts +++ b/packages/node/src/integrations/tracing/connect.ts @@ -1,7 +1,9 @@ +import { isWrapped } from '@opentelemetry/core'; import { ConnectInstrumentation } from '@opentelemetry/instrumentation-connect'; import { captureException, defineIntegration } from '@sentry/core'; import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; import type { IntegrationFn } from '@sentry/types'; +import { consoleSandbox } from '@sentry/utils'; type ConnectApp = { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -27,4 +29,13 @@ function connectErrorMiddleware(err: any, req: any, res: any, next: any): void { export const setupConnectErrorHandler = (app: ConnectApp): void => { app.use(connectErrorMiddleware); + + if (!isWrapped(app.use)) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[Sentry] Connect is not instrumented. This is likely because you required/imported connect before calling `Sentry.init()`.', + ); + }); + } }; From 3804ff01e65421947a722c8711c8f7d587d7c331 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 7 May 2024 15:19:30 +0200 Subject: [PATCH 05/13] fix(feedback): Pick user from any scope (#11928) The user can be set on any scope, we should pick it correctly from wherever it is set. --- packages/feedback/src/modal/integration.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/feedback/src/modal/integration.tsx b/packages/feedback/src/modal/integration.tsx index 02a6597f97e1..cf5048c53619 100644 --- a/packages/feedback/src/modal/integration.tsx +++ b/packages/feedback/src/modal/integration.tsx @@ -1,4 +1,4 @@ -import { getCurrentScope } from '@sentry/core'; +import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import type { CreateDialogProps, FeedbackFormData, FeedbackModalIntegration, IntegrationFn } from '@sentry/types'; import { h, render } from 'preact'; import { DOCUMENT } from '../constants'; @@ -13,8 +13,7 @@ export const feedbackModalIntegration = ((): FeedbackModalIntegration => { createDialog: ({ options, screenshotIntegration, sendFeedback, shadow }: CreateDialogProps) => { const shadowRoot = shadow as unknown as ShadowRoot; const userKey = options.useSentryUser; - const scope = getCurrentScope(); - const user = scope && scope.getUser(); + const user = getCurrentScope().getUser() || getIsolationScope().getUser() || getGlobalScope().getUser(); const el = DOCUMENT.createElement('div'); const style = createDialogStyles(); From 3138695b24d69001e4b8e9661862d25f0c2f5856 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 7 May 2024 16:12:52 +0200 Subject: [PATCH 06/13] fix(browser): Continuously record CLS web vital (#11934) --- .../browser-utils/src/metrics/instrument.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/browser-utils/src/metrics/instrument.ts b/packages/browser-utils/src/metrics/instrument.ts index ad390191266f..acd3717e8bd4 100644 --- a/packages/browser-utils/src/metrics/instrument.ts +++ b/packages/browser-utils/src/metrics/instrument.ts @@ -202,12 +202,17 @@ function triggerHandlers(type: InstrumentHandlerType, data: unknown): void { } function instrumentCls(): StopListening { - return onCLS(metric => { - triggerHandlers('cls', { - metric, - }); - _previousCls = metric; - }); + return onCLS( + metric => { + triggerHandlers('cls', { + metric, + }); + _previousCls = metric; + }, + // We want the callback to be called whenever the CLS value updates. + // By default, the callback is only called when the tab goes to the background. + { reportAllChanges: true }, + ); } function instrumentFid(): void { From 29e89e1d6b2f73dac07c80efc5cc632c00169e05 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 7 May 2024 16:20:00 +0200 Subject: [PATCH 07/13] feat(fastify): Warn if fastify is not instrumented (#11917) When using `setupFastifyErrorHandler(app)` we can check if the app was correctly instrumented. Not sure how to best test this - I tested this manually in a test app and could verify that the log was printed when requiring fastify before calling init! --- packages/node/src/integrations/tracing/fastify.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/node/src/integrations/tracing/fastify.ts b/packages/node/src/integrations/tracing/fastify.ts index 59622d826aaa..bf6e683f3fb6 100644 --- a/packages/node/src/integrations/tracing/fastify.ts +++ b/packages/node/src/integrations/tracing/fastify.ts @@ -1,7 +1,9 @@ +import { isWrapped } from '@opentelemetry/core'; import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify'; import { captureException, defineIntegration, getIsolationScope } from '@sentry/core'; import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; import type { IntegrationFn } from '@sentry/types'; +import { consoleSandbox } from '@sentry/utils'; import { addOriginToSpan } from '../../utils/addOriginToSpan'; @@ -81,4 +83,13 @@ export function setupFastifyErrorHandler(fastify: Fastify): void { ); fastify.register(plugin); + + if (!isWrapped(fastify.addHook)) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[Sentry] Fastify is not instrumented. This is likely because you required/imported fastify before calling `Sentry.init()`.', + ); + }); + } } From 31d462e16e4153af83af7c57a3d82155011bc5bb Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 7 May 2024 16:59:33 +0200 Subject: [PATCH 08/13] test: Streamline E2E test app names (#11828) No need to have the `-app` suffix there. --- .github/workflows/build.yml | 11 +++++------ .../{node-connect-app => node-connect}/.npmrc | 0 .../package.json | 2 +- .../playwright.config.ts | 0 .../src/app.ts | 0 .../start-event-proxy.ts | 2 +- .../tests/errors.test.ts | 2 +- .../tests/transactions.test.ts | 2 +- .../tsconfig.json | 0 .../node-express-app/start-event-proxy.ts | 6 ------ .../.gitignore | 0 .../{node-express-app => node-express}/.npmrc | 0 .../package.json | 0 .../playwright.config.ts | 0 .../src/app.ts | 0 .../start-event-proxy.ts | 2 +- .../tests/error.test.ts | 4 ++-- .../tests/transaction.test.ts | 0 .../tests/trpc.test.ts | 12 ++++++------ .../tsconfig.json | 0 .../.gitignore | 0 .../{node-fastify-app => node-fastify}/.npmrc | 0 .../package.json | 2 +- .../playwright.config.ts | 0 .../src/app.ts | 0 .../start-event-proxy.ts | 2 +- .../tests/errors.test.ts | 2 +- .../tests/propagation.test.ts | 16 ++++++++-------- .../tests/transactions.test.ts | 2 +- .../tsconfig.json | 0 .../{node-koa-app => node-koa}/.npmrc | 0 .../{node-koa-app => node-koa}/index.js | 0 .../{node-koa-app => node-koa}/package.json | 2 +- .../playwright.config.ts | 0 .../start-event-proxy.ts | 2 +- .../tests/errors.test.ts | 2 +- .../tests/propagation.test.ts | 16 ++++++++-------- .../tests/transactions.test.ts | 2 +- .../{node-koa-app => node-koa}/tsconfig.json | 0 .../{node-koa-app => node-koa}/yarn.lock | 0 .../{node-nestjs-app => node-nestjs}/.gitignore | 0 .../{node-nestjs-app => node-nestjs}/.npmrc | 0 .../nest-cli.json | 0 .../package.json | 2 +- .../playwright.config.ts | 0 .../src/app.controller.ts | 0 .../src/app.module.ts | 0 .../src/app.service.ts | 0 .../{node-nestjs-app => node-nestjs}/src/main.ts | 0 .../src/utils.ts | 0 .../node-nestjs/start-event-proxy.ts | 6 ++++++ .../tests/errors.test.ts | 2 +- .../tests/propagation.test.ts | 16 ++++++++-------- .../tests/transactions.test.ts | 2 +- .../tsconfig.build.json | 0 .../tsconfig.json | 0 56 files changed, 59 insertions(+), 60 deletions(-) rename dev-packages/e2e-tests/test-applications/{node-connect-app => node-connect}/.npmrc (100%) rename dev-packages/e2e-tests/test-applications/{node-connect-app => node-connect}/package.json (96%) rename dev-packages/e2e-tests/test-applications/{node-connect-app => node-connect}/playwright.config.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-connect-app => node-connect}/src/app.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-connect}/start-event-proxy.ts (77%) rename dev-packages/e2e-tests/test-applications/{node-connect-app => node-connect}/tests/errors.test.ts (96%) rename dev-packages/e2e-tests/test-applications/{node-connect-app => node-connect}/tests/transactions.test.ts (99%) rename dev-packages/e2e-tests/test-applications/{node-connect-app => node-connect}/tsconfig.json (100%) delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-app/start-event-proxy.ts rename dev-packages/e2e-tests/test-applications/{node-express-app => node-express}/.gitignore (100%) rename dev-packages/e2e-tests/test-applications/{node-express-app => node-express}/.npmrc (100%) rename dev-packages/e2e-tests/test-applications/{node-express-app => node-express}/package.json (100%) rename dev-packages/e2e-tests/test-applications/{node-express-app => node-express}/playwright.config.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-express-app => node-express}/src/app.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-express}/start-event-proxy.ts (75%) rename dev-packages/e2e-tests/test-applications/{node-express-app => node-express}/tests/error.test.ts (96%) rename dev-packages/e2e-tests/test-applications/{node-express-app => node-express}/tests/transaction.test.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-express-app => node-express}/tests/trpc.test.ts (93%) rename dev-packages/e2e-tests/test-applications/{node-express-app => node-express}/tsconfig.json (100%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-fastify}/.gitignore (100%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-fastify}/.npmrc (100%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-fastify}/package.json (96%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-fastify}/playwright.config.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-fastify}/src/app.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-connect-app => node-fastify}/start-event-proxy.ts (75%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-fastify}/tests/errors.test.ts (97%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-fastify}/tests/propagation.test.ts (97%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-fastify}/tests/transactions.test.ts (99%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-fastify}/tsconfig.json (100%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-koa}/.npmrc (100%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-koa}/index.js (100%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-koa}/package.json (96%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-koa}/playwright.config.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-koa}/start-event-proxy.ts (75%) rename dev-packages/e2e-tests/test-applications/{node-fastify-app => node-koa}/tests/errors.test.ts (96%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-koa}/tests/propagation.test.ts (98%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-koa}/tests/transactions.test.ts (99%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-koa}/tsconfig.json (100%) rename dev-packages/e2e-tests/test-applications/{node-koa-app => node-koa}/yarn.lock (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/.gitignore (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/.npmrc (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/nest-cli.json (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/package.json (98%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/playwright.config.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/src/app.controller.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/src/app.module.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/src/app.service.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/src/main.ts (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/src/utils.ts (100%) create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs/start-event-proxy.ts rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/tests/errors.test.ts (96%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/tests/propagation.test.ts (97%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/tests/transactions.test.ts (99%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/tsconfig.build.json (100%) rename dev-packages/e2e-tests/test-applications/{node-nestjs-app => node-nestjs}/tsconfig.json (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99ca517863ac..01f9ab5584f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -995,7 +995,7 @@ jobs: [ 'angular-17', 'cloudflare-astro', - 'node-express-app', + 'node-express', 'create-react-app', 'create-next-app', 'create-remix-app', @@ -1015,13 +1015,12 @@ jobs: 'sveltekit-2', 'sveltekit-2-svelte-5', 'generic-ts3.8', - 'node-fastify-app', - # TODO(v8): Re-enable hapi tests + 'node-fastify', 'node-hapi', - 'node-nestjs-app', + 'node-nestjs', 'node-exports-test-app', - 'node-koa-app', - 'node-connect-app', + 'node-koa', + 'node-connect', 'vue-3', 'webpack-4', 'webpack-5' diff --git a/dev-packages/e2e-tests/test-applications/node-connect-app/.npmrc b/dev-packages/e2e-tests/test-applications/node-connect/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-connect-app/.npmrc rename to dev-packages/e2e-tests/test-applications/node-connect/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/node-connect-app/package.json b/dev-packages/e2e-tests/test-applications/node-connect/package.json similarity index 96% rename from dev-packages/e2e-tests/test-applications/node-connect-app/package.json rename to dev-packages/e2e-tests/test-applications/node-connect/package.json index ad93a513eca8..24ecbcea1b02 100644 --- a/dev-packages/e2e-tests/test-applications/node-connect-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-connect/package.json @@ -1,5 +1,5 @@ { - "name": "node-connect-app", + "name": "node-connect", "version": "1.0.0", "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/node-connect-app/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-connect/playwright.config.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-connect-app/playwright.config.ts rename to dev-packages/e2e-tests/test-applications/node-connect/playwright.config.ts diff --git a/dev-packages/e2e-tests/test-applications/node-connect-app/src/app.ts b/dev-packages/e2e-tests/test-applications/node-connect/src/app.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-connect-app/src/app.ts rename to dev-packages/e2e-tests/test-applications/node-connect/src/app.ts diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/node-connect/start-event-proxy.ts similarity index 77% rename from dev-packages/e2e-tests/test-applications/node-koa-app/start-event-proxy.ts rename to dev-packages/e2e-tests/test-applications/node-connect/start-event-proxy.ts index 65eda84d3f8a..cac2f8db1f82 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa-app/start-event-proxy.ts +++ b/dev-packages/e2e-tests/test-applications/node-connect/start-event-proxy.ts @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; startEventProxyServer({ port: 3031, - proxyServerName: 'node-koa-app', + proxyServerName: 'node-connect', }); diff --git a/dev-packages/e2e-tests/test-applications/node-connect-app/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-connect/tests/errors.test.ts similarity index 96% rename from dev-packages/e2e-tests/test-applications/node-connect-app/tests/errors.test.ts rename to dev-packages/e2e-tests/test-applications/node-connect/tests/errors.test.ts index 68ba042612d9..c83fa1b38889 100644 --- a/dev-packages/e2e-tests/test-applications/node-connect-app/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-connect/tests/errors.test.ts @@ -40,7 +40,7 @@ test('Sends exception to Sentry', async ({ baseURL }) => { }); test('Sends correct error event', async ({ baseURL }) => { - const errorEventPromise = waitForError('node-connect-app', event => { + const errorEventPromise = waitForError('node-connect', event => { return !event.type && event.exception?.values?.[0]?.value === 'This is an exception'; }); diff --git a/dev-packages/e2e-tests/test-applications/node-connect-app/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts similarity index 99% rename from dev-packages/e2e-tests/test-applications/node-connect-app/tests/transactions.test.ts rename to dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts index f42c417371c6..4a9548015422 100644 --- a/dev-packages/e2e-tests/test-applications/node-connect-app/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts @@ -8,7 +8,7 @@ const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; const EVENT_POLLING_TIMEOUT = 90_000; test('Sends an API route transaction', async ({ baseURL }) => { - const pageloadTransactionEventPromise = waitForTransaction('node-connect-app', transactionEvent => { + const pageloadTransactionEventPromise = waitForTransaction('node-connect', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'GET /test-transaction' diff --git a/dev-packages/e2e-tests/test-applications/node-connect-app/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-connect/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-connect-app/tsconfig.json rename to dev-packages/e2e-tests/test-applications/node-connect/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/node-express-app/start-event-proxy.ts deleted file mode 100644 index 369041a9c792..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-app/start-event-proxy.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'node-express-app', -}); diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/.gitignore b/dev-packages/e2e-tests/test-applications/node-express/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-express-app/.gitignore rename to dev-packages/e2e-tests/test-applications/node-express/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/.npmrc b/dev-packages/e2e-tests/test-applications/node-express/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-express-app/.npmrc rename to dev-packages/e2e-tests/test-applications/node-express/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/package.json b/dev-packages/e2e-tests/test-applications/node-express/package.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-express-app/package.json rename to dev-packages/e2e-tests/test-applications/node-express/package.json diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-express/playwright.config.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-express-app/playwright.config.ts rename to dev-packages/e2e-tests/test-applications/node-express/playwright.config.ts diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts rename to dev-packages/e2e-tests/test-applications/node-express/src/app.ts diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/node-express/start-event-proxy.ts similarity index 75% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/start-event-proxy.ts rename to dev-packages/e2e-tests/test-applications/node-express/start-event-proxy.ts index 9f99b9ac5d23..a31586dd09fc 100644 --- a/dev-packages/e2e-tests/test-applications/node-nestjs-app/start-event-proxy.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/start-event-proxy.ts @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; startEventProxyServer({ port: 3031, - proxyServerName: 'node-nestjs-app', + proxyServerName: 'node-express', }); diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/tests/error.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/error.test.ts similarity index 96% rename from dev-packages/e2e-tests/test-applications/node-express-app/tests/error.test.ts rename to dev-packages/e2e-tests/test-applications/node-express/tests/error.test.ts index 23d922b50a84..4e725f8eb8ad 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-app/tests/error.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/error.test.ts @@ -40,7 +40,7 @@ test('Sends exception to Sentry', async ({ baseURL }) => { }); test('Sends correct error event', async ({ baseURL }) => { - const errorEventPromise = waitForError('node-express-app', event => { + const errorEventPromise = waitForError('node-express', event => { return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; }); @@ -109,7 +109,7 @@ test('Should record caught exceptions with local variable', async ({ baseURL }) }); test('Should record uncaught exceptions with local variable', async ({ baseURL }) => { - const errorEventPromise = waitForError('node-express-app', errorEvent => { + const errorEventPromise = waitForError('node-express', errorEvent => { return !!errorEvent?.exception?.values?.[0]?.value?.includes('Uncaught Local Variable Error'); }); diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/tests/transaction.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transaction.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-express-app/tests/transaction.test.ts rename to dev-packages/e2e-tests/test-applications/node-express/tests/transaction.test.ts diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/tests/trpc.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts similarity index 93% rename from dev-packages/e2e-tests/test-applications/node-express-app/tests/trpc.test.ts rename to dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts index 4b287946d86d..368f14bd43ee 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-app/tests/trpc.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts @@ -4,7 +4,7 @@ import { createTRPCProxyClient, httpBatchLink } from '@trpc/client'; import type { AppRouter } from '../src/app'; test('Should record span for trpc query', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('node-express-app', transactionEvent => { + const transactionEventPromise = waitForTransaction('node-express', transactionEvent => { return ( transactionEvent.transaction === 'GET /trpc' && !!transactionEvent.spans?.find(span => span.description === 'trpc/getSomething') @@ -41,7 +41,7 @@ test('Should record span for trpc query', async ({ baseURL }) => { }); test('Should record transaction for trpc mutation', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('node-express-app', transactionEvent => { + const transactionEventPromise = waitForTransaction('node-express', transactionEvent => { return ( transactionEvent.transaction === 'POST /trpc' && !!transactionEvent.spans?.find(span => span.description === 'trpc/createSomething') @@ -77,14 +77,14 @@ test('Should record transaction for trpc mutation', async ({ baseURL }) => { }); test('Should record transaction and error for a crashing trpc handler', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('node-express-app', transactionEvent => { + const transactionEventPromise = waitForTransaction('node-express', transactionEvent => { return ( transactionEvent.transaction === 'POST /trpc' && !!transactionEvent.spans?.find(span => span.description === 'trpc/crashSomething') ); }); - const errorEventPromise = waitForError('node-express-app', errorEvent => { + const errorEventPromise = waitForError('node-express', errorEvent => { return !!errorEvent?.exception?.values?.some(exception => exception.value?.includes('I crashed in a trpc handler')); }); @@ -103,14 +103,14 @@ test('Should record transaction and error for a crashing trpc handler', async ({ }); test('Should record transaction and error for a trpc handler that returns a status code', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('node-express-app', transactionEvent => { + const transactionEventPromise = waitForTransaction('node-express', transactionEvent => { return ( transactionEvent.transaction === 'POST /trpc' && !!transactionEvent.spans?.find(span => span.description === 'trpc/dontFindSomething') ); }); - const errorEventPromise = waitForError('node-express-app', errorEvent => { + const errorEventPromise = waitForError('node-express', errorEvent => { return !!errorEvent?.exception?.values?.some(exception => exception.value?.includes('Page not found')); }); diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-express/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-express-app/tsconfig.json rename to dev-packages/e2e-tests/test-applications/node-express/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/.gitignore b/dev-packages/e2e-tests/test-applications/node-fastify/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/.gitignore rename to dev-packages/e2e-tests/test-applications/node-fastify/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/.npmrc b/dev-packages/e2e-tests/test-applications/node-fastify/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/.npmrc rename to dev-packages/e2e-tests/test-applications/node-fastify/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/package.json b/dev-packages/e2e-tests/test-applications/node-fastify/package.json similarity index 96% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/package.json rename to dev-packages/e2e-tests/test-applications/node-fastify/package.json index 56c8818933af..0293412ba76b 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-fastify/package.json @@ -1,5 +1,5 @@ { - "name": "node-fastify-app", + "name": "node-fastify", "version": "1.0.0", "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-fastify/playwright.config.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/playwright.config.ts rename to dev-packages/e2e-tests/test-applications/node-fastify/playwright.config.ts diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/src/app.ts b/dev-packages/e2e-tests/test-applications/node-fastify/src/app.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/src/app.ts rename to dev-packages/e2e-tests/test-applications/node-fastify/src/app.ts diff --git a/dev-packages/e2e-tests/test-applications/node-connect-app/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/node-fastify/start-event-proxy.ts similarity index 75% rename from dev-packages/e2e-tests/test-applications/node-connect-app/start-event-proxy.ts rename to dev-packages/e2e-tests/test-applications/node-fastify/start-event-proxy.ts index 97434b7c7b07..e627ce9a8bef 100644 --- a/dev-packages/e2e-tests/test-applications/node-connect-app/start-event-proxy.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify/start-event-proxy.ts @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; startEventProxyServer({ port: 3031, - proxyServerName: 'node-connect-app', + proxyServerName: 'node-fastify', }); diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify/tests/errors.test.ts similarity index 97% rename from dev-packages/e2e-tests/test-applications/node-koa-app/tests/errors.test.ts rename to dev-packages/e2e-tests/test-applications/node-fastify/tests/errors.test.ts index 5759c2bad543..8014803360cd 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa-app/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify/tests/errors.test.ts @@ -40,7 +40,7 @@ test('Sends exception to Sentry', async ({ baseURL }) => { }); test('Sends correct error event', async ({ baseURL }) => { - const errorEventPromise = waitForError('node-koa-app', event => { + const errorEventPromise = waitForError('node-fastify', event => { return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; }); diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify/tests/propagation.test.ts similarity index 97% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/tests/propagation.test.ts rename to dev-packages/e2e-tests/test-applications/node-fastify/tests/propagation.test.ts index 11d8e896b2aa..471d9daa16b5 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-app/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify/tests/propagation.test.ts @@ -7,14 +7,14 @@ import axios from 'axios'; test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const id = crypto.randomUUID(); - const inboundTransactionPromise = waitForTransaction('node-fastify-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-fastify', transactionEvent => { return ( transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` ); }); - const outboundTransactionPromise = waitForTransaction('node-fastify-app', transactionEvent => { + const outboundTransactionPromise = waitForTransaction('node-fastify', transactionEvent => { return ( transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http/${id}` @@ -121,14 +121,14 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { const id = crypto.randomUUID(); - const inboundTransactionPromise = waitForTransaction('node-fastify-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-fastify', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` ); }); - const outboundTransactionPromise = waitForTransaction('node-fastify-app', transactionEvent => { + const outboundTransactionPromise = waitForTransaction('node-fastify', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch/${id}` @@ -233,7 +233,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { }); test('Propagates trace for outgoing external http requests', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-fastify-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-fastify', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-allowed` @@ -269,7 +269,7 @@ test('Propagates trace for outgoing external http requests', async ({ baseURL }) }); test('Does not propagate outgoing http requests not covered by tracePropagationTargets', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-fastify-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-fastify', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-disallowed` @@ -292,7 +292,7 @@ test('Does not propagate outgoing http requests not covered by tracePropagationT }); test('Propagates trace for outgoing external fetch requests', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-fastify-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-fastify', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-allowed` @@ -328,7 +328,7 @@ test('Propagates trace for outgoing external fetch requests', async ({ baseURL } }); test('Does not propagate outgoing fetch requests not covered by tracePropagationTargets', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-fastify-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-fastify', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-disallowed` diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify/tests/transactions.test.ts similarity index 99% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/tests/transactions.test.ts rename to dev-packages/e2e-tests/test-applications/node-fastify/tests/transactions.test.ts index 0e2fe4f2215b..8324a9913d1b 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-app/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify/tests/transactions.test.ts @@ -8,7 +8,7 @@ const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; const EVENT_POLLING_TIMEOUT = 90_000; test('Sends an API route transaction', async ({ baseURL }) => { - const pageloadTransactionEventPromise = waitForTransaction('node-fastify-app', transactionEvent => { + const pageloadTransactionEventPromise = waitForTransaction('node-fastify', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'GET /test-transaction' diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-fastify/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/tsconfig.json rename to dev-packages/e2e-tests/test-applications/node-fastify/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/.npmrc b/dev-packages/e2e-tests/test-applications/node-koa/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-koa-app/.npmrc rename to dev-packages/e2e-tests/test-applications/node-koa/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/index.js b/dev-packages/e2e-tests/test-applications/node-koa/index.js similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-koa-app/index.js rename to dev-packages/e2e-tests/test-applications/node-koa/index.js diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/package.json b/dev-packages/e2e-tests/test-applications/node-koa/package.json similarity index 96% rename from dev-packages/e2e-tests/test-applications/node-koa-app/package.json rename to dev-packages/e2e-tests/test-applications/node-koa/package.json index c9e35dda13ac..34a30fd5f9f1 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-koa/package.json @@ -1,5 +1,5 @@ { - "name": "node-koa-app", + "name": "node-koa", "version": "1.0.0", "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-koa/playwright.config.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-koa-app/playwright.config.ts rename to dev-packages/e2e-tests/test-applications/node-koa/playwright.config.ts diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/node-koa/start-event-proxy.ts similarity index 75% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/start-event-proxy.ts rename to dev-packages/e2e-tests/test-applications/node-koa/start-event-proxy.ts index cb3a189ed920..969f745f2d7a 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-app/start-event-proxy.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/start-event-proxy.ts @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; startEventProxyServer({ port: 3031, - proxyServerName: 'node-fastify-app', + proxyServerName: 'node-koa', }); diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-app/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/errors.test.ts similarity index 96% rename from dev-packages/e2e-tests/test-applications/node-fastify-app/tests/errors.test.ts rename to dev-packages/e2e-tests/test-applications/node-koa/tests/errors.test.ts index 8a4dd6c9bcd7..ce9ade128884 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-app/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/errors.test.ts @@ -40,7 +40,7 @@ test('Sends exception to Sentry', async ({ baseURL }) => { }); test('Sends correct error event', async ({ baseURL }) => { - const errorEventPromise = waitForError('node-fastify-app', event => { + const errorEventPromise = waitForError('node-koa', event => { return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; }); diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts similarity index 98% rename from dev-packages/e2e-tests/test-applications/node-koa-app/tests/propagation.test.ts rename to dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts index 4ed65dbe69a8..801539ebbafe 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa-app/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts @@ -7,14 +7,14 @@ import axios from 'axios'; test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const id = crypto.randomUUID(); - const inboundTransactionPromise = waitForTransaction('node-koa-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-koa', transactionEvent => { return ( transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` ); }); - const outboundTransactionPromise = waitForTransaction('node-koa-app', transactionEvent => { + const outboundTransactionPromise = waitForTransaction('node-koa', transactionEvent => { return ( transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http/${id}` @@ -121,14 +121,14 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { const id = crypto.randomUUID(); - const inboundTransactionPromise = waitForTransaction('node-koa-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-koa', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` ); }); - const outboundTransactionPromise = waitForTransaction('node-koa-app', transactionEvent => { + const outboundTransactionPromise = waitForTransaction('node-koa', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch/${id}` @@ -233,7 +233,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { }); test('Propagates trace for outgoing external http requests', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-koa-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-koa', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-allowed` @@ -269,7 +269,7 @@ test('Propagates trace for outgoing external http requests', async ({ baseURL }) }); test('Does not propagate outgoing http requests not covered by tracePropagationTargets', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-koa-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-koa', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-disallowed` @@ -292,7 +292,7 @@ test('Does not propagate outgoing http requests not covered by tracePropagationT }); test('Propagates trace for outgoing external fetch requests', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-koa-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-koa', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-allowed` @@ -328,7 +328,7 @@ test('Propagates trace for outgoing external fetch requests', async ({ baseURL } }); test('Does not propagate outgoing fetch requests not covered by tracePropagationTargets', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-koa-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-koa', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-disallowed` diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts similarity index 99% rename from dev-packages/e2e-tests/test-applications/node-koa-app/tests/transactions.test.ts rename to dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts index 1ff7c3f78d6e..720c835dcfdd 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa-app/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts @@ -8,7 +8,7 @@ const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; const EVENT_POLLING_TIMEOUT = 90_000; test('Sends an API route transaction', async ({ baseURL }) => { - const pageloadTransactionEventPromise = waitForTransaction('node-koa-app', transactionEvent => { + const pageloadTransactionEventPromise = waitForTransaction('node-koa', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'GET /test-transaction' diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-koa/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-koa-app/tsconfig.json rename to dev-packages/e2e-tests/test-applications/node-koa/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/node-koa-app/yarn.lock b/dev-packages/e2e-tests/test-applications/node-koa/yarn.lock similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-koa-app/yarn.lock rename to dev-packages/e2e-tests/test-applications/node-koa/yarn.lock diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/.gitignore b/dev-packages/e2e-tests/test-applications/node-nestjs/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/.gitignore rename to dev-packages/e2e-tests/test-applications/node-nestjs/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/.npmrc b/dev-packages/e2e-tests/test-applications/node-nestjs/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/.npmrc rename to dev-packages/e2e-tests/test-applications/node-nestjs/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/nest-cli.json b/dev-packages/e2e-tests/test-applications/node-nestjs/nest-cli.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/nest-cli.json rename to dev-packages/e2e-tests/test-applications/node-nestjs/nest-cli.json diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/package.json b/dev-packages/e2e-tests/test-applications/node-nestjs/package.json similarity index 98% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/package.json rename to dev-packages/e2e-tests/test-applications/node-nestjs/package.json index 6135e17ad1a1..8da77df738c5 100644 --- a/dev-packages/e2e-tests/test-applications/node-nestjs-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-nestjs/package.json @@ -1,5 +1,5 @@ { - "name": "node-nestjs-app", + "name": "node-nestjs", "version": "0.0.1", "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/playwright.config.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/playwright.config.ts rename to dev-packages/e2e-tests/test-applications/node-nestjs/playwright.config.ts diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/src/app.controller.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/src/app.controller.ts rename to dev-packages/e2e-tests/test-applications/node-nestjs/src/app.controller.ts diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/src/app.module.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/src/app.module.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/src/app.module.ts rename to dev-packages/e2e-tests/test-applications/node-nestjs/src/app.module.ts diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/src/app.service.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/src/app.service.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/src/app.service.ts rename to dev-packages/e2e-tests/test-applications/node-nestjs/src/app.service.ts diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/src/main.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/src/main.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/src/main.ts rename to dev-packages/e2e-tests/test-applications/node-nestjs/src/main.ts diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/src/utils.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/src/utils.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/src/utils.ts rename to dev-packages/e2e-tests/test-applications/node-nestjs/src/utils.ts diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/start-event-proxy.ts new file mode 100644 index 000000000000..583769c59a92 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs/start-event-proxy.ts @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'node-nestjs', +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/tests/errors.test.ts similarity index 96% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/errors.test.ts rename to dev-packages/e2e-tests/test-applications/node-nestjs/tests/errors.test.ts index 7ad93315a84f..dd0e4cc7e1bf 100644 --- a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-nestjs/tests/errors.test.ts @@ -42,7 +42,7 @@ test('Sends captured error to Sentry', async ({ baseURL }) => { }); test('Sends exception to Sentry', async ({ baseURL }) => { - const errorEventPromise = waitForError('node-nestjs-app', event => { + const errorEventPromise = waitForError('node-nestjs', event => { return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; }); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/tests/propagation.test.ts similarity index 97% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/propagation.test.ts rename to dev-packages/e2e-tests/test-applications/node-nestjs/tests/propagation.test.ts index 698b0833fd0d..e0a75a0b3af0 100644 --- a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-nestjs/tests/propagation.test.ts @@ -7,14 +7,14 @@ import axios from 'axios'; test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const id = crypto.randomUUID(); - const inboundTransactionPromise = waitForTransaction('node-nestjs-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => { return ( transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` ); }); - const outboundTransactionPromise = waitForTransaction('node-nestjs-app', transactionEvent => { + const outboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => { return ( transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http/${id}` @@ -121,14 +121,14 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { const id = crypto.randomUUID(); - const inboundTransactionPromise = waitForTransaction('node-nestjs-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` ); }); - const outboundTransactionPromise = waitForTransaction('node-nestjs-app', transactionEvent => { + const outboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch/${id}` @@ -233,7 +233,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { }); test('Propagates trace for outgoing external http requests', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-nestjs-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-allowed` @@ -269,7 +269,7 @@ test('Propagates trace for outgoing external http requests', async ({ baseURL }) }); test('Does not propagate outgoing http requests not covered by tracePropagationTargets', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-nestjs-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-disallowed` @@ -292,7 +292,7 @@ test('Does not propagate outgoing http requests not covered by tracePropagationT }); test('Propagates trace for outgoing external fetch requests', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-nestjs-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-allowed` @@ -328,7 +328,7 @@ test('Propagates trace for outgoing external fetch requests', async ({ baseURL } }); test('Does not propagate outgoing fetch requests not covered by tracePropagationTargets', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('node-nestjs-app', transactionEvent => { + const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-disallowed` diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-nestjs/tests/transactions.test.ts similarity index 99% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/transactions.test.ts rename to dev-packages/e2e-tests/test-applications/node-nestjs/tests/transactions.test.ts index e5a78a6f6fb7..3fe5c1e65263 100644 --- a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-nestjs/tests/transactions.test.ts @@ -8,7 +8,7 @@ const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; const EVENT_POLLING_TIMEOUT = 90_000; test('Sends an API route transaction', async ({ baseURL }) => { - const pageloadTransactionEventPromise = waitForTransaction('node-nestjs-app', transactionEvent => { + const pageloadTransactionEventPromise = waitForTransaction('node-nestjs', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'GET /test-transaction' diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/node-nestjs/tsconfig.build.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/tsconfig.build.json rename to dev-packages/e2e-tests/test-applications/node-nestjs/tsconfig.build.json diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-nestjs/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-nestjs-app/tsconfig.json rename to dev-packages/e2e-tests/test-applications/node-nestjs/tsconfig.json From 55d41f7c20b8cfcafda9bbeef75d551c1e7e22d7 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 7 May 2024 16:01:12 +0100 Subject: [PATCH 09/13] fix(node): Fix cron instrumentation and add tests (#11811) Closes #11766 These tests are also in TypeScript so they check the types too. I found that two out of three cron libraries were actually swallowing exceptions so that they were not captured by Sentry. I added calls to `captureException` to rectify that. --- .../node-integration-tests/package.json | 4 + .../node-integration-tests/src/index.ts | 2 +- .../suites/cron/cron/scenario.ts | 31 ++++++++ .../suites/cron/cron/test.ts | 75 +++++++++++++++++++ .../suites/cron/node-cron/scenario.ts | 38 ++++++++-- .../suites/cron/node-cron/test.ts | 70 ++++++++++++++++- .../suites/cron/node-schedule/scenario.ts | 29 +++++++ .../suites/cron/node-schedule/test.ts | 75 +++++++++++++++++++ .../node-integration-tests/utils/runner.ts | 22 ++++++ packages/node/src/cron/cron.ts | 24 ++++-- packages/node/src/cron/node-cron.ts | 37 +++++---- packages/node/src/cron/node-schedule.ts | 30 +++++--- yarn.lock | 47 +++++++++++- 13 files changed, 444 insertions(+), 40 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/cron/cron/scenario.ts create mode 100644 dev-packages/node-integration-tests/suites/cron/cron/test.ts create mode 100644 dev-packages/node-integration-tests/suites/cron/node-schedule/scenario.ts create mode 100644 dev-packages/node-integration-tests/suites/cron/node-schedule/test.ts diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 930833be0ff5..c8f358c9f0b6 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -50,6 +50,8 @@ "mongoose": "^5.13.22", "mysql": "^2.18.1", "mysql2": "^3.7.1", + "node-cron": "^3.0.3", + "node-schedule": "^2.1.1", "nock": "^13.1.0", "pg": "^8.7.3", "proxy": "^2.1.1", @@ -58,6 +60,8 @@ "yargs": "^16.2.0" }, "devDependencies": { + "@types/node-cron": "^3.0.11", + "@types/node-schedule": "^2.1.7", "globby": "11" }, "config": { diff --git a/dev-packages/node-integration-tests/src/index.ts b/dev-packages/node-integration-tests/src/index.ts index 46c6e98401ae..08afc11fe7ea 100644 --- a/dev-packages/node-integration-tests/src/index.ts +++ b/dev-packages/node-integration-tests/src/index.ts @@ -13,7 +13,7 @@ export function loggingTransport(_options: BaseTransportOptions): Transport { return Promise.resolve({ statusCode: 200 }); }, flush(): PromiseLike { - return Promise.resolve(true); + return new Promise(resolve => setTimeout(() => resolve(true), 1000)); }, }; } diff --git a/dev-packages/node-integration-tests/suites/cron/cron/scenario.ts b/dev-packages/node-integration-tests/suites/cron/cron/scenario.ts new file mode 100644 index 000000000000..12416fd056ca --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/cron/scenario.ts @@ -0,0 +1,31 @@ +import { loggingTransport } from '@sentry-internal/node-integration-tests'; +import * as Sentry from '@sentry/node'; +import { CronJob } from 'cron'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + autoSessionTracking: false, + transport: loggingTransport, +}); + +const CronJobWithCheckIn = Sentry.cron.instrumentCron(CronJob, 'my-cron-job'); + +let closeNext = false; + +const cron = new CronJobWithCheckIn('* * * * * *', () => { + if (closeNext) { + cron.stop(); + throw new Error('Error in cron job'); + } + + // eslint-disable-next-line no-console + console.log('You will see this message every second'); + closeNext = true; +}); + +cron.start(); + +setTimeout(() => { + process.exit(); +}, 5000); diff --git a/dev-packages/node-integration-tests/suites/cron/cron/test.ts b/dev-packages/node-integration-tests/suites/cron/cron/test.ts new file mode 100644 index 000000000000..ee83f4dc1226 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/cron/test.ts @@ -0,0 +1,75 @@ +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +afterAll(() => { + cleanupChildProcesses(); +}); + +test('cron instrumentation', done => { + createRunner(__dirname, 'scenario.ts') + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'in_progress', + release: '1.0', + monitor_config: { schedule: { type: 'crontab', value: '* * * * * *' } }, + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'ok', + release: '1.0', + duration: expect.any(Number), + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'in_progress', + release: '1.0', + monitor_config: { schedule: { type: 'crontab', value: '* * * * * *' } }, + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'error', + release: '1.0', + duration: expect.any(Number), + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + event: { + exception: { values: [{ type: 'Error', value: 'Error in cron job' }] }, + }, + }) + .start(done); +}); diff --git a/dev-packages/node-integration-tests/suites/cron/node-cron/scenario.ts b/dev-packages/node-integration-tests/suites/cron/node-cron/scenario.ts index 71b005d4dfd6..8fe4f1bd34c5 100644 --- a/dev-packages/node-integration-tests/suites/cron/node-cron/scenario.ts +++ b/dev-packages/node-integration-tests/suites/cron/node-cron/scenario.ts @@ -1,9 +1,37 @@ +import { loggingTransport } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import { CronJob } from 'cron'; +import * as cron from 'node-cron'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const CronJobWithCheckIn = Sentry.cron.instrumentCron(CronJob, 'my-cron-job'); +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + autoSessionTracking: false, + transport: loggingTransport, +}); + +const cronWithCheckIn = Sentry.cron.instrumentNodeCron(cron); + +let closeNext = false; + +const task = cronWithCheckIn.schedule( + '* * * * * *', + () => { + if (closeNext) { + // https://github.com/node-cron/node-cron/issues/317 + setImmediate(() => { + task.stop(); + }); + + throw new Error('Error in cron job'); + } + + // eslint-disable-next-line no-console + console.log('You will see this message every second'); + closeNext = true; + }, + { name: 'my-cron-job' }, +); setTimeout(() => { - process.exit(0); -}, 1_000); + process.exit(); +}, 5000); diff --git a/dev-packages/node-integration-tests/suites/cron/node-cron/test.ts b/dev-packages/node-integration-tests/suites/cron/node-cron/test.ts index b768599cd215..2c3be50907a3 100644 --- a/dev-packages/node-integration-tests/suites/cron/node-cron/test.ts +++ b/dev-packages/node-integration-tests/suites/cron/node-cron/test.ts @@ -4,6 +4,72 @@ afterAll(() => { cleanupChildProcesses(); }); -test('node-cron types should match', done => { - createRunner(__dirname, 'scenario.ts').ensureNoErrorOutput().start(done); +test('node-cron instrumentation', done => { + createRunner(__dirname, 'scenario.ts') + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'in_progress', + release: '1.0', + monitor_config: { schedule: { type: 'crontab', value: '* * * * * *' } }, + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'ok', + release: '1.0', + duration: expect.any(Number), + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'in_progress', + release: '1.0', + monitor_config: { schedule: { type: 'crontab', value: '* * * * * *' } }, + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'error', + release: '1.0', + duration: expect.any(Number), + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + event: { + exception: { values: [{ type: 'Error', value: 'Error in cron job' }] }, + }, + }) + .start(done); }); diff --git a/dev-packages/node-integration-tests/suites/cron/node-schedule/scenario.ts b/dev-packages/node-integration-tests/suites/cron/node-schedule/scenario.ts new file mode 100644 index 000000000000..badcc87fbbce --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/node-schedule/scenario.ts @@ -0,0 +1,29 @@ +import { loggingTransport } from '@sentry-internal/node-integration-tests'; +import * as Sentry from '@sentry/node'; +import * as schedule from 'node-schedule'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + autoSessionTracking: false, + transport: loggingTransport, +}); + +const scheduleWithCheckIn = Sentry.cron.instrumentNodeSchedule(schedule); + +let closeNext = false; + +const job = scheduleWithCheckIn.scheduleJob('my-cron-job', '* * * * * *', () => { + if (closeNext) { + job.cancel(); + throw new Error('Error in cron job'); + } + + // eslint-disable-next-line no-console + console.log('You will see this message every second'); + closeNext = true; +}); + +setTimeout(() => { + process.exit(); +}, 5000); diff --git a/dev-packages/node-integration-tests/suites/cron/node-schedule/test.ts b/dev-packages/node-integration-tests/suites/cron/node-schedule/test.ts new file mode 100644 index 000000000000..84d94c54ad8d --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/node-schedule/test.ts @@ -0,0 +1,75 @@ +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +afterAll(() => { + cleanupChildProcesses(); +}); + +test('node-schedule instrumentation', done => { + createRunner(__dirname, 'scenario.ts') + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'in_progress', + release: '1.0', + monitor_config: { schedule: { type: 'crontab', value: '* * * * * *' } }, + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'ok', + release: '1.0', + duration: expect.any(Number), + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'in_progress', + release: '1.0', + monitor_config: { schedule: { type: 'crontab', value: '* * * * * *' } }, + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + check_in: { + check_in_id: expect.any(String), + monitor_slug: 'my-cron-job', + status: 'error', + release: '1.0', + duration: expect.any(Number), + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + }, + }, + }, + }) + .expect({ + event: { + exception: { values: [{ type: 'Error', value: 'Error in cron job' }] }, + }, + }) + .start(done); +}); diff --git a/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts index bcbbf90417d1..9d0eb8a64b25 100644 --- a/dev-packages/node-integration-tests/utils/runner.ts +++ b/dev-packages/node-integration-tests/utils/runner.ts @@ -7,6 +7,7 @@ import type { EnvelopeItemType, Event, EventEnvelope, + SerializedCheckIn, SerializedSession, SessionAggregates, } from '@sentry/types'; @@ -38,6 +39,13 @@ export function assertSentryTransaction(actual: Event, expected: Partial) }); } +export function assertSentryCheckIn(actual: SerializedCheckIn, expected: Partial): void { + expect(actual).toMatchObject({ + check_in_id: expect.any(String), + ...expected, + }); +} + export function assertEnvelopeHeader(actual: Envelope[0], expected: Partial): void { expect(actual).toEqual({ event_id: expect.any(String), @@ -137,6 +145,9 @@ type Expected = } | { sessions: Partial | ((event: SessionAggregates) => void); + } + | { + check_in: Partial | ((event: SerializedCheckIn) => void); }; type ExpectedEnvelopeHeader = @@ -300,6 +311,17 @@ export function createRunner(...paths: string[]) { expectCallbackCalled(); } + + if ('check_in' in expected) { + const checkIn = item[1] as SerializedCheckIn; + if (typeof expected.check_in === 'function') { + expected.check_in(checkIn); + } else { + assertSentryCheckIn(checkIn, expected.check_in); + } + + expectCallbackCalled(); + } } catch (e) { complete(e as Error); } diff --git a/packages/node/src/cron/cron.ts b/packages/node/src/cron/cron.ts index 8b6fc324a7a6..ce6225ced2fa 100644 --- a/packages/node/src/cron/cron.ts +++ b/packages/node/src/cron/cron.ts @@ -1,4 +1,4 @@ -import { withMonitor } from '@sentry/core'; +import { captureException, withMonitor } from '@sentry/core'; import { replaceCronNames } from './common'; export type CronJobParams = { @@ -92,11 +92,16 @@ export function instrumentCron(lib: T & CronJobConstructor, monitorSlug: stri const cronString = replaceCronNames(cronTime); - function monitoredTick(context: unknown, onComplete?: unknown): void | Promise { + async function monitoredTick(context: unknown, onComplete?: unknown): Promise { return withMonitor( monitorSlug, - () => { - return onTick(context, onComplete); + async () => { + try { + await onTick(context, onComplete); + } catch (e) { + captureException(e); + throw e; + } }, { schedule: { type: 'crontab', value: cronString }, @@ -124,11 +129,16 @@ export function instrumentCron(lib: T & CronJobConstructor, monitorSlug: stri const cronString = replaceCronNames(cronTime); - param.onTick = (context: unknown, onComplete?: unknown) => { + param.onTick = async (context: unknown, onComplete?: unknown) => { return withMonitor( monitorSlug, - () => { - return onTick(context, onComplete); + async () => { + try { + await onTick(context, onComplete); + } catch (e) { + captureException(e); + throw e; + } }, { schedule: { type: 'crontab', value: cronString }, diff --git a/packages/node/src/cron/node-cron.ts b/packages/node/src/cron/node-cron.ts index 4495a0b54909..efa7cde2b698 100644 --- a/packages/node/src/cron/node-cron.ts +++ b/packages/node/src/cron/node-cron.ts @@ -1,4 +1,4 @@ -import { withMonitor } from '@sentry/core'; +import { captureException, withMonitor } from '@sentry/core'; import { replaceCronNames } from './common'; export interface NodeCronOptions { @@ -15,7 +15,7 @@ export interface NodeCron { * * ```ts * import * as Sentry from "@sentry/node"; - * import cron from "node-cron"; + * import * as cron from "node-cron"; * * const cronWithCheckIn = Sentry.cron.instrumentNodeCron(cron); * @@ -35,22 +35,33 @@ export function instrumentNodeCron(lib: Partial & T): T { // When 'get' is called for schedule, return a proxied version of the schedule function return new Proxy(target.schedule, { apply(target, thisArg, argArray: Parameters) { - const [expression, , options] = argArray; + const [expression, callback, options] = argArray; if (!options?.name) { throw new Error('Missing "name" for scheduled job. A name is required for Sentry check-in monitoring.'); } - return withMonitor( - options.name, - () => { - return target.apply(thisArg, argArray); - }, - { - schedule: { type: 'crontab', value: replaceCronNames(expression) }, - timezone: options?.timezone, - }, - ); + async function monitoredCallback(): Promise { + return withMonitor( + options.name, + async () => { + // We have to manually catch here and capture the exception because node-cron swallows errors + // https://github.com/node-cron/node-cron/issues/399 + try { + return await callback(); + } catch (e) { + captureException(e); + throw e; + } + }, + { + schedule: { type: 'crontab', value: replaceCronNames(expression) }, + timezone: options?.timezone, + }, + ); + } + + return target.apply(thisArg, [expression, monitoredCallback, options]); }, }); } else { diff --git a/packages/node/src/cron/node-schedule.ts b/packages/node/src/cron/node-schedule.ts index 79ae44a06e52..35db51618b9a 100644 --- a/packages/node/src/cron/node-schedule.ts +++ b/packages/node/src/cron/node-schedule.ts @@ -30,9 +30,13 @@ export function instrumentNodeSchedule(lib: T & NodeSchedule): T { // eslint-disable-next-line @typescript-eslint/unbound-method return new Proxy(target.scheduleJob, { apply(target, thisArg, argArray: Parameters) { - const [nameOrExpression, expressionOrCallback] = argArray; + const [nameOrExpression, expressionOrCallback, callback] = argArray; - if (typeof nameOrExpression !== 'string' || typeof expressionOrCallback !== 'string') { + if ( + typeof nameOrExpression !== 'string' || + typeof expressionOrCallback !== 'string' || + typeof callback !== 'function' + ) { throw new Error( "Automatic instrumentation of 'node-schedule' requires the first parameter of 'scheduleJob' to be a job name string and the second parameter to be a crontab string", ); @@ -41,15 +45,19 @@ export function instrumentNodeSchedule(lib: T & NodeSchedule): T { const monitorSlug = nameOrExpression; const expression = expressionOrCallback; - return withMonitor( - monitorSlug, - () => { - return target.apply(thisArg, argArray); - }, - { - schedule: { type: 'crontab', value: replaceCronNames(expression) }, - }, - ); + async function monitoredCallback(): Promise { + return withMonitor( + monitorSlug, + async () => { + await callback?.(); + }, + { + schedule: { type: 'crontab', value: replaceCronNames(expression) }, + }, + ); + } + + return target.apply(thisArg, [monitorSlug, expression, monitoredCallback]); }, }); } diff --git a/yarn.lock b/yarn.lock index 17831308adfb..61e4411b1bcd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8539,6 +8539,11 @@ resolved "https://registry.yarnpkg.com/@types/node-abi/-/node-abi-3.0.3.tgz#a8334d75fe45ccd4cdb2a6c1ae82540a7a76828c" integrity sha512-5oos6sivyXcDEuVC5oX3+wLwfgrGZu4NIOn826PGAjPCHsqp2zSPTGU7H1Tv+GZBOiDUY3nBXY1MdaofSEt4fw== +"@types/node-cron@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.11.tgz#70b7131f65038ae63cfe841354c8aba363632344" + integrity sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg== + "@types/node-fetch@^2.6.0": version "2.6.2" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" @@ -8554,6 +8559,13 @@ dependencies: "@types/node" "*" +"@types/node-schedule@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@types/node-schedule/-/node-schedule-2.1.7.tgz#79a1e61adc7bbf8d8eaabcef307e07d76cb40d82" + integrity sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA== + dependencies: + "@types/node" "*" + "@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "17.0.38" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947" @@ -13308,6 +13320,13 @@ critters@0.0.16: postcss "^8.3.7" pretty-bytes "^5.3.0" +cron-parser@^4.2.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5" + integrity sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q== + dependencies: + luxon "^3.2.1" + cron@^3.1.6: version "3.1.6" resolved "https://registry.yarnpkg.com/cron/-/cron-3.1.6.tgz#e7e1798a468e017c8d31459ecd7c2d088f97346c" @@ -21029,6 +21048,11 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== +long-timeout@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" + integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w== + long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -21131,7 +21155,7 @@ lunr@^2.3.8: resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== -luxon@~3.4.0: +luxon@^3.2.1, luxon@~3.4.0: version "3.4.4" resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== @@ -22864,6 +22888,13 @@ node-addon-api@^6.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== +node-cron@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.3.tgz#c4bc7173dd96d96c50bdb51122c64415458caff2" + integrity sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A== + dependencies: + uuid "8.3.2" + node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -22998,6 +23029,15 @@ node-releases@^2.0.6: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== +node-schedule@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-2.1.1.tgz#6958b2c5af8834954f69bb0a7a97c62b97185de3" + integrity sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ== + dependencies: + cron-parser "^4.2.0" + long-timeout "0.1.1" + sorted-array-functions "^1.3.0" + node-source-walk@^4.0.0, node-source-walk@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-4.2.0.tgz#c2efe731ea8ba9c03c562aa0a9d984e54f27bc2c" @@ -27510,6 +27550,11 @@ sort-package-json@^1.57.0: is-plain-obj "2.1.0" sort-object-keys "^1.1.3" +sorted-array-functions@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" + integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== + source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" From 4dcfb2483b01e583e12de970d5dbeddc2159c240 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 8 May 2024 09:41:18 +0200 Subject: [PATCH 10/13] deps(node): Bump `@opentelemetry/core` to `1.24.1` and `@opentelemetry/instrumentation` to `0.51.1` (#11941) --- packages/node/package.json | 4 +- packages/opentelemetry/package.json | 6 +- yarn.lock | 161 ++++++++++++++++++---------- 3 files changed, 109 insertions(+), 62 deletions(-) diff --git a/packages/node/package.json b/packages/node/package.json index 28741fc9db8f..0aa64d22fba7 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -56,8 +56,8 @@ "dependencies": { "@opentelemetry/api": "^1.8.0", "@opentelemetry/context-async-hooks": "^1.23.0", - "@opentelemetry/core": "^1.23.0", - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/core": "^1.24.1", + "@opentelemetry/instrumentation": "^0.51.1", "@opentelemetry/instrumentation-connect": "0.35.0", "@opentelemetry/instrumentation-express": "0.35.0", "@opentelemetry/instrumentation-fastify": "0.35.0", diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index cd767091428e..b0aa64af8af2 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -48,15 +48,15 @@ }, "peerDependencies": { "@opentelemetry/api": "^1.8.0", - "@opentelemetry/core": "^1.23.0", - "@opentelemetry/instrumentation": "^0.51.0", + "@opentelemetry/core": "^1.24.1", + "@opentelemetry/instrumentation": "^0.51.1", "@opentelemetry/sdk-trace-base": "^1.23.0", "@opentelemetry/semantic-conventions": "^1.23.0" }, "devDependencies": { "@opentelemetry/api": "^1.8.0", "@opentelemetry/context-async-hooks": "^1.23.0", - "@opentelemetry/core": "^1.23.0", + "@opentelemetry/core": "^1.24.1", "@opentelemetry/sdk-trace-base": "^1.23.0", "@opentelemetry/semantic-conventions": "^1.23.0" }, diff --git a/yarn.lock b/yarn.lock index 61e4411b1bcd..5ee23d0231e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6126,6 +6126,13 @@ dependencies: "@opentelemetry/api" "^1.0.0" +"@opentelemetry/api-logs@0.51.1": + version "0.51.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.51.1.tgz#ded1874c04516c2b8cb24828eef3d6c3d1f75343" + integrity sha512-E3skn949Pk1z2XtXu/lxf6QAZpawuTM/IUEXcAzpiUkTd73Hmvw26FiN3cJuTmkpM5hZzHwkomVdtrh/n/zzwA== + dependencies: + "@opentelemetry/api" "^1.0.0" + "@opentelemetry/api@1.8.0", "@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.6.0", "@opentelemetry/api@^1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec" @@ -6148,7 +6155,7 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.12.0.tgz#4906ae27359d3311e3dea1b63770a16f60848550" integrity sha512-UXwSsXo3F3yZ1dIBOG9ID8v2r9e+bqLWoizCtTb8rXtwF+N5TM7hzzvQz72o3nBU+zrI/D5e+OqAYK8ZgDd3DA== -"@opentelemetry/core@1.23.0", "@opentelemetry/core@^1.0.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.23.0", "@opentelemetry/core@^1.8.0": +"@opentelemetry/core@1.23.0", "@opentelemetry/core@^1.0.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.8.0": version "1.23.0" resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.23.0.tgz#f2e7ada7f35750f3c1674aef1e52c879005c0731" integrity sha512-hdQ/a9TMzMQF/BO8Cz1juA43/L5YGtCSiKoOHmrTEf7VMDAZgy8ucpWx3eQTnQ3gBloRcWtzvcrMZABC3PTSKQ== @@ -6171,6 +6178,13 @@ "@opentelemetry/context-base" "^0.12.0" semver "^7.1.3" +"@opentelemetry/core@^1.24.1": + version "1.24.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.24.1.tgz#35ab9d2ac9ca938e0ffbdfa40c49c169ac8ba80d" + integrity sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg== + dependencies: + "@opentelemetry/semantic-conventions" "1.24.1" + "@opentelemetry/instrumentation-aws-lambda@0.40.0": version "0.40.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.40.0.tgz#5a54025a1eccb4f2f33115a006a6f8a7c6be4ad8" @@ -6367,6 +6381,18 @@ semver "^7.5.2" shimmer "^1.2.1" +"@opentelemetry/instrumentation@^0.51.1": + version "0.51.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.51.1.tgz#46fb2291150ec6923e50b2f094b9407bc726ca9b" + integrity sha512-JIrvhpgqY6437QIqToyozrUG1h5UhwHkaGK/WAX+fkrpyPtc+RO5FkRtUd9BH0MibabHHvqsnBGKfKVijbmp8w== + dependencies: + "@opentelemetry/api-logs" "0.51.1" + "@types/shimmer" "^1.0.2" + import-in-the-middle "1.7.4" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + "@opentelemetry/propagation-utils@^0.30.8": version "0.30.8" resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.30.8.tgz#5ae1468250e4f225be98b70aed994586248e2de3" @@ -6428,6 +6454,11 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.0.tgz#f074db930a7feb4d64103a9a576c5fbad046fcac" integrity sha512-yL0jI6Ltuz8R+Opj7jClGrul6pOoYrdfVmzQS4SITXRPH7I5IRZbrwe/6/v8v4WYMa6MYZG480S1+uc/IGfqsA== +"@opentelemetry/semantic-conventions@1.24.1": + version "1.24.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz#d4bcebda1cb5146d47a2a53daaa7922f8e084dfb" + integrity sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw== + "@opentelemetry/semantic-conventions@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-0.12.0.tgz#7e392aecdbdbd5d737d3995998b120dc17589ab0" @@ -8319,8 +8350,17 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": - name "@types/history-4" +"@types/history-4@npm:@types/history@4.7.8": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + +"@types/history-5@npm:@types/history@4.7.8": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + +"@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -8539,11 +8579,6 @@ resolved "https://registry.yarnpkg.com/@types/node-abi/-/node-abi-3.0.3.tgz#a8334d75fe45ccd4cdb2a6c1ae82540a7a76828c" integrity sha512-5oos6sivyXcDEuVC5oX3+wLwfgrGZu4NIOn826PGAjPCHsqp2zSPTGU7H1Tv+GZBOiDUY3nBXY1MdaofSEt4fw== -"@types/node-cron@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.11.tgz#70b7131f65038ae63cfe841354c8aba363632344" - integrity sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg== - "@types/node-fetch@^2.6.0": version "2.6.2" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" @@ -8559,13 +8594,6 @@ dependencies: "@types/node" "*" -"@types/node-schedule@^2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@types/node-schedule/-/node-schedule-2.1.7.tgz#79a1e61adc7bbf8d8eaabcef307e07d76cb40d82" - integrity sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA== - dependencies: - "@types/node" "*" - "@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "17.0.38" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947" @@ -8686,7 +8714,15 @@ "@types/history" "^3" "@types/react" "*" -"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14": +"@types/react-router-4@npm:@types/react-router@5.1.14": + version "5.1.14" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" + integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react-router-5@npm:@types/react-router@5.1.14": version "5.1.14" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== @@ -9838,6 +9874,11 @@ acorn-import-assertions@^1.9.0: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" @@ -13320,13 +13361,6 @@ critters@0.0.16: postcss "^8.3.7" pretty-bytes "^5.3.0" -cron-parser@^4.2.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5" - integrity sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q== - dependencies: - luxon "^3.2.1" - cron@^3.1.6: version "3.1.6" resolved "https://registry.yarnpkg.com/cron/-/cron-3.1.6.tgz#e7e1798a468e017c8d31459ecd7c2d088f97346c" @@ -18589,6 +18623,16 @@ import-in-the-middle@1.7.1: cjs-module-lexer "^1.2.2" module-details-from-path "^1.0.3" +import-in-the-middle@1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.7.4.tgz#508da6e91cfa84f210dcdb6c0a91ab0c9e8b3ebc" + integrity sha512-Lk+qzWmiQuRPPulGQeK5qq0v32k2bHnWrRPFgqyvhw7Kkov5L6MOLOIU3pcWeujc9W4q54Cp3Q2WV16eQkc7Bg== + dependencies: + acorn "^8.8.2" + acorn-import-attributes "^1.9.5" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -21048,11 +21092,6 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== -long-timeout@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" - integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w== - long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -21155,7 +21194,7 @@ lunr@^2.3.8: resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== -luxon@^3.2.1, luxon@~3.4.0: +luxon@~3.4.0: version "3.4.4" resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== @@ -22888,13 +22927,6 @@ node-addon-api@^6.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== -node-cron@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.3.tgz#c4bc7173dd96d96c50bdb51122c64415458caff2" - integrity sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A== - dependencies: - uuid "8.3.2" - node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -23029,15 +23061,6 @@ node-releases@^2.0.6: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== -node-schedule@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-2.1.1.tgz#6958b2c5af8834954f69bb0a7a97c62b97185de3" - integrity sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ== - dependencies: - cron-parser "^4.2.0" - long-timeout "0.1.1" - sorted-array-functions "^1.3.0" - node-source-walk@^4.0.0, node-source-walk@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-4.2.0.tgz#c2efe731ea8ba9c03c562aa0a9d984e54f27bc2c" @@ -25731,8 +25754,7 @@ react-is@^18.0.0: dependencies: "@remix-run/router" "1.0.2" -"react-router-6@npm:react-router@6.3.0", react-router@6.3.0: - name react-router-6 +"react-router-6@npm:react-router@6.3.0": version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -25747,6 +25769,13 @@ react-router-dom@^6.2.2: history "^5.2.0" react-router "6.3.0" +react-router@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" + integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== + dependencies: + history "^5.2.0" + react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -27550,11 +27579,6 @@ sort-package-json@^1.57.0: is-plain-obj "2.1.0" sort-object-keys "^1.1.3" -sorted-array-functions@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" - integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== - source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -27991,8 +28015,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: - name string-width-cjs +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -28018,6 +28041,15 @@ string-width@^2.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -28113,7 +28145,14 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -30699,8 +30738,7 @@ workerpool@^6.4.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -30718,6 +30756,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 4cc5301990f67da4dc49f87417aa946382e0bb04 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 8 May 2024 08:06:27 -0230 Subject: [PATCH 11/13] feat(replay): Use `vitest` instead of jest (#11899) Use vitest instead of jest. Some notes: * Use vi/jest `*Async` fake timer functions instead of `process.nextTick` * Our `useFakeTimers` module was setting an interval which was causing all sorts of flakes in tests with the updated fake timers library * removed the interval in `use-fake-timers` module which seemed to be unnecessary and was causing lots of flakes in our tests (also was able to remove all of the random tick values in timestamps due to this) --- packages/replay-internal/.eslintrc.js | 2 +- packages/replay-internal/jest.config.ts | 17 -- packages/replay-internal/package.json | 9 +- .../{jest.setup.ts => test.setup.ts} | 68 +++---- .../test/integration/autoSaveSession.test.ts | 27 +-- .../beforeAddRecordingEvent.test.ts | 34 ++-- .../coreHandlers/handleAfterSendEvent.test.ts | 60 +++---- .../handleBeforeSendEvent.test.ts | 6 +- .../coreHandlers/handleGlobalEvent.test.ts | 2 +- .../test/integration/errorSampleRate.test.ts | 166 ++++++++---------- .../test/integration/eventProcessors.test.ts | 18 +- .../test/integration/events.test.ts | 20 +-- .../test/integration/flush.test.ts | 117 ++++++------ .../test/integration/getReplayId.test.ts | 2 +- .../integration/integrationSettings.test.ts | 17 +- .../test/integration/rateLimiting.test.ts | 12 +- .../test/integration/rrweb.test.ts | 10 +- .../test/integration/sampling.test.ts | 14 +- .../test/integration/sendReplayEvent.test.ts | 78 ++++---- .../test/integration/session.test.ts | 64 +++---- .../integration/shouldFilterRequest.test.ts | 2 +- .../test/integration/stop.test.ts | 37 ++-- .../replay-internal/test/mocks/mockRrweb.ts | 65 +++---- .../replay-internal/test/mocks/mockSdk.ts | 3 +- .../test/mocks/resetSdkMock.ts | 14 +- .../unit/coreHandlers/handleClick.test.ts | 86 ++++----- .../handleNetworkBreadcrumbs.test.ts | 6 +- .../util/addBreadcrumbEvent.test.ts | 4 +- .../coreHandlers/util/networkUtils.test.ts | 4 +- .../unit/coreHandlers/util/xhrUtils.test.ts | 6 +- .../EventBufferCompressionWorker.test.ts | 4 +- .../unit/eventBuffer/EventBufferProxy.test.ts | 4 +- .../test/unit/session/createSession.test.ts | 16 +- .../unit/session/loadOrCreateSession.test.ts | 16 +- .../test/unit/util/addEvent.test.ts | 13 +- .../unit/util/createPerformanceEntry.test.ts | 17 +- .../test/unit/util/debounce.test.ts | 96 +++++----- .../test/unit/util/getPrivacyOptions.test.ts | 12 +- .../unit/util/handleRecordingEmit.test.ts | 15 +- .../test/unit/util/isSampled.test.ts | 2 +- .../test/unit/util/prepareReplayEvent.test.ts | 6 +- .../test/unit/util/throttle.test.ts | 32 ++-- .../replay-internal/test/utils/TestClient.ts | 2 - .../test/utils/use-fake-timers.ts | 15 +- packages/replay-internal/tsconfig.test.json | 2 +- packages/replay-internal/vitest.config.ts | 16 ++ yarn.lock | 25 +++ 47 files changed, 628 insertions(+), 635 deletions(-) delete mode 100644 packages/replay-internal/jest.config.ts rename packages/replay-internal/{jest.setup.ts => test.setup.ts} (80%) create mode 100644 packages/replay-internal/vitest.config.ts diff --git a/packages/replay-internal/.eslintrc.js b/packages/replay-internal/.eslintrc.js index 6af926975df0..dc39a10b354b 100644 --- a/packages/replay-internal/.eslintrc.js +++ b/packages/replay-internal/.eslintrc.js @@ -10,7 +10,7 @@ module.exports = { files: ['src/**/*.ts'], }, { - files: ['jest.setup.ts', 'jest.config.ts'], + files: ['test.setup.ts', 'vitest.config.ts'], parserOptions: { project: ['tsconfig.test.json'], }, diff --git a/packages/replay-internal/jest.config.ts b/packages/replay-internal/jest.config.ts deleted file mode 100644 index 90a3cf471f8d..000000000000 --- a/packages/replay-internal/jest.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Config } from '@jest/types'; -import { jsWithTs as jsWithTsPreset } from 'ts-jest/presets'; - -export default async (): Promise => { - return { - ...jsWithTsPreset, - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.test.json', - }, - __DEBUG_BUILD__: true, - }, - setupFilesAfterEnv: ['./jest.setup.ts'], - testEnvironment: 'jsdom', - testMatch: ['/test/**/*(*.)@(spec|test).ts'], - }; -}; diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index 778f2137b882..e0b1f807b9fe 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -51,10 +51,12 @@ "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build sentry-replay-*.tgz", - "fix": "eslint . --format stylish --fix", + "fix": "run-s fix:biome fix:eslint", + "fix:eslint": "eslint . --format stylish --fix", + "fix:biome": "biome check --apply .", "lint": "eslint . --format stylish", - "test": "jest", - "test:watch": "jest --watch", + "test": "vitest", + "test:watch": "vitest --watch", "yalc:publish": "ts-node ../../scripts/prepack.ts --bundles && yalc publish ./build/npm --push --sig" }, "repository": { @@ -73,6 +75,7 @@ "@sentry-internal/rrweb": "2.15.0", "@sentry-internal/rrweb-snapshot": "2.15.0", "fflate": "^0.8.1", + "jest-matcher-utils": "^29.0.0", "jsdom-worker": "^0.2.1" }, "dependencies": { diff --git a/packages/replay-internal/jest.setup.ts b/packages/replay-internal/test.setup.ts similarity index 80% rename from packages/replay-internal/jest.setup.ts rename to packages/replay-internal/test.setup.ts index eaba8ee05179..05a762e60d50 100644 --- a/packages/replay-internal/jest.setup.ts +++ b/packages/replay-internal/test.setup.ts @@ -1,4 +1,7 @@ -import { TextEncoder } from 'util'; +import { printDiffOrStringify } from 'jest-matcher-utils'; +import { vi } from 'vitest'; +import type { Mocked, MockedFunction } from 'vitest'; + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { getClient } from '@sentry/core'; import type { ReplayRecordingData, Transport } from '@sentry/types'; @@ -6,12 +9,9 @@ import * as SentryUtils from '@sentry/utils'; import type { ReplayContainer, Session } from './src/types'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -(global as any).TextEncoder = TextEncoder; - -type MockTransport = jest.MockedFunction; +type MockTransport = MockedFunction; -jest.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); +vi.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); type EnvelopeHeader = { event_id: string; @@ -36,7 +36,7 @@ type SentReplayExpected = { }; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const toHaveSameSession = function (received: jest.Mocked, expected: undefined | Session) { +const toHaveSameSession = function (received: Mocked, expected: undefined | Session) { const pass = this.equals(received.session?.id, expected?.id) as boolean; const options = { @@ -47,12 +47,12 @@ const toHaveSameSession = function (received: jest.Mocked, expe return { pass, message: () => - `${this.utils.matcherHint( - 'toHaveSameSession', - undefined, - undefined, - options, - )}\n\n${this.utils.printDiffOrStringify(expected, received.session, 'Expected', 'Received')}`, + `${this.utils.matcherHint('toHaveSameSession', undefined, undefined, options)}\n\n${printDiffOrStringify( + expected, + received.session, + 'Expected', + 'Received', + )}`, }; }; @@ -101,6 +101,7 @@ function checkCallForSentReplay( : (expected as SentReplayExpected); if (isObjectContaining) { + // eslint-disable-next-line no-console console.warn('`expect.objectContaining` is unnecessary when using the `toHaveSentReplay` matcher'); } @@ -152,7 +153,7 @@ function getReplayCalls(calls: any[][][]): any[][][] { */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const toHaveSentReplay = function ( - _received: jest.Mocked, + _received: Mocked, expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, ) { const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock; @@ -194,12 +195,7 @@ const toHaveSentReplay = function ( : 'Expected Replay to have been sent, but a request was not attempted' : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results .map(({ key, expectedVal, actualVal }: Result) => - this.utils.printDiffOrStringify( - expectedVal, - actualVal, - `Expected (key: ${key})`, - `Received (key: ${key})`, - ), + printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`), ) .join('\n')}`, }; @@ -211,7 +207,7 @@ const toHaveSentReplay = function ( */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const toHaveLastSentReplay = function ( - _received: jest.Mocked, + _received: Mocked, expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, ) { const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock; @@ -235,12 +231,7 @@ const toHaveLastSentReplay = function ( : 'Expected Replay to have last been sent, but a request was not attempted' : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results .map(({ key, expectedVal, actualVal }: Result) => - this.utils.printDiffOrStringify( - expectedVal, - actualVal, - `Expected (key: ${key})`, - `Received (key: ${key})`, - ), + printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`), ) .join('\n')}`, }; @@ -252,18 +243,13 @@ expect.extend({ toHaveLastSentReplay, }); -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace jest { - interface AsymmetricMatchers { - toHaveSentReplay(expected?: SentReplayExpected): void; - toHaveLastSentReplay(expected?: SentReplayExpected): void; - toHaveSameSession(expected: undefined | Session): void; - } - interface Matchers { - toHaveSentReplay(expected?: SentReplayExpected): R; - toHaveLastSentReplay(expected?: SentReplayExpected): R; - toHaveSameSession(expected: undefined | Session): R; - } - } +interface CustomMatchers { + toHaveSentReplay(expected?: SentReplayExpected): R; + toHaveLastSentReplay(expected?: SentReplayExpected): R; + toHaveSameSession(expected: undefined | Session): R; +} + +declare module 'vitest' { + type Assertion = CustomMatchers; + type AsymmetricMatchersContaining = CustomMatchers; } diff --git a/packages/replay-internal/test/integration/autoSaveSession.test.ts b/packages/replay-internal/test/integration/autoSaveSession.test.ts index 6fc0539c771d..7fb3951756ae 100644 --- a/packages/replay-internal/test/integration/autoSaveSession.test.ts +++ b/packages/replay-internal/test/integration/autoSaveSession.test.ts @@ -1,5 +1,8 @@ +import { vi } from 'vitest'; + import { EventType } from '@sentry-internal/rrweb'; +import { saveSession } from '../../src/session/saveSession'; import type { RecordingEvent } from '../../src/types'; import { addEvent } from '../../src/util/addEvent'; import { resetSdkMock } from '../mocks/resetSdkMock'; @@ -7,23 +10,21 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); +vi.mock('../../src/session/saveSession', () => { + return { + saveSession: vi.fn(), + }; +}); + describe('Integration | autoSaveSession', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); test.each([ ['with stickySession=true', true, 1], ['with stickySession=false', false, 0], ])('%s', async (_: string, stickySession: boolean, addSummand: number) => { - const saveSessionSpy = jest.fn(); - - jest.mock('../../src/session/saveSession', () => { - return { - saveSession: saveSessionSpy, - }; - }); - const { replay } = await resetSdkMock({ replayOptions: { stickySession, @@ -31,11 +32,11 @@ describe('Integration | autoSaveSession', () => { }); // Initially called up to three times: once for start, then once for replay.updateSessionActivity & once for segmentId increase - expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 3); + expect(saveSession).toHaveBeenCalledTimes(addSummand * 3); replay['_updateSessionActivity'](); - expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 4); + expect(saveSession).toHaveBeenCalledTimes(addSummand * 4); // In order for runFlush to actually do something, we need to add an event const event = { @@ -48,8 +49,8 @@ describe('Integration | autoSaveSession', () => { addEvent(replay, event); - await replay['_runFlush'](); + await Promise.all([replay['_runFlush'](), vi.runAllTimersAsync()]); - expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 5); + expect(saveSession).toHaveBeenCalledTimes(addSummand * 5); }); }); diff --git a/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts b/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts index a8331149838b..7f8495375864 100644 --- a/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts +++ b/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance, MockedFunction } from 'vitest'; + import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; import * as SentryCore from '@sentry/core'; import type { Transport } from '@sentry/types'; @@ -14,24 +17,19 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -type MockTransportSend = jest.MockedFunction; +type MockTransportSend = MockedFunction; describe('Integration | beforeAddRecordingEvent', () => { let replay: ReplayContainer; let integration: Replay; let mockTransportSend: MockTransportSend; - let mockSendReplayRequest: jest.SpyInstance; + let mockSendReplayRequest: MockInstance; let domHandler: DomHandler; const { record: mockRecord } = mockRrweb(); beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { domHandler = handler; }); @@ -69,14 +67,14 @@ describe('Integration | beforeAddRecordingEvent', () => { }, })); - mockSendReplayRequest = jest.spyOn(SendReplayRequest, 'sendReplayRequest'); + mockSendReplayRequest = vi.spyOn(SendReplayRequest, 'sendReplayRequest'); - jest.runAllTimers(); + vi.runAllTimers(); mockTransportSend = SentryCore.getClient()?.getTransport()?.send as MockTransportSend; }); beforeEach(() => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); mockRecord.takeFullSnapshot.mockClear(); mockTransportSend.mockClear(); @@ -90,9 +88,9 @@ describe('Integration | beforeAddRecordingEvent', () => { }); afterEach(async () => { - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); clearSession(replay); }); @@ -106,7 +104,7 @@ describe('Integration | beforeAddRecordingEvent', () => { event: new Event('click'), }); - await advanceTimers(5000); + await vi.runAllTimersAsync(); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -135,8 +133,7 @@ describe('Integration | beforeAddRecordingEvent', () => { integration.start(); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }]), @@ -174,8 +171,7 @@ describe('Integration | beforeAddRecordingEvent', () => { ]), ); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); expect(replay).not.toHaveLastSentReplay(); expect(replay.isEnabled()).toBe(true); diff --git a/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts index 03f4c236b81d..afab6ac6030a 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance } from 'vitest'; + import { getClient } from '@sentry/core'; import type { ErrorEvent, Event } from '@sentry/types'; @@ -79,8 +82,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().traceIds)).toEqual(['tr2']); // Does not affect error session - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(Array.from(replay.getContext().errorIds)).toEqual([]); expect(Array.from(replay.getContext().traceIds)).toEqual(['tr2']); @@ -152,9 +154,11 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; + expect(mockSend).toHaveBeenCalledTimes(0); const error1 = Error({ event_id: 'err1', tags: { replayId: 'replayid1' } }); + await vi.runOnlyPendingTimersAsync(); const handler = handleAfterSendEvent(replay); @@ -164,13 +168,13 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual(['err1']); - jest.runAllTimers(); - await new Promise(process.nextTick); - // Send twice, one for the error & one right after for the session conversion + // handleAfterSendEvent calls `sendBufferedReplayOrFlush`, which + // flushes immediately but also calls `startRecording` which eventually + // triggers another flush after flush delay. + await vi.runOnlyPendingTimersAsync(); expect(mockSend).toHaveBeenCalledTimes(1); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runOnlyPendingTimersAsync(); expect(mockSend).toHaveBeenCalledTimes(2); // This is removed now, because it has been converted to a "session" session @@ -191,7 +195,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1 = Error({ event_id: 'err1' }); @@ -203,8 +207,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual(['err1']); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); // Send once for the regular session sending expect(mockSend).toHaveBeenCalledTimes(1); @@ -225,7 +228,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const profileEvent: Event = { type: 'profile' }; const replayEvent: Event = { type: 'replay_event' }; @@ -239,8 +242,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual([]); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(mockSend).toHaveBeenCalledTimes(0); expect(Array.from(replay.getContext().errorIds)).toEqual([]); @@ -260,7 +262,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1 = Error({ event_id: 'err1' }); @@ -272,8 +274,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual([]); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); // Remains in buffer mode & without flushing expect(mockSend).toHaveBeenCalledTimes(0); @@ -294,7 +295,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1: ErrorEvent = { event_id: 'err1', type: undefined }; @@ -306,8 +307,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual(['err1']); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); // Remains in buffer mode & without flushing expect(mockSend).toHaveBeenCalledTimes(0); @@ -328,7 +328,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1 = Error({ event_id: 'err1', message: UNABLE_TO_SEND_REPLAY }); @@ -340,8 +340,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual(['err1']); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); // Remains in buffer mode & without flushing expect(mockSend).toHaveBeenCalledTimes(0); @@ -362,7 +361,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1 = Error({ event_id: 'err1', tags: { replayId: 'replayid1' } }); @@ -372,8 +371,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { replay['_isEnabled'] = false; - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(mockSend).toHaveBeenCalledTimes(0); }); @@ -382,7 +380,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { const error1 = Error({ event_id: 'err1', tags: { replayId: 'replayid1' } }); const error2 = Error({ event_id: 'err2', tags: { replayId: 'replayid1' } }); - const beforeErrorSampling = jest.fn(event => event === error2); + const beforeErrorSampling = vi.fn(event => event === error2); ({ replay } = await resetSdkMock({ replayOptions: { @@ -395,7 +393,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const handler = handleAfterSendEvent(replay); @@ -403,8 +401,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { handler(error1, { statusCode: 200 }); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(beforeErrorSampling).toHaveBeenCalledTimes(1); @@ -415,8 +412,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { handler(error2, { statusCode: 200 }); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(beforeErrorSampling).toHaveBeenCalledTimes(2); diff --git a/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts index a1462b974711..8cd9fda46247 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { handleBeforeSendEvent } from '../../../src/coreHandlers/handleBeforeSendEvent'; import type { ReplayContainer } from '../../../src/replay'; import { Error } from '../../fixtures/error'; @@ -24,7 +26,7 @@ describe('Integration | coreHandlers | handleBeforeSendEvent', () => { })); const handler = handleBeforeSendEvent(replay); - const addBreadcrumbSpy = jest.spyOn(replay, 'throttledAddEvent'); + const addBreadcrumbSpy = vi.spyOn(replay, 'throttledAddEvent'); const error = Error(); error.exception.values[0].value = @@ -58,7 +60,7 @@ describe('Integration | coreHandlers | handleBeforeSendEvent', () => { })); const handler = handleBeforeSendEvent(replay); - const addBreadcrumbSpy = jest.spyOn(replay, 'throttledAddEvent'); + const addBreadcrumbSpy = vi.spyOn(replay, 'throttledAddEvent'); const error = Error(); error.exception.values[0].value = 'https://reactjs.org/docs/error-decoder.html?invariant=423'; diff --git a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts index 5200294808db..ebe43c25eb98 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts @@ -191,7 +191,7 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(Array.from(replay.getContext().traceIds)).toEqual([]); expect(Array.from(replay.getContext().errorIds)).toEqual([]); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(Array.from(replay.getContext().errorIds)).toEqual([]); diff --git a/packages/replay-internal/test/integration/errorSampleRate.test.ts b/packages/replay-internal/test/integration/errorSampleRate.test.ts index 4f83f150548f..b3b605f28c12 100644 --- a/packages/replay-internal/test/integration/errorSampleRate.test.ts +++ b/packages/replay-internal/test/integration/errorSampleRate.test.ts @@ -1,4 +1,5 @@ import { captureException, getClient } from '@sentry/core'; +import { vi } from 'vitest'; import { BUFFER_CHECKOUT_TIME, @@ -23,12 +24,7 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -async function waitForBufferFlush() { - await new Promise(process.nextTick); + vi.advanceTimersByTime(time); await new Promise(process.nextTick); } @@ -72,13 +68,13 @@ describe('Integration | errorSampleRate', () => { name: 'click', event: new Event('click'), }); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -114,10 +110,10 @@ describe('Integration | errorSampleRate', () => { replayEventPayload: expect.objectContaining({ replay_type: 'buffer', }), - recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + 40, type: 2 }]), + recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }]), }); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); // Check that click will get captured domHandler({ @@ -131,11 +127,11 @@ describe('Integration | errorSampleRate', () => { recordingData: JSON.stringify([ { type: 5, - timestamp: BASE_TIMESTAMP + 10000 + 80, + timestamp: BASE_TIMESTAMP + 10000, data: { tag: 'breadcrumb', payload: { - timestamp: (BASE_TIMESTAMP + 10000 + 80) / 1000, + timestamp: (BASE_TIMESTAMP + 10000) / 1000, type: 'default', category: 'ui.click', message: '', @@ -160,13 +156,13 @@ describe('Integration | errorSampleRate', () => { name: 'click', event: new Event('click'), }); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); replay.sendBufferedReplayOrFlush({ continueRecording: false }); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -194,7 +190,7 @@ describe('Integration | errorSampleRate', () => { ]), }); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); // Check that click will not get captured domHandler({ name: 'click', @@ -245,14 +241,14 @@ describe('Integration | errorSampleRate', () => { name: 'click', event: new Event('click'), }); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); replay.sendBufferedReplayOrFlush({ continueRecording: true }); replay.sendBufferedReplayOrFlush({ continueRecording: true }); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -280,7 +276,7 @@ describe('Integration | errorSampleRate', () => { ]), }); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); // Check that click will not get captured domHandler({ name: 'click', @@ -309,7 +305,7 @@ describe('Integration | errorSampleRate', () => { replay['_initializeSessionForSampling'](); replay.setInitialState(); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); @@ -323,11 +319,11 @@ describe('Integration | errorSampleRate', () => { }, }); - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); @@ -342,13 +338,13 @@ describe('Integration | errorSampleRate', () => { }); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); // User comes back before `SESSION_IDLE_EXPIRE_DURATION` elapses - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 100); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 100); Object.defineProperty(document, 'visibilityState', { configurable: true, get: function () { @@ -357,7 +353,7 @@ describe('Integration | errorSampleRate', () => { }); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); @@ -374,14 +370,14 @@ describe('Integration | errorSampleRate', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); addEvent(replay, TEST_EVENT); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); @@ -397,7 +393,7 @@ describe('Integration | errorSampleRate', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); @@ -408,7 +404,7 @@ describe('Integration | errorSampleRate', () => { // Fire a new event every 4 seconds, 4 times [...Array(4)].forEach(() => { mockRecord._emitter(TEST_EVENT); - jest.advanceTimersByTime(4000); + vi.advanceTimersByTime(4000); }); // We are at time = +16seconds now (relative to BASE_TIMESTAMP) @@ -425,7 +421,7 @@ describe('Integration | errorSampleRate', () => { // Let's make sure it continues to work mockRecord._emitter(TEST_EVENT); await waitForFlush(); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); }); @@ -440,7 +436,7 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -463,7 +459,7 @@ describe('Integration | errorSampleRate', () => { const sessionId = replay.getSessionId(); // Idle for given time - jest.advanceTimersByTime(waitTime + 1); + vi.advanceTimersByTime(waitTime + 1); await new Promise(process.nextTick); const TEST_EVENT = getTestEventIncremental({ @@ -472,7 +468,7 @@ describe('Integration | errorSampleRate', () => { }); mockRecord._emitter(TEST_EVENT); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); // We stop recording after 15 minutes of inactivity in error mode @@ -499,7 +495,7 @@ describe('Integration | errorSampleRate', () => { expect(replay).not.toHaveLastSentReplay(); // Idle for given time - jest.advanceTimersByTime(waitTime + 1); + vi.advanceTimersByTime(waitTime + 1); await new Promise(process.nextTick); const TEST_EVENT = getTestEventIncremental({ @@ -508,7 +504,7 @@ describe('Integration | errorSampleRate', () => { }); mockRecord._emitter(TEST_EVENT); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); // in production, this happens at a time interval, here we mock this @@ -536,7 +532,7 @@ describe('Integration | errorSampleRate', () => { // should still react to errors later on captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay.session?.id).toBe(oldSessionId); @@ -560,7 +556,7 @@ describe('Integration | errorSampleRate', () => { expect(oldSessionId).toBeDefined(); // Idle for 15 minutes - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); const TEST_EVENT = getTestEventIncremental({ data: { name: 'lost event' }, @@ -569,7 +565,7 @@ describe('Integration | errorSampleRate', () => { mockRecord._emitter(TEST_EVENT); expect(replay).not.toHaveLastSentReplay(); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); // We stop recording after SESSION_IDLE_EXPIRE_DURATION of inactivity in error mode @@ -582,7 +578,7 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); await new Promise(process.nextTick); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); await new Promise(process.nextTick); expect(replay.session?.id).toBe(oldSessionId); @@ -617,16 +613,12 @@ describe('Integration | errorSampleRate', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); - jest.runAllTimers(); - await new Promise(process.nextTick); - - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); captureException(new Error('testing')); - await new Promise(process.nextTick); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); - await new Promise(process.nextTick); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveSentReplay({ recordingData: JSON.stringify([ @@ -636,14 +628,10 @@ describe('Integration | errorSampleRate', () => { ]), replayEventPayload: expect.objectContaining({ replay_start_timestamp: BASE_TIMESTAMP / 1000, - // the exception happens roughly 10 seconds after BASE_TIMESTAMP - // (advance timers + waiting for flush after the checkout) and - // extra time is likely due to async of `addMemoryEntry()` - - timestamp: (BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY + DEFAULT_FLUSH_MIN_DELAY + 40) / 1000, + timestamp: (BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY + DEFAULT_FLUSH_MIN_DELAY) / 1000, error_ids: [expect.any(String)], trace_ids: [], - urls: ['http://localhost/'], + urls: ['http://localhost:3000/'], replay_id: expect.any(String), }), recordingPayloadHeader: { segment_id: 0 }, @@ -652,49 +640,49 @@ describe('Integration | errorSampleRate', () => { it('has correct timestamps when error occurs much later than initial pageload/checkout', async () => { const ELAPSED = BUFFER_CHECKOUT_TIME; - const TICK = 20; const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); // add a mock performance event replay.performanceEntries.push(PerformanceEntryResource()); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); // in production, this happens at a time interval // session started time should be updated to this current timestamp mockRecord.takeFullSnapshot(true); const optionsEvent = createOptionsEvent(replay); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); + // await vi.advanceTimersToNextTimerAsync(); // This is still the timestamp from the full snapshot we took earlier - expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + TICK); + expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED); // Does not capture mouse click expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, replayEventPayload: expect.objectContaining({ // Make sure the old performance event is thrown out - replay_start_timestamp: (BASE_TIMESTAMP + ELAPSED + TICK) / 1000, + replay_start_timestamp: (BASE_TIMESTAMP + ELAPSED) / 1000, }), recordingData: JSON.stringify([ { data: { isCheckout: true }, - timestamp: BASE_TIMESTAMP + ELAPSED + TICK, + timestamp: BASE_TIMESTAMP + ELAPSED, type: 2, }, optionsEvent, @@ -703,7 +691,7 @@ describe('Integration | errorSampleRate', () => { }); it('refreshes replay when user goes idle', async () => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); @@ -711,12 +699,10 @@ describe('Integration | errorSampleRate', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); - jest.runAllTimers(); - await new Promise(process.nextTick); - captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveLastSentReplay(); @@ -725,14 +711,14 @@ describe('Integration | errorSampleRate', () => { // Now wait after session expires - should stop recording mockRecord.takeFullSnapshot.mockClear(); - (getClient()!.getTransport()!.send as unknown as jest.SpyInstance).mockClear(); + (getClient()!.getTransport()!.send as unknown as MockInstance).mockClear(); expect(replay).not.toHaveLastSentReplay(); const sessionId = replay.getSessionId(); // Go idle - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); await new Promise(process.nextTick); mockRecord._emitter(TEST_EVENT); @@ -749,7 +735,7 @@ describe('Integration | errorSampleRate', () => { it('refreshes replay when session exceeds max length after latest captured error', async () => { const sessionId = replay.session?.id; - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); @@ -757,10 +743,9 @@ describe('Integration | errorSampleRate', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); - jest.advanceTimersByTime(2 * MAX_REPLAY_DURATION); + await vi.advanceTimersByTimeAsync(2 * MAX_REPLAY_DURATION); // in production, this happens at a time interval, here we mock this mockRecord.takeFullSnapshot(true); @@ -768,11 +753,11 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); // Flush due to exception - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); await waitForFlush(); expect(replay.session?.id).toBe(sessionId); - expect(replay).toHaveLastSentReplay({ + expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, }); @@ -785,7 +770,7 @@ describe('Integration | errorSampleRate', () => { data: { isCheckout: true, }, - timestamp: BASE_TIMESTAMP + 2 * MAX_REPLAY_DURATION + DEFAULT_FLUSH_MIN_DELAY + 40, + timestamp: BASE_TIMESTAMP + 2 * MAX_REPLAY_DURATION + DEFAULT_FLUSH_MIN_DELAY, type: 2, }, ]), @@ -793,14 +778,12 @@ describe('Integration | errorSampleRate', () => { // Now wait after session expires - should stop recording mockRecord.takeFullSnapshot.mockClear(); - (getClient()!.getTransport()!.send as unknown as jest.SpyInstance).mockClear(); + (getClient()!.getTransport()!.send as unknown as MockInstance).mockClear(); - jest.advanceTimersByTime(MAX_REPLAY_DURATION); - await new Promise(process.nextTick); + await advanceTimers(MAX_REPLAY_DURATION); mockRecord._emitter(TEST_EVENT); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); expect(replay).not.toHaveLastSentReplay(); expect(mockRecord.takeFullSnapshot).toHaveBeenCalledTimes(0); @@ -812,7 +795,7 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); await new Promise(process.nextTick); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); await new Promise(process.nextTick); expect(replay).toHaveLastSentReplay(); }); @@ -821,14 +804,14 @@ describe('Integration | errorSampleRate', () => { const stepDuration = 10_000; const steps = 5_000; - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); expect(replay).not.toHaveLastSentReplay(); let optionsEvent = createOptionsEvent(replay); for (let i = 1; i <= steps; i++) { - jest.advanceTimersByTime(stepDuration); + vi.advanceTimersByTime(stepDuration); optionsEvent = createOptionsEvent(replay); mockRecord._emitter({ data: { step: i }, timestamp: BASE_TIMESTAMP + stepDuration * i, type: 2 }, true); mockRecord._emitter({ data: { step: i }, timestamp: BASE_TIMESTAMP + stepDuration * i + 5, type: 3 }); @@ -842,7 +825,8 @@ describe('Integration | errorSampleRate', () => { // Now capture an error captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([ @@ -854,7 +838,7 @@ describe('Integration | errorSampleRate', () => { replay_start_timestamp: (BASE_TIMESTAMP + stepDuration * steps) / 1000, error_ids: [expect.any(String)], trace_ids: [], - urls: ['http://localhost/'], + urls: ['http://localhost:3000/'], replay_id: expect.any(String), }), recordingPayloadHeader: { segment_id: 0 }, @@ -890,7 +874,7 @@ describe('Integration | errorSampleRate', () => { // Waiting for max life should eventually refresh the session // We simulate a full checkout which would otherwise be done automatically for (let i = 0; i < MAX_REPLAY_DURATION / 60_000; i++) { - jest.advanceTimersByTime(60_000); + vi.advanceTimersByTime(60_000); await new Promise(process.nextTick); mockRecord.takeFullSnapshot(true); } @@ -917,7 +901,7 @@ describe('Integration | errorSampleRate', () => { }); integration['_initialize'](); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); @@ -928,7 +912,7 @@ describe('Integration | errorSampleRate', () => { // Waiting for max life should eventually stop recording // We simulate a full checkout which would otherwise be done automatically for (let i = 0; i < MAX_REPLAY_DURATION / 60_000; i++) { - jest.advanceTimersByTime(60_000); + vi.advanceTimersByTime(60_000); await new Promise(process.nextTick); mockRecord.takeFullSnapshot(true); } @@ -962,9 +946,6 @@ describe('Integration | errorSampleRate', () => { integration['_initialize'](); const optionsEvent = createOptionsEvent(replay); - jest.runAllTimers(); - - await new Promise(process.nextTick); const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); @@ -973,7 +954,8 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); // 2 ticks to send replay from an error - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); + await vi.advanceTimersToNextTimerAsync(); // Buffered events before error expect(replay).toHaveSentReplay({ @@ -986,13 +968,13 @@ describe('Integration | errorSampleRate', () => { }); // `startRecording()` after switching to session mode to continue recording - await waitForFlush(); + await vi.advanceTimersToNextTimerAsync(); // Latest checkout when we call `startRecording` again after uploading segment // after an error occurs (e.g. when we switch to session replay recording) expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 1 }, - recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + 40, type: 2 }]), + recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }]), }); }); }); diff --git a/packages/replay-internal/test/integration/eventProcessors.test.ts b/packages/replay-internal/test/integration/eventProcessors.test.ts index b683e1ac4279..58a0f488eb91 100644 --- a/packages/replay-internal/test/integration/eventProcessors.test.ts +++ b/packages/replay-internal/test/integration/eventProcessors.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { getClient, getCurrentScope } from '@sentry/core'; import type { Event } from '@sentry/types'; @@ -15,7 +17,7 @@ describe('Integration | eventProcessors', () => { }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('handles event processors properly', async () => { @@ -29,17 +31,17 @@ describe('Integration | eventProcessors', () => { const client = getClient()!; - jest.runAllTimers(); - const mockTransportSend = jest.spyOn(client.getTransport()!, 'send'); + await vi.runAllTimersAsync(); + const mockTransportSend = vi.spyOn(client.getTransport()!, 'send'); mockTransportSend.mockReset(); - const handler1 = jest.fn((event: Event): Event | null => { + const handler1 = vi.fn((event: Event): Event | null => { event.timestamp = MUTATED_TIMESTAMP; return event; }); - const handler2 = jest.fn((): Event | null => { + const handler2 = vi.fn((): Event | null => { return null; }); @@ -48,8 +50,7 @@ describe('Integration | eventProcessors', () => { const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); - jest.runAllTimers(); - jest.advanceTimersByTime(1); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockTransportSend).toHaveBeenCalledTimes(1); @@ -59,8 +60,7 @@ describe('Integration | eventProcessors', () => { const TEST_EVENT2 = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT2); - jest.runAllTimers(); - jest.advanceTimersByTime(1); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockTransportSend).toHaveBeenCalledTimes(1); diff --git a/packages/replay-internal/test/integration/events.test.ts b/packages/replay-internal/test/integration/events.test.ts index 7a3d6e920a9e..c7670b70a0b6 100644 --- a/packages/replay-internal/test/integration/events.test.ts +++ b/packages/replay-internal/test/integration/events.test.ts @@ -14,19 +14,19 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); + vi.advanceTimersByTime(time); await new Promise(process.nextTick); } describe('Integration | events', () => { let replay: ReplayContainer; let mockRecord: RecordMock; - let mockTransportSend: jest.SpyInstance; + let mockTransportSend: MockInstance; const prevLocation = WINDOW.location; beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.runAllTimers(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.runAllTimers(); }); beforeEach(async () => { @@ -36,7 +36,7 @@ describe('Integration | events', () => { }, })); - mockTransportSend = jest.spyOn(getClient()!.getTransport()!, 'send'); + mockTransportSend = vi.spyOn(getClient()!.getTransport()!, 'send'); // Create a new session and clear mocks because a segment (from initial // checkout) will have already been uploaded by the time the tests run @@ -47,14 +47,14 @@ describe('Integration | events', () => { }); afterEach(async () => { - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); Object.defineProperty(WINDOW, 'location', { value: prevLocation, writable: true, }); clearSession(replay); - jest.clearAllMocks(); + vi.clearAllMocks(); mockRecord.takeFullSnapshot.mockClear(); replay.stop(); }); @@ -86,7 +86,7 @@ describe('Integration | events', () => { expect(replay).toHaveLastSentReplay({ replayEventPayload: expect.objectContaining({ replay_start_timestamp: BASE_TIMESTAMP / 1000, - urls: ['http://localhost/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough + urls: ['http://localhost:3000/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough }), }); }); @@ -129,7 +129,7 @@ describe('Integration | events', () => { expect(replay).toHaveLastSentReplay({ replayEventPayload: expect.objectContaining({ replay_start_timestamp: (BASE_TIMESTAMP - 10000) / 1000, - urls: ['http://localhost/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough + urls: ['http://localhost:3000/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough }), }); }); @@ -160,7 +160,7 @@ describe('Integration | events', () => { addEvent(replay, TEST_EVENT); // This event will trigger a flush WINDOW.dispatchEvent(new Event('blur')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockTransportSend).toHaveBeenCalledTimes(1); diff --git a/packages/replay-internal/test/integration/flush.test.ts b/packages/replay-internal/test/integration/flush.test.ts index ee4cf456fbf9..bb00da732df3 100644 --- a/packages/replay-internal/test/integration/flush.test.ts +++ b/packages/replay-internal/test/integration/flush.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockedFunction } from 'vitest'; + import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; import * as SentryUtils from '@sentry/utils'; @@ -5,7 +8,6 @@ import { DEFAULT_FLUSH_MIN_DELAY, MAX_REPLAY_DURATION, WINDOW } from '../../src/ import type { ReplayContainer } from '../../src/replay'; import { clearSession } from '../../src/session/clearSession'; import type { EventBuffer } from '../../src/types'; -import * as AddMemoryEntry from '../../src/util/addMemoryEntry'; import { createPerformanceEntries } from '../../src/util/createPerformanceEntries'; import { createPerformanceSpans } from '../../src/util/createPerformanceSpans'; import * as SendReplay from '../../src/util/sendReplay'; @@ -16,17 +18,11 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -type MockSendReplay = jest.MockedFunction; -type MockAddPerformanceEntries = jest.MockedFunction; -type MockAddMemoryEntry = jest.SpyInstance; -type MockEventBufferFinish = jest.MockedFunction; -type MockFlush = jest.MockedFunction; -type MockRunFlush = jest.MockedFunction; +type MockSendReplay = MockedFunction; +type MockAddPerformanceEntries = MockedFunction; +type MockEventBufferFinish = MockedFunction; +type MockFlush = MockedFunction; +type MockRunFlush = MockedFunction; const prevLocation = WINDOW.location; const prevBrowserPerformanceTimeOrigin = SentryUtils.browserPerformanceTimeOrigin; @@ -41,48 +37,41 @@ describe('Integration | flush', () => { let mockFlush: MockFlush; let mockRunFlush: MockRunFlush; let mockEventBufferFinish: MockEventBufferFinish; - let mockAddMemoryEntry: MockAddMemoryEntry; let mockAddPerformanceEntries: MockAddPerformanceEntries; beforeAll(async () => { - jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { + vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { domHandler = handler; }); ({ replay } = await mockSdk()); - mockSendReplay = jest.spyOn(SendReplay, 'sendReplay'); + mockSendReplay = vi.spyOn(SendReplay, 'sendReplay'); mockSendReplay.mockImplementation( - jest.fn(async () => { + vi.fn(async () => { return; }), ); // @ts-expect-error private API - mockFlush = jest.spyOn(replay, '_flush'); + mockFlush = vi.spyOn(replay, '_flush'); // @ts-expect-error private API - mockRunFlush = jest.spyOn(replay, '_runFlush'); + mockRunFlush = vi.spyOn(replay, '_runFlush'); // @ts-expect-error private API - mockAddPerformanceEntries = jest.spyOn(replay, '_addPerformanceEntries'); + mockAddPerformanceEntries = vi.spyOn(replay, '_addPerformanceEntries'); mockAddPerformanceEntries.mockImplementation(async () => { return []; }); - - mockAddMemoryEntry = jest.spyOn(AddMemoryEntry, 'addMemoryEntry'); }); - beforeEach(() => { - jest.runAllTimers(); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - mockSendReplay.mockClear(); + beforeEach(async () => { + await vi.runAllTimersAsync(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); replay.eventBuffer?.destroy(); - mockAddPerformanceEntries.mockClear(); - mockFlush.mockClear(); - mockRunFlush.mockClear(); - mockAddMemoryEntry.mockClear(); + vi.clearAllMocks(); sessionStorage.clear(); clearSession(replay); @@ -90,7 +79,7 @@ describe('Integration | flush', () => { replay.setInitialState(); if (replay.eventBuffer) { - jest.spyOn(replay.eventBuffer, 'finish'); + vi.spyOn(replay.eventBuffer, 'finish'); } mockEventBufferFinish = replay.eventBuffer?.finish as MockEventBufferFinish; mockEventBufferFinish.mockClear(); @@ -102,9 +91,8 @@ describe('Integration | flush', () => { }); afterEach(async () => { - jest.runAllTimers(); - await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + await vi.runAllTimersAsync(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); mockRecord.takeFullSnapshot.mockClear(); Object.defineProperty(WINDOW, 'location', { value: prevLocation, @@ -133,16 +121,12 @@ describe('Integration | flush', () => { expect(mockFlush).toHaveBeenCalledTimes(4); - jest.runAllTimers(); - await new Promise(process.nextTick); expect(mockRunFlush).toHaveBeenCalledTimes(1); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(mockRunFlush).toHaveBeenCalledTimes(2); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(mockRunFlush).toHaveBeenCalledTimes(2); }); @@ -164,18 +148,18 @@ describe('Integration | flush', () => { name: 'click', event: new Event('click'), }); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); // flush #2 @ t=5s - due to click expect(mockFlush).toHaveBeenCalledTimes(2); - await advanceTimers(1000); + await vi.advanceTimersByTimeAsync(1000); // flush #3 @ t=6s - due to blur WINDOW.dispatchEvent(new Event('blur')); expect(mockFlush).toHaveBeenCalledTimes(3); // NOTE: Blur also adds a breadcrumb which calls `addUpdate`, meaning it will // flush after `flushMinDelay`, but this gets cancelled by the blur - await advanceTimers(8000); + await vi.advanceTimersByTimeAsync(8000); expect(mockFlush).toHaveBeenCalledTimes(3); // flush #4 @ t=14s - due to blur @@ -183,7 +167,7 @@ describe('Integration | flush', () => { expect(mockFlush).toHaveBeenCalledTimes(4); expect(mockRunFlush).toHaveBeenCalledTimes(1); - await advanceTimers(6000); + await vi.advanceTimersByTimeAsync(6000); // t=20s // addPerformanceEntries is finished, `flushLock` promise is resolved, calls // debouncedFlush, which will call `flush` in 1 second @@ -236,7 +220,7 @@ describe('Integration | flush', () => { ); // flush #5 @ t=25s - debounced flush calls `flush` // 20s + `flushMinDelay` which is 5 seconds - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(5); expect(mockRunFlush).toHaveBeenCalledTimes(2); @@ -251,7 +235,7 @@ describe('Integration | flush', () => { }); // Make sure there's no other calls - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockSendReplay).toHaveBeenCalledTimes(2); }); @@ -267,11 +251,11 @@ describe('Integration | flush', () => { const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); // Make sure there's nothing queued up after - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); }); @@ -293,13 +277,13 @@ describe('Integration | flush', () => { const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); expect(mockSendReplay).toHaveBeenCalledTimes(0); // it should re-schedule the flush, so once the min. duration is reached it should automatically send it - await advanceTimers(100_000 - DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(100_000 - DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(20); expect(mockSendReplay).toHaveBeenCalledTimes(1); @@ -309,7 +293,7 @@ describe('Integration | flush', () => { it('does not flush if session is too long', async () => { replay.getOptions().maxReplayDuration = 100_000; - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); sessionStorage.clear(); clearSession(replay); @@ -322,7 +306,7 @@ describe('Integration | flush', () => { return true; }; - await advanceTimers(120_000); + await vi.advanceTimersByTimeAsync(120_000); // click happens first domHandler({ @@ -334,7 +318,7 @@ describe('Integration | flush', () => { const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); expect(mockSendReplay).toHaveBeenCalledTimes(0); @@ -351,7 +335,7 @@ describe('Integration | flush', () => { replay['_initializeSessionForSampling'](); replay.setInitialState(); await new Promise(process.nextTick); - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); // Clear the event buffer to simulate no checkout happened replay.eventBuffer!.clear(); @@ -363,7 +347,7 @@ describe('Integration | flush', () => { }); // no checkout! - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); expect(mockSendReplay).toHaveBeenCalledTimes(1); @@ -385,6 +369,21 @@ describe('Integration | flush', () => { }, }, }, + { + type: 5, + timestamp: BASE_TIMESTAMP, + data: { + tag: 'breadcrumb', + payload: { + timestamp: BASE_TIMESTAMP / 1000, + type: 'default', + category: 'console', + data: { logger: 'replay' }, + level: 'info', + message: '[Replay] Creating new session', + }, + }, + }, { type: 5, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY, @@ -408,14 +407,14 @@ describe('Integration | flush', () => { it('logs warning if adding event that is after maxReplayDuration', async () => { replay.getOptions()._experiments.traceInternals = true; - const spyLogger = jest.spyOn(SentryUtils.logger, 'info'); + const spyLogger = vi.spyOn(SentryUtils.logger, 'info'); sessionStorage.clear(); clearSession(replay); replay['_initializeSessionForSampling'](); replay.setInitialState(); await new Promise(process.nextTick); - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); replay.eventBuffer!.clear(); @@ -427,7 +426,7 @@ describe('Integration | flush', () => { mockRecord._emitter(TEST_EVENT); // no checkout! - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); // No flush is scheduled is aborted because event is after maxReplayDuration expect(mockFlush).toHaveBeenCalledTimes(0); @@ -457,7 +456,7 @@ describe('Integration | flush', () => { replay['_initializeSessionForSampling'](); replay.setInitialState(); await new Promise(process.nextTick); - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); replay.eventBuffer!.clear(); @@ -473,7 +472,7 @@ describe('Integration | flush', () => { const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP + 100 }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(160_000); + await vi.advanceTimersByTimeAsync(160_000); expect(mockFlush).toHaveBeenCalledTimes(1); expect(mockSendReplay).toHaveBeenCalledTimes(0); diff --git a/packages/replay-internal/test/integration/getReplayId.test.ts b/packages/replay-internal/test/integration/getReplayId.test.ts index 1080186974fc..7f24e5b1cbb7 100644 --- a/packages/replay-internal/test/integration/getReplayId.test.ts +++ b/packages/replay-internal/test/integration/getReplayId.test.ts @@ -5,7 +5,7 @@ useFakeTimers(); describe('Integration | getReplayId', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('works', async () => { diff --git a/packages/replay-internal/test/integration/integrationSettings.test.ts b/packages/replay-internal/test/integration/integrationSettings.test.ts index 3d7c180faf2f..b0ec35fc8a05 100644 --- a/packages/replay-internal/test/integration/integrationSettings.test.ts +++ b/packages/replay-internal/test/integration/integrationSettings.test.ts @@ -1,8 +1,11 @@ +import { vi } from 'vitest'; +import type { MockInstance } from 'vitest'; + import { mockSdk } from '../index'; describe('Integration | integrationSettings', () => { beforeEach(() => { - jest.resetModules(); + vi.resetModules(); }); describe('blockAllMedia', () => { @@ -14,10 +17,10 @@ describe('Integration | integrationSettings', () => { }); describe('replaysSessionSampleRate', () => { - let mockConsole: jest.SpyInstance; + let mockConsole: MockInstance; beforeEach(() => { - mockConsole = jest.spyOn(console, 'warn').mockImplementation(jest.fn()); + mockConsole = vi.spyOn(console, 'warn').mockImplementation(vi.fn()); }); afterEach(() => { @@ -53,10 +56,10 @@ describe('Integration | integrationSettings', () => { }); describe('replaysOnErrorSampleRate', () => { - let mockConsole: jest.SpyInstance; + let mockConsole: MockInstance; beforeEach(() => { - mockConsole = jest.spyOn(console, 'warn').mockImplementation(jest.fn()); + mockConsole = vi.spyOn(console, 'warn').mockImplementation(vi.fn()); }); afterEach(() => { @@ -92,10 +95,10 @@ describe('Integration | integrationSettings', () => { }); describe('all sample rates', () => { - let mockConsole: jest.SpyInstance; + let mockConsole: MockInstance; beforeEach(() => { - mockConsole = jest.spyOn(console, 'warn').mockImplementation(jest.fn()); + mockConsole = vi.spyOn(console, 'warn').mockImplementation(vi.fn()); }); afterEach(() => { diff --git a/packages/replay-internal/test/integration/rateLimiting.test.ts b/packages/replay-internal/test/integration/rateLimiting.test.ts index 70cba8f35eff..01e52aa641ef 100644 --- a/packages/replay-internal/test/integration/rateLimiting.test.ts +++ b/packages/replay-internal/test/integration/rateLimiting.test.ts @@ -10,18 +10,18 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); + vi.advanceTimersByTime(time); await new Promise(process.nextTick); } -type MockTransportSend = jest.MockedFunction; +type MockTransportSend = vi.MockedFunction; describe('Integration | rate-limiting behaviour', () => { let replay: ReplayContainer; let mockTransportSend: MockTransportSend; beforeEach(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); ({ replay } = await mockSdk({ autoStart: false, @@ -35,7 +35,7 @@ describe('Integration | rate-limiting behaviour', () => { afterEach(async () => { clearSession(replay); - jest.clearAllMocks(); + vi.clearAllMocks(); replay && replay.stop(); }); @@ -61,7 +61,7 @@ describe('Integration | rate-limiting behaviour', () => { } as TransportMakeRequestResponse, ], ])('handles %s responses by stopping the replay', async (_name, { statusCode, headers }) => { - const mockStop = jest.spyOn(replay, 'stop'); + const mockStop = vi.spyOn(replay, 'stop'); mockTransportSend.mockImplementationOnce(() => { return Promise.resolve({ statusCode, headers }); @@ -93,7 +93,7 @@ describe('Integration | rate-limiting behaviour', () => { } as TransportMakeRequestResponse, ], ])('handles %s responses by not stopping', async (_name, { statusCode, headers }) => { - const mockStop = jest.spyOn(replay, 'stop'); + const mockStop = vi.spyOn(replay, 'stop'); mockTransportSend.mockImplementationOnce(() => { return Promise.resolve({ statusCode, headers }); diff --git a/packages/replay-internal/test/integration/rrweb.test.ts b/packages/replay-internal/test/integration/rrweb.test.ts index 4423c45246ea..1b571b356244 100644 --- a/packages/replay-internal/test/integration/rrweb.test.ts +++ b/packages/replay-internal/test/integration/rrweb.test.ts @@ -5,7 +5,7 @@ useFakeTimers(); describe('Integration | rrweb', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('calls rrweb.record with custom options', async () => { @@ -16,19 +16,19 @@ describe('Integration | rrweb', () => { }, }); expect(mockRecord.mock.calls[0][0]).toMatchInlineSnapshot(` - Object { - "blockSelector": ".sentry-block,[data-sentry-block],base[href=\\"/\\"],img,image,svg,video,object,picture,embed,map,audio,link[rel=\\"icon\\"],link[rel=\\"apple-touch-icon\\"]", + { + "blockSelector": ".sentry-block,[data-sentry-block],base[href="/"],img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]", "collectFonts": true, "emit": [Function], "errorHandler": [Function], - "ignoreSelector": ".sentry-test-ignore,.sentry-ignore,[data-sentry-ignore],input[type=\\"file\\"]", + "ignoreSelector": ".sentry-test-ignore,.sentry-ignore,[data-sentry-ignore],input[type="file"]", "inlineImages": false, "inlineStylesheet": true, "maskAllInputs": true, "maskAllText": true, "maskAttributeFn": [Function], "maskInputFn": undefined, - "maskInputOptions": Object { + "maskInputOptions": { "password": true, }, "maskTextFn": undefined, diff --git a/packages/replay-internal/test/integration/sampling.test.ts b/packages/replay-internal/test/integration/sampling.test.ts index b82bb9538b5e..433a010b76aa 100644 --- a/packages/replay-internal/test/integration/sampling.test.ts +++ b/packages/replay-internal/test/integration/sampling.test.ts @@ -5,7 +5,7 @@ useFakeTimers(); describe('Integration | sampling', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('does nothing if not sampled', async () => { @@ -20,8 +20,8 @@ describe('Integration | sampling', () => { }); // @ts-expect-error private API - const spyAddListeners = jest.spyOn(replay, '_addListeners'); - jest.runAllTimers(); + const spyAddListeners = vi.spyOn(replay, '_addListeners'); + vi.runAllTimers(); expect(replay.session).toBe(undefined); expect(replay.eventBuffer).toBeNull(); @@ -55,12 +55,12 @@ describe('Integration | sampling', () => { }); // @ts-expect-error private API - const spyAddListeners = jest.spyOn(replay, '_addListeners'); + const spyAddListeners = vi.spyOn(replay, '_addListeners'); // @ts-expect-error protected integration._initialize(); - jest.runAllTimers(); + vi.runAllTimers(); expect(replay.session?.id).toBeDefined(); expect(replay.eventBuffer).toBeDefined(); @@ -70,9 +70,9 @@ describe('Integration | sampling', () => { expect(replay.getContext()).toEqual({ errorIds: new Set(), initialTimestamp: expect.any(Number), - initialUrl: 'http://localhost/', + initialUrl: 'http://localhost:3000/', traceIds: new Set(), - urls: ['http://localhost/'], + urls: ['http://localhost:3000/'], }); expect(replay.recordingMode).toBe('buffer'); diff --git a/packages/replay-internal/test/integration/sendReplayEvent.test.ts b/packages/replay-internal/test/integration/sendReplayEvent.test.ts index 58cdecaf1c65..8e99f72ff517 100644 --- a/packages/replay-internal/test/integration/sendReplayEvent.test.ts +++ b/packages/replay-internal/test/integration/sendReplayEvent.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance, MockedFunction } from 'vitest'; + import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; import * as SentryCore from '@sentry/core'; import type { Transport } from '@sentry/types'; @@ -14,23 +17,18 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -type MockTransportSend = jest.MockedFunction; +type MockTransportSend = MockedFunction; describe('Integration | sendReplayEvent', () => { let replay: ReplayContainer; let mockTransportSend: MockTransportSend; - let mockSendReplayRequest: jest.SpyInstance; + let mockSendReplayRequest: MockInstance; let domHandler: DomHandler; const { record: mockRecord } = mockRrweb(); beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { domHandler = handler; }); @@ -45,14 +43,14 @@ describe('Integration | sendReplayEvent', () => { }, })); - mockSendReplayRequest = jest.spyOn(SendReplayRequest, 'sendReplayRequest'); + mockSendReplayRequest = vi.spyOn(SendReplayRequest, 'sendReplayRequest'); - jest.runAllTimers(); + await vi.runAllTimersAsync(); mockTransportSend = SentryCore.getClient()?.getTransport()?.send as MockTransportSend; }); beforeEach(() => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); mockRecord.takeFullSnapshot.mockClear(); mockTransportSend.mockClear(); @@ -66,9 +64,9 @@ describe('Integration | sendReplayEvent', () => { }); afterEach(async () => { - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); clearSession(replay); }); @@ -87,7 +85,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); addEvent(replay, TEST_EVENT); @@ -121,7 +119,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); domHandler({ name: 'click', @@ -143,7 +141,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); domHandler({ name: 'input', @@ -158,7 +156,7 @@ describe('Integration | sendReplayEvent', () => { mockRecord._emitter(TEST_EVENT); // Pretend 5 seconds have passed const ELAPSED = 5000; - await advanceTimers(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); @@ -177,7 +175,7 @@ describe('Integration | sendReplayEvent', () => { // Fire a new event every 4 seconds, 4 times for (let i = 0; i < 4; i++) { mockRecord._emitter(TEST_EVENT); - jest.advanceTimersByTime(4_000); + vi.advanceTimersByTime(4_000); } // We are at time = +16seconds now (relative to BASE_TIMESTAMP) @@ -191,7 +189,7 @@ describe('Integration | sendReplayEvent', () => { // There should also not be another attempt at an upload 5 seconds after the last replay event mockTransportSend.mockClear(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).not.toHaveLastSentReplay(); expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); @@ -202,7 +200,7 @@ describe('Integration | sendReplayEvent', () => { // Let's make sure it continues to work mockTransportSend.mockClear(); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); }); @@ -216,7 +214,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); const hiddenBreadcrumb = { @@ -254,13 +252,13 @@ describe('Integration | sendReplayEvent', () => { }); // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); addEvent(replay, TEST_EVENT); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); @@ -281,7 +279,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - await advanceTimers(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([ @@ -310,26 +308,26 @@ describe('Integration | sendReplayEvent', () => { const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); // Suppress console.errors - const mockConsole = jest.spyOn(console, 'error').mockImplementation(jest.fn()); + const mockConsole = vi.spyOn(console, 'error').mockImplementation(vi.fn()); // fail the first and second requests and pass the third one mockTransportSend.mockImplementationOnce(() => { throw new Error('Something bad happened'); }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); mockTransportSend.mockImplementationOnce(() => { throw new Error('Something bad happened'); }); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); // next tick should retry and succeed mockConsole.mockRestore(); - await advanceTimers(8000); - await advanceTimers(2000); + await vi.advanceTimersByTimeAsync(8000); + await vi.advanceTimersByTimeAsync(2000); expect(replay).toHaveLastSentReplay({ replayEventPayload: expect.objectContaining({ @@ -339,7 +337,7 @@ describe('Integration | sendReplayEvent', () => { // timestamp is set on first try, after 5s flush timestamp: (BASE_TIMESTAMP + 5000) / 1000, trace_ids: [], - urls: ['http://localhost/'], + urls: ['http://localhost:3000/'], }), recordingPayloadHeader: { segment_id: 0 }, recordingData: JSON.stringify([TEST_EVENT]), @@ -351,17 +349,17 @@ describe('Integration | sendReplayEvent', () => { expect(replay.session?.segmentId).toBe(1); // next tick should do nothing - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).not.toHaveLastSentReplay(); }); it('fails to upload data and hits retry max and stops', async () => { const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); - const spyHandleException = jest.spyOn(SentryCore, 'captureException'); + const spyHandleException = vi.spyOn(SentryCore, 'captureException'); // Suppress console.errors - const mockConsole = jest.spyOn(console, 'error').mockImplementation(jest.fn()); + const mockConsole = vi.spyOn(console, 'error').mockImplementation(vi.fn()); expect(replay.session?.segmentId).toBe(0); @@ -371,24 +369,24 @@ describe('Integration | sendReplayEvent', () => { }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(mockSendReplayRequest).toHaveBeenCalledTimes(1); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockSendReplayRequest).toHaveBeenCalledTimes(2); - await advanceTimers(10000); + await vi.advanceTimersByTimeAsync(10000); expect(mockSendReplayRequest).toHaveBeenCalledTimes(3); - await advanceTimers(30000); + await vi.advanceTimersByTimeAsync(30000); expect(mockSendReplayRequest).toHaveBeenCalledTimes(4); mockConsole.mockReset(); // Make sure it doesn't retry again - jest.runAllTimers(); + await vi.runAllTimersAsync(); expect(mockSendReplayRequest).toHaveBeenCalledTimes(4); // Retries = 3 (total tries = 4 including initial attempt) @@ -407,7 +405,7 @@ describe('Integration | sendReplayEvent', () => { // Events are ignored now, because we stopped mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockSendReplayRequest).toHaveBeenCalledTimes(4); }); diff --git a/packages/replay-internal/test/integration/session.test.ts b/packages/replay-internal/test/integration/session.test.ts index 0485fa78dd95..fb1327296ad2 100644 --- a/packages/replay-internal/test/integration/session.test.ts +++ b/packages/replay-internal/test/integration/session.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { getClient } from '@sentry/core'; import type { Transport } from '@sentry/types'; @@ -24,11 +26,6 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - const prevLocation = WINDOW.location; describe('Integration | session', () => { @@ -43,16 +40,16 @@ describe('Integration | session', () => { }, })); - const mockTransport = getClient()?.getTransport()?.send as jest.MockedFunction; + const mockTransport = getClient()?.getTransport()?.send as vi.MockedFunction; mockTransport?.mockClear(); + await vi.runAllTimersAsync(); }); afterEach(async () => { replay.stop(); - jest.runAllTimers(); - await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + await vi.runAllTimersAsync(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); Object.defineProperty(WINDOW, 'location', { value: prevLocation, @@ -72,7 +69,7 @@ describe('Integration | session', () => { const initialSession = { ...replay.session } as Session; - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); document.dispatchEvent(new Event('visibilitychange')); @@ -83,7 +80,7 @@ describe('Integration | session', () => { it('does not create a new session when document becomes focused after [SESSION_IDLE_EXPIRE_DURATION]ms', () => { const initialSession = { ...replay.session } as Session; - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); WINDOW.dispatchEvent(new Event('focus')); @@ -105,7 +102,7 @@ describe('Integration | session', () => { expect(replay).toHaveSameSession(initialSession); // User comes back before `SESSION_IDLE_EXPIRE_DURATION` elapses - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 1); Object.defineProperty(document, 'visibilityState', { configurable: true, get: function () { @@ -126,7 +123,7 @@ describe('Integration | session', () => { expect(initialSession?.id).toBeDefined(); expect(replay.getContext()).toEqual( expect.objectContaining({ - initialUrl: 'http://localhost/', + initialUrl: 'http://localhost:3000/', initialTimestamp: BASE_TIMESTAMP, }), ); @@ -137,7 +134,7 @@ describe('Integration | session', () => { }); const ELAPSED = SESSION_IDLE_EXPIRE_DURATION + 1; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); // Session has become in an idle state // @@ -203,10 +200,10 @@ describe('Integration | session', () => { // Replay does not send immediately because checkout was due to expired session expect(replay).not.toHaveLastSentReplay(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); await new Promise(process.nextTick); - const newTimestamp = BASE_TIMESTAMP + ELAPSED + 20; + const newTimestamp = BASE_TIMESTAMP + ELAPSED; expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -236,7 +233,7 @@ describe('Integration | session', () => { expect(initialSession?.id).toBeDefined(); expect(replay.getContext()).toEqual( expect.objectContaining({ - initialUrl: 'http://localhost/', + initialUrl: 'http://localhost:3000/', initialTimestamp: BASE_TIMESTAMP, }), ); @@ -247,7 +244,7 @@ describe('Integration | session', () => { }); const ELAPSED = SESSION_IDLE_PAUSE_DURATION + 1; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); // Session has become in an idle state // @@ -305,7 +302,7 @@ describe('Integration | session', () => { // Replay does not send immediately expect(replay).not.toHaveLastSentReplay(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).toHaveLastSentReplay(); }); @@ -326,17 +323,15 @@ describe('Integration | session', () => { }); it('creates a new session if current session exceeds MAX_REPLAY_DURATION', async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); const initialSession = { ...replay.session } as Session; expect(initialSession?.id).toBeDefined(); - expect(replay.getContext()).toEqual( - expect.objectContaining({ - initialUrl: 'http://localhost/', - initialTimestamp: BASE_TIMESTAMP, - }), - ); + expect(replay.getContext()).toMatchObject({ + initialUrl: 'http://localhost:3000/', + initialTimestamp: BASE_TIMESTAMP, + }); const url = 'http://dummy/'; Object.defineProperty(WINDOW, 'location', { @@ -345,7 +340,7 @@ describe('Integration | session', () => { // Advanced past MAX_REPLAY_DURATION const ELAPSED = MAX_REPLAY_DURATION + 1; - jest.advanceTimersByTime(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); // Update activity so as to not consider session to be idling replay['_updateUserActivity'](); replay['_updateSessionActivity'](); @@ -360,8 +355,7 @@ describe('Integration | session', () => { const optionsEvent = createOptionsEvent(replay); const timestampAtRefresh = BASE_TIMESTAMP + ELAPSED; - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); expect(replay).not.toHaveSameSession(initialSession); expect(replay).not.toHaveLastSentReplay(); @@ -373,17 +367,15 @@ describe('Integration | session', () => { event: new Event('click'), }); - // 20 is for the process.nextTick - const newTimestamp = timestampAtRefresh + 20; + const newTimestamp = timestampAtRefresh; const NEW_TEST_EVENT = getTestEventIncremental({ data: { name: 'test' }, - timestamp: newTimestamp + DEFAULT_FLUSH_MIN_DELAY + 20, + timestamp: newTimestamp + DEFAULT_FLUSH_MIN_DELAY, }); mockRecord._emitter(NEW_TEST_EVENT); - jest.runAllTimers(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -431,7 +423,7 @@ describe('Integration | session', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - await advanceTimers(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); @@ -445,7 +437,7 @@ describe('Integration | session', () => { addEvent(replay, TEST_EVENT); WINDOW.dispatchEvent(new Event('blur')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay.session?.segmentId).toBe(2); expect(replay).toHaveLastSentReplay({ diff --git a/packages/replay-internal/test/integration/shouldFilterRequest.test.ts b/packages/replay-internal/test/integration/shouldFilterRequest.test.ts index 888b30e2c25f..207a8182581f 100644 --- a/packages/replay-internal/test/integration/shouldFilterRequest.test.ts +++ b/packages/replay-internal/test/integration/shouldFilterRequest.test.ts @@ -3,7 +3,7 @@ import { mockSdk } from '../index'; describe('Integration | shouldFilterRequest', () => { beforeEach(() => { - jest.resetModules(); + vi.resetModules(); }); it('should not filter requests from non-Sentry ingest URLs', async () => { diff --git a/packages/replay-internal/test/integration/stop.test.ts b/packages/replay-internal/test/integration/stop.test.ts index 3afbe3eeef1a..b4d256f3f9f0 100644 --- a/packages/replay-internal/test/integration/stop.test.ts +++ b/packages/replay-internal/test/integration/stop.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance, MockedFunction } from 'vitest'; + import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; import { WINDOW } from '../../src/constants'; @@ -13,7 +16,7 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -type MockRunFlush = jest.MockedFunction; +type MockRunFlush = MockedFunction; describe('Integration | stop', () => { let replay: ReplayContainer; @@ -22,31 +25,31 @@ describe('Integration | stop', () => { const { record: mockRecord } = mockRrweb(); - let mockAddDomInstrumentationHandler: jest.SpyInstance; + let mockAddDomInstrumentationHandler: MockInstance; let mockRunFlush: MockRunFlush; beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - mockAddDomInstrumentationHandler = jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler'); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + mockAddDomInstrumentationHandler = vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler'); ({ replay, integration } = await mockSdk()); // @ts-expect-error private API - mockRunFlush = jest.spyOn(replay, '_runFlush'); + mockRunFlush = vi.spyOn(replay, '_runFlush'); - jest.runAllTimers(); + vi.runAllTimers(); }); beforeEach(() => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); replay.eventBuffer?.destroy(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); afterEach(async () => { - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); sessionStorage.clear(); clearSession(replay); replay['_initializeSessionForSampling'](); @@ -71,8 +74,6 @@ describe('Integration | stop', () => { }, }); const ELAPSED = 5000; - // Not sure where the 20ms comes from tbh - const EXTRA_TICKS = 20; const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); const previousSessionId = replay.session?.id; @@ -80,7 +81,7 @@ describe('Integration | stop', () => { await integration.stop(); // Pretend 5 seconds have passed - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); addEvent(replay, TEST_EVENT); WINDOW.dispatchEvent(new Event('blur')); @@ -99,9 +100,9 @@ describe('Integration | stop', () => { // will be different session expect(replay.session?.id).not.toEqual(previousSessionId); - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); - const timestamp = +new Date(BASE_TIMESTAMP + ELAPSED + ELAPSED + EXTRA_TICKS) / 1000; + const timestamp = +new Date(BASE_TIMESTAMP + ELAPSED + ELAPSED) / 1000; const hiddenBreadcrumb = { type: 5, @@ -118,7 +119,7 @@ describe('Integration | stop', () => { addEvent(replay, TEST_EVENT); WINDOW.dispatchEvent(new Event('blur')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -126,7 +127,7 @@ describe('Integration | stop', () => { // This event happens when we call `replay.start` { data: { isCheckout: true }, - timestamp: BASE_TIMESTAMP + ELAPSED + EXTRA_TICKS, + timestamp: BASE_TIMESTAMP + ELAPSED, type: 2, }, optionsEvent, @@ -137,7 +138,7 @@ describe('Integration | stop', () => { // Session's last activity is last updated when we call `setup()` and *NOT* // when tab is blurred - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP + ELAPSED + 20); + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP + ELAPSED); }); it('does not buffer new events after being stopped', async function () { diff --git a/packages/replay-internal/test/mocks/mockRrweb.ts b/packages/replay-internal/test/mocks/mockRrweb.ts index 30c5557298fc..bdffef9b7c32 100644 --- a/packages/replay-internal/test/mocks/mockRrweb.ts +++ b/packages/replay-internal/test/mocks/mockRrweb.ts @@ -1,10 +1,40 @@ -import type { record as rrwebRecord } from '@sentry-internal/rrweb'; +import { vi } from 'vitest'; +import type { Mock, MockedFunction } from 'vitest'; + +import { record } from '@sentry-internal/rrweb'; import type { RecordingEvent, ReplayEventWithTime } from '../../src/types'; import { ReplayEventTypeFullSnapshot, ReplayEventTypeIncrementalSnapshot } from '../../src/types'; +vi.mock('@sentry-internal/rrweb', async () => { + const mockRecordFn: Mock & Partial = vi.fn(({ emit }) => { + mockRecordFn._emitter = emit; + + emit(createCheckoutPayload()); + return function stop() { + mockRecordFn._emitter = vi.fn(); + }; + }); + mockRecordFn.takeFullSnapshot = vi.fn((isCheckout: boolean) => { + if (!mockRecordFn._emitter) { + return; + } + + mockRecordFn._emitter(createCheckoutPayload(isCheckout), isCheckout); + }); + + const ActualRrweb = await vi.importActual('@sentry-internal/rrweb'); + + mockRecordFn.mirror = ActualRrweb.record.mirror; + + return { + ...ActualRrweb, + record: mockRecordFn, + }; +}); + type RecordAdditionalProperties = { - takeFullSnapshot: jest.Mock; + takeFullSnapshot: Mock; // Below are not mocked addCustomEvent: () => void; @@ -15,7 +45,7 @@ type RecordAdditionalProperties = { _emitter: (event: RecordingEvent, ...args: any[]) => void; }; -export type RecordMock = jest.MockedFunction & RecordAdditionalProperties; +export type RecordMock = MockedFunction & RecordAdditionalProperties; function createCheckoutPayload(isCheckout: boolean = true): ReplayEventWithTime { return { @@ -26,34 +56,7 @@ function createCheckoutPayload(isCheckout: boolean = true): ReplayEventWithTime } export function mockRrweb(): { record: RecordMock } { - const mockRecordFn: jest.Mock & Partial = jest.fn(({ emit }) => { - mockRecordFn._emitter = emit; - - emit(createCheckoutPayload()); - return function stop() { - mockRecordFn._emitter = jest.fn(); - }; - }); - mockRecordFn.takeFullSnapshot = jest.fn((isCheckout: boolean) => { - if (!mockRecordFn._emitter) { - return; - } - - mockRecordFn._emitter(createCheckoutPayload(isCheckout), isCheckout); - }); - - jest.mock('@sentry-internal/rrweb', () => { - const ActualRrweb = jest.requireActual('@sentry-internal/rrweb'); - - mockRecordFn.mirror = ActualRrweb.record.mirror; - - return { - ...ActualRrweb, - record: mockRecordFn, - }; - }); - return { - record: mockRecordFn as RecordMock, + record: record as RecordMock, }; } diff --git a/packages/replay-internal/test/mocks/mockSdk.ts b/packages/replay-internal/test/mocks/mockSdk.ts index f57ef8c51d71..d5c9a088b779 100644 --- a/packages/replay-internal/test/mocks/mockSdk.ts +++ b/packages/replay-internal/test/mocks/mockSdk.ts @@ -1,4 +1,5 @@ import type { Envelope, Transport, TransportMakeRequestResponse } from '@sentry/types'; +import { vi } from 'vitest'; import type { Replay as ReplayIntegration } from '../../src/integration'; import type { ReplayContainer } from '../../src/replay'; @@ -16,7 +17,7 @@ class MockTransport implements Transport { send: (request: Envelope) => PromiseLike; constructor() { - this.send = jest.fn(async () => { + this.send = vi.fn(async () => { return { statusCode: 200, }; diff --git a/packages/replay-internal/test/mocks/resetSdkMock.ts b/packages/replay-internal/test/mocks/resetSdkMock.ts index 47a1522a4c63..ff2006b0604e 100644 --- a/packages/replay-internal/test/mocks/resetSdkMock.ts +++ b/packages/replay-internal/test/mocks/resetSdkMock.ts @@ -1,4 +1,5 @@ import { resetInstrumentationHandlers } from '@sentry/utils'; +import { vi } from 'vitest'; import type { Replay as ReplayIntegration } from '../../src/integration'; import type { ReplayContainer } from '../../src/replay'; @@ -16,15 +17,15 @@ export async function resetSdkMock({ replayOptions, sentryOptions, autoStart }: }> { let domHandler: DomHandler; - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.clearAllMocks(); - jest.resetModules(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.clearAllMocks(); + vi.resetModules(); // Clear all handlers that have been registered resetInstrumentationHandlers(); const SentryBrowserUtils = await import('@sentry-internal/browser-utils'); - jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { + vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { domHandler = handler; }); const { mockRrweb } = await import('./mockRrweb'); @@ -37,9 +38,8 @@ export async function resetSdkMock({ replayOptions, sentryOptions, autoStart }: }); // XXX: This is needed to ensure `domHandler` is set - jest.runAllTimers(); - await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + await vi.runAllTimersAsync(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); return { // @ts-expect-error use before assign diff --git a/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts b/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts index ae52d2076293..07ef281800c1 100644 --- a/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts @@ -4,12 +4,12 @@ import { BASE_TIMESTAMP } from '../..'; import { ClickDetector, ignoreElement } from '../../../src/coreHandlers/handleClick'; import type { ReplayContainer } from '../../../src/types'; -jest.useFakeTimers(); +vi.useFakeTimers(); describe('Unit | coreHandlers | handleClick', () => { describe('ClickDetector', () => { beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); }); test('it captures a single click', async () => { @@ -17,7 +17,7 @@ describe('Unit | coreHandlers | handleClick', () => { getCurrentRoute: () => 'test-route', } as ReplayContainer; - const mockAddBreadcrumbEvent = jest.fn(); + const mockAddBreadcrumbEvent = vi.fn(); const detector = new ClickDetector( replay, @@ -41,15 +41,15 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -61,13 +61,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: expect.any(Number), }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); }); @@ -76,7 +76,7 @@ describe('Unit | coreHandlers | handleClick', () => { getCurrentRoute: () => 'test-route', } as ReplayContainer; - const mockAddBreadcrumbEvent = jest.fn(); + const mockAddBreadcrumbEvent = vi.fn(); const detector = new ClickDetector( replay, @@ -114,15 +114,15 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -134,13 +134,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: BASE_TIMESTAMP / 1000, }); - jest.advanceTimersByTime(2_000); + vi.advanceTimersByTime(2_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(2); expect(mockAddBreadcrumbEvent).toHaveBeenLastCalledWith(replay, { @@ -152,13 +152,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: (BASE_TIMESTAMP + 1200) / 1000, }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(2); }); @@ -167,7 +167,7 @@ describe('Unit | coreHandlers | handleClick', () => { getCurrentRoute: () => 'test-route', } as ReplayContainer; - const mockAddBreadcrumbEvent = jest.fn(); + const mockAddBreadcrumbEvent = vi.fn(); const detector = new ClickDetector( replay, @@ -207,11 +207,11 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(3); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(3); }); @@ -220,7 +220,7 @@ describe('Unit | coreHandlers | handleClick', () => { getCurrentRoute: () => 'test-route', } as ReplayContainer; - const mockAddBreadcrumbEvent = jest.fn(); + const mockAddBreadcrumbEvent = vi.fn(); const detector = new ClickDetector( replay, @@ -260,23 +260,23 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); }); describe('mutations', () => { let detector: ClickDetector; - let mockAddBreadcrumbEvent = jest.fn(); + let mockAddBreadcrumbEvent = vi.fn(); const replay = { getCurrentRoute: () => 'test-route', } as ReplayContainer; beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); - mockAddBreadcrumbEvent = jest.fn(); + mockAddBreadcrumbEvent = vi.fn(); detector = new ClickDetector( replay, @@ -302,14 +302,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(500); + vi.advanceTimersByTime(500); // Pretend a mutation happend detector['_lastMutation'] = BASE_TIMESTAMP / 1000 + 0.5; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); }); @@ -326,14 +326,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); // Pretend a mutation happend detector['_lastMutation'] = BASE_TIMESTAMP / 1000 + 2; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -345,13 +345,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 2000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: expect.any(Number), }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); }); @@ -367,14 +367,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); // Pretend a mutation happend detector['_lastMutation'] = BASE_TIMESTAMP / 1000 + 5; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -386,29 +386,29 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: expect.any(Number), }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); }); }); describe('scroll', () => { let detector: ClickDetector; - let mockAddBreadcrumbEvent = jest.fn(); + let mockAddBreadcrumbEvent = vi.fn(); const replay = { getCurrentRoute: () => 'test-route', } as ReplayContainer; beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); - mockAddBreadcrumbEvent = jest.fn(); + mockAddBreadcrumbEvent = vi.fn(); detector = new ClickDetector( replay, @@ -434,14 +434,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); // Pretend a mutation happend detector['_lastScroll'] = BASE_TIMESTAMP / 1000 + 0.15; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); }); @@ -458,14 +458,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(300); + vi.advanceTimersByTime(300); // Pretend a mutation happend detector['_lastScroll'] = BASE_TIMESTAMP / 1000 + 0.3; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -477,13 +477,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: expect.any(Number), }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/replay-internal/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts b/packages/replay-internal/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts index b3522c0ceb6c..ef81feef3a8e 100644 --- a/packages/replay-internal/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts @@ -14,7 +14,7 @@ import type { EventBufferArray } from '../../../src/eventBuffer/EventBufferArray import type { ReplayContainer, ReplayNetworkOptions } from '../../../src/types'; import { setupReplayContainer } from '../../utils/setupReplayContainer'; -jest.useFakeTimers(); +vi.useFakeTimers(); async function waitForReplayEventBuffer() { // Need one Promise.resolve() per await in the util functions @@ -56,7 +56,7 @@ describe('Unit | coreHandlers | handleNetworkBreadcrumbs', () => { }; beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); options = { replay: setupReplayContainer(), @@ -67,7 +67,7 @@ describe('Unit | coreHandlers | handleNetworkBreadcrumbs', () => { networkResponseHeaders: ['content-type', 'accept', 'x-custom-header'], }; - jest.runAllTimers(); + vi.runAllTimers(); }); it('ignores breadcrumb without data', async () => { diff --git a/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts b/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts index 45483ebab5b4..1979556b3dc9 100644 --- a/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts @@ -3,11 +3,11 @@ import { addBreadcrumbEvent } from '../../../../src/coreHandlers/util/addBreadcr import type { EventBufferArray } from '../../../../src/eventBuffer/EventBufferArray'; import { setupReplayContainer } from '../../../utils/setupReplayContainer'; -jest.useFakeTimers(); +vi.useFakeTimers(); describe('Unit | coreHandlers | util | addBreadcrumbEvent', function () { beforeEach(function () { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); }); it('handles circular references', async () => { diff --git a/packages/replay-internal/test/unit/coreHandlers/util/networkUtils.test.ts b/packages/replay-internal/test/unit/coreHandlers/util/networkUtils.test.ts index 8f240c8ea7a7..4a330081507a 100644 --- a/packages/replay-internal/test/unit/coreHandlers/util/networkUtils.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/util/networkUtils.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { NETWORK_BODY_MAX_SIZE } from '../../../../src/constants'; import { buildNetworkRequestOrResponse, @@ -7,7 +9,7 @@ import { parseContentLengthHeader, } from '../../../../src/coreHandlers/util/networkUtils'; -jest.useFakeTimers(); +vi.useFakeTimers(); describe('Unit | coreHandlers | util | networkUtils', () => { describe('parseContentLengthHeader()', () => { diff --git a/packages/replay-internal/test/unit/coreHandlers/util/xhrUtils.test.ts b/packages/replay-internal/test/unit/coreHandlers/util/xhrUtils.test.ts index d39b473f317f..6c805e6d2238 100644 --- a/packages/replay-internal/test/unit/coreHandlers/util/xhrUtils.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/util/xhrUtils.test.ts @@ -8,12 +8,12 @@ describe('Unit | coreHandlers | util | xhrUtils', () => { }); it('works with a Document', () => { - const body = document.implementation.createHTMLDocument(); + const doc = document.implementation.createHTMLDocument(); const bodyEl = document.createElement('body'); bodyEl.innerHTML = '
abc
'; - body.body = bodyEl; + doc.body = bodyEl; - const actual = _parseXhrResponse(body, ''); + const actual = _parseXhrResponse(doc, ''); expect(actual).toEqual(['
abc
']); }); diff --git a/packages/replay-internal/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts b/packages/replay-internal/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts index 6b314a0d1b24..5854a4fbaa6e 100644 --- a/packages/replay-internal/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts +++ b/packages/replay-internal/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts @@ -120,7 +120,7 @@ describe('Unit | eventBuffer | EventBufferCompressionWorker', () => { await buffer.addEvent(TEST_EVENT); // @ts-expect-error Mock this private so it triggers an error - jest.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { + vi.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { return Promise.reject('test worker error'); }); @@ -141,7 +141,7 @@ describe('Unit | eventBuffer | EventBufferCompressionWorker', () => { await buffer.addEvent({ data: { o: 2 }, timestamp: BASE_TIMESTAMP, type: 3 }); // @ts-expect-error Mock this private so it triggers an error - jest.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { + vi.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { return Promise.reject('test worker error'); }); diff --git a/packages/replay-internal/test/unit/eventBuffer/EventBufferProxy.test.ts b/packages/replay-internal/test/unit/eventBuffer/EventBufferProxy.test.ts index 101d061037dc..819813776c5d 100644 --- a/packages/replay-internal/test/unit/eventBuffer/EventBufferProxy.test.ts +++ b/packages/replay-internal/test/unit/eventBuffer/EventBufferProxy.test.ts @@ -9,11 +9,11 @@ import { createEventBuffer } from './../../../src/eventBuffer'; const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); describe('Unit | eventBuffer | EventBufferProxy', () => { - let consoleErrorSpy: jest.SpyInstance; + let consoleErrorSpy: MockInstance; beforeEach(() => { // Avoid logging errors to console - consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); }); afterEach(() => { diff --git a/packages/replay-internal/test/unit/session/createSession.test.ts b/packages/replay-internal/test/unit/session/createSession.test.ts index 7713e821e74c..f6e6850754a5 100644 --- a/packages/replay-internal/test/unit/session/createSession.test.ts +++ b/packages/replay-internal/test/unit/session/createSession.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import * as Sentry from '@sentry/core'; import type { Hub } from '@sentry/types'; @@ -5,23 +7,23 @@ import { WINDOW } from '../../../src/constants'; import { createSession } from '../../../src/session/createSession'; import { saveSession } from '../../../src/session/saveSession'; -jest.mock('./../../../src/session/saveSession'); +vi.mock('./../../../src/session/saveSession'); -jest.mock('@sentry/utils', () => { +vi.mock('@sentry/utils', async () => { return { - ...(jest.requireActual('@sentry/utils') as { string: unknown }), - uuid4: jest.fn(() => 'test_session_id'), + ...((await vi.importActual('@sentry/utils')) as { string: unknown }), + uuid4: vi.fn(() => 'test_session_id'), }; }); -type CaptureEventMockType = jest.MockedFunction; +type CaptureEventMockType = vi.MockedFunction; describe('Unit | session | createSession', () => { - const captureEventMock: CaptureEventMockType = jest.fn(); + const captureEventMock: CaptureEventMockType = vi.fn(); beforeAll(() => { WINDOW.sessionStorage.clear(); - jest.spyOn(Sentry, 'getCurrentHub').mockImplementation(() => { + vi.spyOn(Sentry, 'getCurrentHub').mockImplementation(() => { return { captureEvent: captureEventMock, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts b/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts index bac9ee397984..51f4793c7768 100644 --- a/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts +++ b/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { MAX_REPLAY_DURATION, SESSION_IDLE_EXPIRE_DURATION, WINDOW } from '../../../src/constants'; import { makeSession } from '../../../src/session/Session'; import * as CreateSession from '../../../src/session/createSession'; @@ -6,10 +8,10 @@ import { loadOrCreateSession } from '../../../src/session/loadOrCreateSession'; import { saveSession } from '../../../src/session/saveSession'; import type { SessionOptions } from '../../../src/types'; -jest.mock('@sentry/utils', () => { +vi.mock('@sentry/utils', async () => { return { - ...(jest.requireActual('@sentry/utils') as { string: unknown }), - uuid4: jest.fn(() => 'test_session_uuid'), + ...((await vi.importActual('@sentry/utils')) as { string: unknown }), + uuid4: vi.fn(() => 'test_session_uuid'), }; }); @@ -42,15 +44,15 @@ function createMockSession(when: number = Date.now(), id = 'test_session_id') { describe('Unit | session | loadOrCreateSession', () => { beforeAll(() => { - jest.spyOn(CreateSession, 'createSession'); - jest.spyOn(FetchSession, 'fetchSession'); + vi.spyOn(CreateSession, 'createSession'); + vi.spyOn(FetchSession, 'fetchSession'); WINDOW.sessionStorage.clear(); }); afterEach(() => { WINDOW.sessionStorage.clear(); - (CreateSession.createSession as jest.MockedFunction).mockClear(); - (FetchSession.fetchSession as jest.MockedFunction).mockClear(); + (CreateSession.createSession as vi.MockedFunction).mockClear(); + (FetchSession.fetchSession as vi.MockedFunction).mockClear(); }); describe('stickySession: false', () => { diff --git a/packages/replay-internal/test/unit/util/addEvent.test.ts b/packages/replay-internal/test/unit/util/addEvent.test.ts index 8c1d9dea8175..748c2a87b684 100644 --- a/packages/replay-internal/test/unit/util/addEvent.test.ts +++ b/packages/replay-internal/test/unit/util/addEvent.test.ts @@ -1,5 +1,7 @@ import 'jsdom-worker'; +import { vi } from 'vitest'; + import { BASE_TIMESTAMP } from '../..'; import { MAX_REPLAY_DURATION, REPLAY_MAX_EVENT_BUFFER_SIZE, SESSION_IDLE_PAUSE_DURATION } from '../../../src/constants'; import type { EventBufferProxy } from '../../../src/eventBuffer/EventBufferProxy'; @@ -12,7 +14,7 @@ useFakeTimers(); describe('Unit | util | addEvent', () => { it('stops when encountering a compression error', async function () { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); const replay = setupReplayContainer({ options: { @@ -20,10 +22,11 @@ describe('Unit | util | addEvent', () => { }, }); + await vi.runAllTimersAsync(); await (replay.eventBuffer as EventBufferProxy).ensureWorkerIsLoaded(); // @ts-expect-error Mock this private so it triggers an error - jest.spyOn(replay.eventBuffer._compression._worker, 'postMessage').mockImplementationOnce(() => { + vi.spyOn(replay.eventBuffer._compression._worker, 'postMessage').mockImplementationOnce(() => { return Promise.reject('test worker error'); }); @@ -33,7 +36,7 @@ describe('Unit | util | addEvent', () => { }); it('stops when exceeding buffer size limit', async function () { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); const replay = setupReplayContainer({ options: { @@ -41,6 +44,8 @@ describe('Unit | util | addEvent', () => { }, }); + await vi.runAllTimersAsync(); + const largeEvent = getTestEventIncremental({ data: { a: 'a'.repeat(REPLAY_MAX_EVENT_BUFFER_SIZE / 3) }, timestamp: BASE_TIMESTAMP, @@ -60,7 +65,7 @@ describe('Unit | util | addEvent', () => { describe('shouldAddEvent', () => { beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); }); it('returns true by default', () => { diff --git a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts index 636eb3aded9d..fd086eea6fa0 100644 --- a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts +++ b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts @@ -1,8 +1,11 @@ -jest.useFakeTimers().setSystemTime(new Date('2023-01-01')); +import { vi } from 'vitest'; -jest.mock('@sentry/utils', () => ({ - ...jest.requireActual('@sentry/utils'), - browserPerformanceTimeOrigin: Date.now(), +vi.useFakeTimers(); +vi.setSystemTime(new Date('2023-01-01')); + +vi.mock('@sentry/utils', async () => ({ + ...(await vi.importActual('@sentry/utils')), + browserPerformanceTimeOrigin: new Date('2023-01-01').getTime(), })); import { WINDOW } from '../../../src/constants'; @@ -12,7 +15,7 @@ import { PerformanceEntryNavigation } from '../../fixtures/performanceEntry/navi describe('Unit | util | createPerformanceEntries', () => { beforeEach(function () { if (!WINDOW.performance.getEntriesByType) { - WINDOW.performance.getEntriesByType = jest.fn((type: string) => { + WINDOW.performance.getEntriesByType = vi.fn((type: string) => { if (type === 'navigation') { return [PerformanceEntryNavigation()]; } @@ -22,8 +25,8 @@ describe('Unit | util | createPerformanceEntries', () => { }); afterAll(function () { - jest.clearAllMocks(); - jest.useRealTimers(); + vi.clearAllMocks(); + vi.useRealTimers(); }); it('ignores sdks own requests', function () { diff --git a/packages/replay-internal/test/unit/util/debounce.test.ts b/packages/replay-internal/test/unit/util/debounce.test.ts index d52f9d456b49..59eb9e2a84fa 100644 --- a/packages/replay-internal/test/unit/util/debounce.test.ts +++ b/packages/replay-internal/test/unit/util/debounce.test.ts @@ -1,77 +1,77 @@ import { debounce } from '../../../src/util/debounce'; describe('Unit | util | debounce', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); it('delay the execution of the passed callback function by the passed minDelay', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(99); + vi.advanceTimersByTime(99); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); expect(callback).toHaveBeenCalled(); }); it('should invoke the callback at latest by maxWait, if the option is specified', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100, { maxWait: 150 }); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(98); + vi.advanceTimersByTime(98); expect(callback).not.toHaveBeenCalled(); debouncedCallback(); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(49); + vi.advanceTimersByTime(49); // at this time, the callback shouldn't be invoked and with a new call, it should be devounced further. debouncedCallback(); expect(callback).not.toHaveBeenCalled(); // But because the maxWait is reached, the callback should nevertheless be invoked. - jest.advanceTimersByTime(10); + vi.advanceTimersByTime(10); expect(callback).toHaveBeenCalled(); }); it('should not invoke the callback as long as it is debounced and no maxWait option is specified', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(99); + vi.advanceTimersByTime(99); expect(callback).not.toHaveBeenCalled(); debouncedCallback(); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(98); + vi.advanceTimersByTime(98); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(99); + vi.advanceTimersByTime(99); expect(callback).not.toHaveBeenCalled(); debouncedCallback(); - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); expect(callback).toHaveBeenCalled(); }); it('should invoke the callback as soon as callback.flush() is called', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100, { maxWait: 200 }); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(10); + vi.advanceTimersByTime(10); expect(callback).not.toHaveBeenCalled(); debouncedCallback.flush(); @@ -79,26 +79,26 @@ describe('Unit | util | debounce', () => { }); it('should not invoke the callback, if callback.cancel() is called', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100, { maxWait: 200 }); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(99); + vi.advanceTimersByTime(99); expect(callback).not.toHaveBeenCalled(); // If the callback is canceled, it should not be invoked after the minwait debouncedCallback.cancel(); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); expect(callback).not.toHaveBeenCalled(); // And it should also not be invoked after the maxWait - jest.advanceTimersByTime(500); + vi.advanceTimersByTime(500); expect(callback).not.toHaveBeenCalled(); }); it("should return the callback's return value when calling callback.flush()", () => { - const callback = jest.fn().mockReturnValue('foo'); + const callback = vi.fn().mockReturnValue('foo'); const debouncedCallback = debounce(callback, 100); debouncedCallback(); @@ -108,7 +108,7 @@ describe('Unit | util | debounce', () => { }); it('should return the callbacks return value on subsequent calls of the debounced function', () => { - const callback = jest.fn().mockReturnValue('foo'); + const callback = vi.fn().mockReturnValue('foo'); const debouncedCallback = debounce(callback, 100); const returnValue1 = debouncedCallback(); @@ -116,7 +116,7 @@ describe('Unit | util | debounce', () => { expect(callback).not.toHaveBeenCalled(); // now we expect the callback to have been invoked - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(callback).toHaveBeenCalledTimes(1); // calling the debounced function now should return the return value of the callback execution @@ -125,13 +125,13 @@ describe('Unit | util | debounce', () => { expect(callback).toHaveBeenCalledTimes(1); // and the callback should also be invoked again - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(callback).toHaveBeenCalledTimes(2); }); it('should handle return values of consecutive invocations without maxWait', () => { let i = 0; - const callback = jest.fn().mockImplementation(() => { + const callback = vi.fn().mockImplementation(() => { return `foo-${++i}`; }); const debouncedCallback = debounce(callback, 100); @@ -141,7 +141,7 @@ describe('Unit | util | debounce', () => { expect(callback).not.toHaveBeenCalled(); // now we expect the callback to have been invoked - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(callback).toHaveBeenCalledTimes(1); // calling the debounced function now should return the return value of the callback execution @@ -149,13 +149,13 @@ describe('Unit | util | debounce', () => { expect(returnValue1).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); const returnValue2 = debouncedCallback(); expect(returnValue2).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); // and the callback should also be invoked again - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); const returnValue3 = debouncedCallback(); expect(returnValue3).toBe('foo-2'); expect(callback).toHaveBeenCalledTimes(2); @@ -163,7 +163,7 @@ describe('Unit | util | debounce', () => { it('should handle return values of consecutive invocations with maxWait', () => { let i = 0; - const callback = jest.fn().mockImplementation(() => { + const callback = vi.fn().mockImplementation(() => { return `foo-${++i}`; }); const debouncedCallback = debounce(callback, 150, { maxWait: 200 }); @@ -173,26 +173,26 @@ describe('Unit | util | debounce', () => { expect(callback).not.toHaveBeenCalled(); // now we expect the callback to have been invoked - jest.advanceTimersByTime(149); + vi.advanceTimersByTime(149); const returnValue1 = debouncedCallback(); expect(returnValue1).toBe(undefined); expect(callback).not.toHaveBeenCalled(); // calling the debounced function now should return the return value of the callback execution // as it was executed because of maxWait - jest.advanceTimersByTime(51); + vi.advanceTimersByTime(51); const returnValue2 = debouncedCallback(); expect(returnValue2).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); // at this point (100ms after the last debounce call), nothing should have happened - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); const returnValue3 = debouncedCallback(); expect(returnValue3).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); // and the callback should now have been invoked again - jest.advanceTimersByTime(150); + vi.advanceTimersByTime(150); const returnValue4 = debouncedCallback(); expect(returnValue4).toBe('foo-2'); expect(callback).toHaveBeenCalledTimes(2); @@ -200,7 +200,7 @@ describe('Unit | util | debounce', () => { it('should handle return values of consecutive invocations after a cancellation', () => { let i = 0; - const callback = jest.fn().mockImplementation(() => { + const callback = vi.fn().mockImplementation(() => { return `foo-${++i}`; }); const debouncedCallback = debounce(callback, 150, { maxWait: 200 }); @@ -210,7 +210,7 @@ describe('Unit | util | debounce', () => { expect(callback).not.toHaveBeenCalled(); // now we expect the callback to have been invoked - jest.advanceTimersByTime(149); + vi.advanceTimersByTime(149); const returnValue1 = debouncedCallback(); expect(returnValue1).toBe(undefined); expect(callback).not.toHaveBeenCalled(); @@ -218,20 +218,20 @@ describe('Unit | util | debounce', () => { debouncedCallback.cancel(); // calling the debounced function now still return undefined because we cancelled the invocation - jest.advanceTimersByTime(51); + vi.advanceTimersByTime(51); const returnValue2 = debouncedCallback(); expect(returnValue2).toBe(undefined); expect(callback).not.toHaveBeenCalled(); // and the callback should also be invoked again - jest.advanceTimersByTime(150); + vi.advanceTimersByTime(150); const returnValue3 = debouncedCallback(); expect(returnValue3).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); }); it('should handle the return value of calling flush after cancelling', () => { - const callback = jest.fn().mockReturnValue('foo'); + const callback = vi.fn().mockReturnValue('foo'); const debouncedCallback = debounce(callback, 100); debouncedCallback(); @@ -242,30 +242,30 @@ describe('Unit | util | debounce', () => { }); it('should handle equal wait and maxWait values and only invoke func once', () => { - const callback = jest.fn().mockReturnValue('foo'); + const callback = vi.fn().mockReturnValue('foo'); const debouncedCallback = debounce(callback, 100, { maxWait: 100 }); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); expect(callback).toHaveBeenCalledTimes(1); const retval = debouncedCallback(); expect(retval).toBe('foo'); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); expect(callback).toHaveBeenCalledTimes(2); }); diff --git a/packages/replay-internal/test/unit/util/getPrivacyOptions.test.ts b/packages/replay-internal/test/unit/util/getPrivacyOptions.test.ts index e6d707ef879c..536ee7d0df21 100644 --- a/packages/replay-internal/test/unit/util/getPrivacyOptions.test.ts +++ b/packages/replay-internal/test/unit/util/getPrivacyOptions.test.ts @@ -1,11 +1,13 @@ +import { afterEach, beforeEach, describe, vi } from 'vitest'; + import { getPrivacyOptions } from '../../../src/util/getPrivacyOptions'; describe('Unit | util | getPrivacyOptions', () => { beforeEach(() => { - jest.spyOn(console, 'warn').mockImplementation(() => {}); + vi.spyOn(console, 'warn').mockImplementation(() => {}); }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('has correct default options', () => { @@ -18,9 +20,9 @@ describe('Unit | util | getPrivacyOptions', () => { ignore: ['.custom-ignore'], }), ).toMatchInlineSnapshot(` - Object { - "blockSelector": ".custom-block,.sentry-block,[data-sentry-block],base[href=\\"/\\"]", - "ignoreSelector": ".custom-ignore,.sentry-ignore,[data-sentry-ignore],input[type=\\"file\\"]", + { + "blockSelector": ".custom-block,.sentry-block,[data-sentry-block],base[href="/"]", + "ignoreSelector": ".custom-ignore,.sentry-ignore,[data-sentry-ignore],input[type="file"]", "maskTextSelector": ".custom-mask,.sentry-mask,[data-sentry-mask]", "unblockSelector": ".custom-unblock", "unmaskTextSelector": ".custom-unmask", diff --git a/packages/replay-internal/test/unit/util/handleRecordingEmit.test.ts b/packages/replay-internal/test/unit/util/handleRecordingEmit.test.ts index 8e22c886a5eb..975762565e17 100644 --- a/packages/replay-internal/test/unit/util/handleRecordingEmit.test.ts +++ b/packages/replay-internal/test/unit/util/handleRecordingEmit.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance } from 'vitest'; + import { EventType } from '@sentry-internal/rrweb'; import { BASE_TIMESTAMP } from '../..'; @@ -12,11 +15,11 @@ useFakeTimers(); let optionsEvent: ReplayOptionFrameEvent; describe('Unit | util | handleRecordingEmit', () => { - let addEventMock: jest.SpyInstance; + let addEventMock: MockInstance; beforeEach(function () { - jest.setSystemTime(BASE_TIMESTAMP); - addEventMock = jest.spyOn(SentryAddEvent, 'addEventSync').mockImplementation(() => { + vi.setSystemTime(BASE_TIMESTAMP); + addEventMock = vi.spyOn(SentryAddEvent, 'addEventSync').mockImplementation(() => { return true; }); }); @@ -45,14 +48,12 @@ describe('Unit | util | handleRecordingEmit', () => { }; handler(event); - await new Promise(process.nextTick); expect(addEventMock).toBeCalledTimes(2); expect(addEventMock).toHaveBeenNthCalledWith(1, replay, event, true); expect(addEventMock).toHaveBeenLastCalledWith(replay, optionsEvent, false); handler(event); - await new Promise(process.nextTick); expect(addEventMock).toBeCalledTimes(3); expect(addEventMock).toHaveBeenLastCalledWith(replay, event, false); @@ -78,7 +79,6 @@ describe('Unit | util | handleRecordingEmit', () => { }; handler(event, true); - await new Promise(process.nextTick); // Called twice, once for event and once for settings on checkout only expect(addEventMock).toBeCalledTimes(2); @@ -86,10 +86,9 @@ describe('Unit | util | handleRecordingEmit', () => { expect(addEventMock).toHaveBeenLastCalledWith(replay, optionsEvent, false); handler(event, true); - await new Promise(process.nextTick); expect(addEventMock).toBeCalledTimes(4); expect(addEventMock).toHaveBeenNthCalledWith(3, replay, event, true); - expect(addEventMock).toHaveBeenLastCalledWith(replay, { ...optionsEvent, timestamp: BASE_TIMESTAMP + 20 }, false); + expect(addEventMock).toHaveBeenLastCalledWith(replay, { ...optionsEvent, timestamp: BASE_TIMESTAMP }, false); }); }); diff --git a/packages/replay-internal/test/unit/util/isSampled.test.ts b/packages/replay-internal/test/unit/util/isSampled.test.ts index 97a824cb0e9d..6dfd163c45c7 100644 --- a/packages/replay-internal/test/unit/util/isSampled.test.ts +++ b/packages/replay-internal/test/unit/util/isSampled.test.ts @@ -14,7 +14,7 @@ const cases: [number, number, boolean][] = [ ]; describe('Unit | util | isSampled', () => { - const mockRandom = jest.spyOn(Math, 'random'); + const mockRandom = vi.spyOn(Math, 'random'); test.each(cases)( 'given sample rate of %p and RNG returns %p, result should be %p', diff --git a/packages/replay-internal/test/unit/util/prepareReplayEvent.test.ts b/packages/replay-internal/test/unit/util/prepareReplayEvent.test.ts index ac4ae503d10e..78c2c457a02d 100644 --- a/packages/replay-internal/test/unit/util/prepareReplayEvent.test.ts +++ b/packages/replay-internal/test/unit/util/prepareReplayEvent.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { getClient, getCurrentScope, setCurrentClient } from '@sentry/core'; import type { ReplayEvent } from '@sentry/types'; @@ -11,7 +13,7 @@ describe('Unit | util | prepareReplayEvent', () => { setCurrentClient(client); client.init(); - jest.spyOn(client, 'getSdkMetadata').mockImplementation(() => { + vi.spyOn(client, 'getSdkMetadata').mockImplementation(() => { return { sdk: { name: 'sentry.javascript.testSdk', @@ -22,7 +24,7 @@ describe('Unit | util | prepareReplayEvent', () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('works', async () => { diff --git a/packages/replay-internal/test/unit/util/throttle.test.ts b/packages/replay-internal/test/unit/util/throttle.test.ts index a242bde73398..797558dea904 100644 --- a/packages/replay-internal/test/unit/util/throttle.test.ts +++ b/packages/replay-internal/test/unit/util/throttle.test.ts @@ -1,14 +1,14 @@ import { BASE_TIMESTAMP } from '../..'; import { SKIPPED, THROTTLED, throttle } from '../../../src/util/throttle'; -jest.useFakeTimers(); +vi.useFakeTimers(); describe('Unit | util | throttle', () => { it('executes when not hitting the limit', () => { const now = BASE_TIMESTAMP; - jest.setSystemTime(now); + vi.setSystemTime(now); - const callback = jest.fn(); + const callback = vi.fn(); const fn = throttle(callback, 100, 60); fn(); @@ -17,20 +17,20 @@ describe('Unit | util | throttle', () => { expect(callback).toHaveBeenCalledTimes(3); - jest.advanceTimersByTime(59_000); + vi.advanceTimersByTime(59_000); fn(); fn(); expect(callback).toHaveBeenCalledTimes(5); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); fn(); expect(callback).toHaveBeenCalledTimes(6); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); fn(); @@ -39,8 +39,8 @@ describe('Unit | util | throttle', () => { it('stops executing when hitting the limit', () => { const now = BASE_TIMESTAMP; - jest.setSystemTime(now); - const callback = jest.fn(); + vi.setSystemTime(now); + const callback = vi.fn(); const fn = throttle(callback, 5, 60); fn(); @@ -49,14 +49,14 @@ describe('Unit | util | throttle', () => { expect(callback).toHaveBeenCalledTimes(3); - jest.advanceTimersByTime(59_000); + vi.advanceTimersByTime(59_000); fn(); fn(); expect(callback).toHaveBeenCalledTimes(5); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); fn(); fn(); @@ -66,7 +66,7 @@ describe('Unit | util | throttle', () => { expect(callback).toHaveBeenCalledTimes(5); // Now, the first three will "expire", so we can add three more - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); fn(); fn(); @@ -78,9 +78,9 @@ describe('Unit | util | throttle', () => { it('has correct return value', () => { const now = BASE_TIMESTAMP; - jest.setSystemTime(now); + vi.setSystemTime(now); - const callback = jest.fn(() => 'foo'); + const callback = vi.fn(() => 'foo'); const fn = throttle(callback, 5, 60); expect(fn()).toBe('foo'); @@ -93,7 +93,7 @@ describe('Unit | util | throttle', () => { expect(fn()).toBe(SKIPPED); // After 61s, should be reset - jest.advanceTimersByTime(61_000); + vi.advanceTimersByTime(61_000); expect(fn()).toBe('foo'); expect(fn()).toBe('foo'); @@ -107,10 +107,10 @@ describe('Unit | util | throttle', () => { it('passes args correctly', () => { const now = BASE_TIMESTAMP; - jest.setSystemTime(now); + vi.setSystemTime(now); const originalFunction = (a: number, b: number) => a + b; - const callback = jest.fn(originalFunction); + const callback = vi.fn(originalFunction); const fn = throttle(callback, 5, 60); expect(fn(1, 2)).toBe(3); diff --git a/packages/replay-internal/test/utils/TestClient.ts b/packages/replay-internal/test/utils/TestClient.ts index 26a14f2a9795..f95eb5309bcb 100644 --- a/packages/replay-internal/test/utils/TestClient.ts +++ b/packages/replay-internal/test/utils/TestClient.ts @@ -20,10 +20,8 @@ export class TestClient extends BaseClient { exception: { values: [ { - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ type: exception.name, value: exception.message, - /* eslint-enable @typescript-eslint/no-unsafe-member-access */ }, ], }, diff --git a/packages/replay-internal/test/utils/use-fake-timers.ts b/packages/replay-internal/test/utils/use-fake-timers.ts index ef7c92dc8101..57fda295cb59 100644 --- a/packages/replay-internal/test/utils/use-fake-timers.ts +++ b/packages/replay-internal/test/utils/use-fake-timers.ts @@ -1,14 +1,5 @@ -export function useFakeTimers(): void { - const _setInterval = setInterval; - const _clearInterval = clearInterval; - jest.useFakeTimers(); - - let interval: any; - beforeAll(function () { - interval = _setInterval(() => jest.advanceTimersByTime(20), 20); - }); +import { vi } from 'vitest'; - afterAll(function () { - _clearInterval(interval); - }); +export function useFakeTimers(): void { + vi.useFakeTimers(); } diff --git a/packages/replay-internal/tsconfig.test.json b/packages/replay-internal/tsconfig.test.json index ad87caa06c48..1e96fa6b8945 100644 --- a/packages/replay-internal/tsconfig.test.json +++ b/packages/replay-internal/tsconfig.test.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", - "include": ["test/**/*.ts", "jest.config.ts", "jest.setup.ts"], + "include": ["test/**/*.ts", "vitest.config.ts", "test.setup.ts"], "compilerOptions": { "types": ["node", "jest"], diff --git a/packages/replay-internal/vitest.config.ts b/packages/replay-internal/vitest.config.ts new file mode 100644 index 000000000000..9b4a2451c0c4 --- /dev/null +++ b/packages/replay-internal/vitest.config.ts @@ -0,0 +1,16 @@ +import type { UserConfig } from 'vitest'; +import { defineConfig } from 'vitest/config'; + +import baseConfig from '../../vite/vite.config'; + +export default defineConfig({ + ...baseConfig, + test: { + ...(baseConfig as UserConfig & { test: any }).test, + coverage: {}, + globals: true, + setupFiles: ['./test.setup.ts'], + reporters: ['default'], + environment: 'jsdom', + }, +}); diff --git a/yarn.lock b/yarn.lock index 5ee23d0231e0..e5b192cc750f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19711,6 +19711,16 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-docblock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" @@ -19764,6 +19774,11 @@ jest-get-type@^29.4.3: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + jest-haste-map@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" @@ -19825,6 +19840,16 @@ jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-matcher-utils@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-message-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" From c3c2bd319da0214aaeb269cdfbdf49ae56a187a2 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 8 May 2024 15:04:26 +0200 Subject: [PATCH 12/13] feat(node): Register ESM patching hooks in `init` for supported Node.js versions (#11933) --- .size-limit.js | 1 + .../suites/esm/warn-esm/test.ts | 22 +++- .../code/otelEsmImportHookTemplate.js | 3 + dev-packages/rollup-utils/npmHelpers.mjs | 5 + .../rollup-utils/plugins/npmPlugins.mjs | 13 +++ packages/nextjs/package.json | 2 +- packages/node/package.json | 2 +- packages/node/src/sdk/init.ts | 32 ++++-- packages/utils/src/worldwide.ts | 1 + yarn.lock | 107 ++++++++++++------ 10 files changed, 140 insertions(+), 48 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 9ead34df50f0..2e8e07f15971 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -206,6 +206,7 @@ module.exports = [ 'zlib', 'net', 'tls', + 'module', ], gzip: true, limit: '180 KB', diff --git a/dev-packages/node-integration-tests/suites/esm/warn-esm/test.ts b/dev-packages/node-integration-tests/suites/esm/warn-esm/test.ts index fa369a12db28..e52f8b915f86 100644 --- a/dev-packages/node-integration-tests/suites/esm/warn-esm/test.ts +++ b/dev-packages/node-integration-tests/suites/esm/warn-esm/test.ts @@ -5,9 +5,14 @@ afterAll(() => { }); const esmWarning = - '[Sentry] You are using the Sentry SDK with an ESM build. This version of the SDK is not compatible with ESM. Please either build your application with CommonJS, or use v7 of the SDK.'; + '[Sentry] You are using Node.js in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or use version 7.x of the Sentry Node.js SDK.'; + +test("warns if using ESM on Node.js versions that don't support `register()`", async () => { + const nodeMajorVersion = Number(process.versions.node.split('.')[0]); + if (nodeMajorVersion >= 18) { + return; + } -test('warns if using ESM', async () => { const runner = createRunner(__dirname, 'server.mjs').ignore('session', 'sessions', 'event').start(); await runner.makeRequest('get', '/test/success'); @@ -15,6 +20,19 @@ test('warns if using ESM', async () => { expect(runner.getLogs()).toContain(esmWarning); }); +test('does not warn if using ESM on Node.js versions that support `register()`', async () => { + const nodeMajorVersion = Number(process.versions.node.split('.')[0]); + if (nodeMajorVersion < 18) { + return; + } + + const runner = createRunner(__dirname, 'server.mjs').ignore('session', 'sessions', 'event').start(); + + await runner.makeRequest('get', '/test/success'); + + expect(runner.getLogs()).not.toContain(esmWarning); +}); + test('does not warn if using CJS', async () => { const runner = createRunner(__dirname, 'server.js').ignore('session', 'sessions', 'event').start(); diff --git a/dev-packages/rollup-utils/code/otelEsmImportHookTemplate.js b/dev-packages/rollup-utils/code/otelEsmImportHookTemplate.js index 99c312a20ab4..35c805c137f7 100644 --- a/dev-packages/rollup-utils/code/otelEsmImportHookTemplate.js +++ b/dev-packages/rollup-utils/code/otelEsmImportHookTemplate.js @@ -1,2 +1,5 @@ import { register } from 'module'; + register('@opentelemetry/instrumentation/hook.mjs', import.meta.url); + +globalThis._sentryEsmLoaderHookRegistered = true; diff --git a/dev-packages/rollup-utils/npmHelpers.mjs b/dev-packages/rollup-utils/npmHelpers.mjs index f984cbf518b9..0347c1d9e12b 100644 --- a/dev-packages/rollup-utils/npmHelpers.mjs +++ b/dev-packages/rollup-utils/npmHelpers.mjs @@ -1,3 +1,5 @@ +// @ts-check + /** * Rollup config docs: https://rollupjs.org/guide/en/#big-list-of-options */ @@ -14,6 +16,7 @@ import { makeCleanupPlugin, makeDebugBuildStatementReplacePlugin, makeExtractPolyfillsPlugin, + makeImportMetaUrlReplacePlugin, makeNodeResolvePlugin, makeRrwebBuildPlugin, makeSetSDKSourcePlugin, @@ -39,6 +42,7 @@ export function makeBaseNPMConfig(options = {}) { const nodeResolvePlugin = makeNodeResolvePlugin(); const sucrasePlugin = makeSucrasePlugin({ disableESTransforms: !addPolyfills, ...sucrase }); const debugBuildStatementReplacePlugin = makeDebugBuildStatementReplacePlugin(); + const importMetaUrlReplacePlugin = makeImportMetaUrlReplacePlugin(); const cleanupPlugin = makeCleanupPlugin(); const extractPolyfillsPlugin = makeExtractPolyfillsPlugin(); const setSdkSourcePlugin = makeSetSDKSourcePlugin('npm'); @@ -105,6 +109,7 @@ export function makeBaseNPMConfig(options = {}) { setSdkSourcePlugin, sucrasePlugin, debugBuildStatementReplacePlugin, + importMetaUrlReplacePlugin, rrwebBuildPlugin, cleanupPlugin, ], diff --git a/dev-packages/rollup-utils/plugins/npmPlugins.mjs b/dev-packages/rollup-utils/plugins/npmPlugins.mjs index 1c6f929c751c..89a0cfda7bb9 100644 --- a/dev-packages/rollup-utils/plugins/npmPlugins.mjs +++ b/dev-packages/rollup-utils/plugins/npmPlugins.mjs @@ -117,6 +117,19 @@ export function makeDebugBuildStatementReplacePlugin() { }); } +/** + * Because jest doesn't like `import.meta` statements but we still need it in the code base we instead use a magic + * string that we replace with import.meta.url in the build. + */ +export function makeImportMetaUrlReplacePlugin() { + return replace({ + preventAssignment: false, + values: { + __IMPORT_META_URL_REPLACEMENT__: 'import.meta.url', + }, + }); +} + /** * Creates a plugin to replace build flags of rrweb with either a constant (if passed true/false) or with a safe statement that: * a) evaluates to `true` diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index c02d6b5c6e3e..72ea287ba9f0 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -65,7 +65,7 @@ "access": "public" }, "dependencies": { - "@opentelemetry/instrumentation-http": "0.51.0", + "@opentelemetry/instrumentation-http": "0.51.1", "@rollup/plugin-commonjs": "24.0.0", "@sentry/core": "8.0.0-rc.1", "@sentry/node": "8.0.0-rc.1", diff --git a/packages/node/package.json b/packages/node/package.json index 0aa64d22fba7..24cbfcff103f 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -63,7 +63,7 @@ "@opentelemetry/instrumentation-fastify": "0.35.0", "@opentelemetry/instrumentation-graphql": "0.39.0", "@opentelemetry/instrumentation-hapi": "0.38.0", - "@opentelemetry/instrumentation-http": "0.51.0", + "@opentelemetry/instrumentation-http": "0.51.1", "@opentelemetry/instrumentation-ioredis": "0.40.0", "@opentelemetry/instrumentation-koa": "0.39.0", "@opentelemetry/instrumentation-mongodb": "0.39.0", diff --git a/packages/node/src/sdk/init.ts b/packages/node/src/sdk/init.ts index 07bb72208f59..7ce2616b8206 100644 --- a/packages/node/src/sdk/init.ts +++ b/packages/node/src/sdk/init.ts @@ -14,6 +14,7 @@ import { import { openTelemetrySetupCheck, setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import type { Client, Integration, Options } from '@sentry/types'; import { + GLOBAL_OBJ, consoleSandbox, dropUndefinedKeys, logger, @@ -25,6 +26,7 @@ import { consoleIntegration } from '../integrations/console'; import { nodeContextIntegration } from '../integrations/context'; import { contextLinesIntegration } from '../integrations/contextlines'; +import moduleModule from 'module'; import { httpIntegration } from '../integrations/http'; import { localVariablesIntegration } from '../integrations/local-variables'; import { modulesIntegration } from '../integrations/modules'; @@ -71,6 +73,8 @@ export function getDefaultIntegrations(options: Options): Integration[] { ]; } +declare const __IMPORT_META_URL_REPLACEMENT__: string; + /** * Initialize Sentry for Node. */ @@ -90,13 +94,27 @@ export function init(options: NodeOptions | undefined = {}): void { } if (!isCjs()) { - // We want to make sure users see this warning - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - '[Sentry] You are using the Sentry SDK with an ESM build. This version of the SDK is not compatible with ESM. Please either build your application with CommonJS, or use v7 of the SDK.', - ); - }); + const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(Number); + + // Register hook was added in v20.6.0 and v18.19.0 + if (nodeMajor >= 22 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) { + // We need to work around using import.meta.url directly because jest complains about it. + const importMetaUrl = + typeof __IMPORT_META_URL_REPLACEMENT__ !== 'undefined' ? __IMPORT_META_URL_REPLACEMENT__ : undefined; + + if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered && importMetaUrl) { + // @ts-expect-error register is available in these versions + moduleModule.register('@opentelemetry/instrumentation/hook.mjs', importMetaUrl); + GLOBAL_OBJ._sentryEsmLoaderHookRegistered = true; + } + } else { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[Sentry] You are using Node.js in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or use version 7.x of the Sentry Node.js SDK.', + ); + }); + } } setOpenTelemetryContextAsyncContextStrategy(); diff --git a/packages/utils/src/worldwide.ts b/packages/utils/src/worldwide.ts index 3f67a044a607..cc5241f00e0f 100644 --- a/packages/utils/src/worldwide.ts +++ b/packages/utils/src/worldwide.ts @@ -66,6 +66,7 @@ export interface InternalGlobal { * Keys are `error.stack` strings, values are the metadata. */ _sentryModuleMetadata?: Record; + _sentryEsmLoaderHookRegistered?: boolean; } /** Get's the global object for the current JavaScript runtime */ diff --git a/yarn.lock b/yarn.lock index e5b192cc750f..2fb2929b577f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6162,12 +6162,12 @@ dependencies: "@opentelemetry/semantic-conventions" "1.23.0" -"@opentelemetry/core@1.24.0": - version "1.24.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.24.0.tgz#5568b6c1328a6b9c94a77f9b2c7f872b852bba40" - integrity sha512-FP2oN7mVPqcdxJDTTnKExj4mi91EH+DNuArKfHTjPuJWe2K1JfMIVXNfahw1h3onJxQnxS8K0stKkogX05s+Aw== +"@opentelemetry/core@1.24.1", "@opentelemetry/core@^1.24.1": + version "1.24.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.24.1.tgz#35ab9d2ac9ca938e0ffbdfa40c49c169ac8ba80d" + integrity sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg== dependencies: - "@opentelemetry/semantic-conventions" "1.24.0" + "@opentelemetry/semantic-conventions" "1.24.1" "@opentelemetry/core@^0.12.0": version "0.12.0" @@ -6178,13 +6178,6 @@ "@opentelemetry/context-base" "^0.12.0" semver "^7.1.3" -"@opentelemetry/core@^1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.24.1.tgz#35ab9d2ac9ca938e0ffbdfa40c49c169ac8ba80d" - integrity sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg== - dependencies: - "@opentelemetry/semantic-conventions" "1.24.1" - "@opentelemetry/instrumentation-aws-lambda@0.40.0": version "0.40.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.40.0.tgz#5a54025a1eccb4f2f33115a006a6f8a7c6be4ad8" @@ -6250,14 +6243,14 @@ "@opentelemetry/instrumentation" "^0.51.0" "@opentelemetry/semantic-conventions" "^1.0.0" -"@opentelemetry/instrumentation-http@0.51.0": - version "0.51.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.51.0.tgz#f23fb24e2eed551859a14486893fe68ba6449de2" - integrity sha512-6VsGPBnU6iVKWhVBnuRpwrmiHfxt8EYrqfnH2glfsMpsn4xy+O6U0yGlggPLhoYeOVafV3h70EEk5MU0tpsbiw== +"@opentelemetry/instrumentation-http@0.51.1": + version "0.51.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.51.1.tgz#c450f01af42e44cfd1302a527dc391f09e8364c0" + integrity sha512-6b3nZnFFEz/3xZ6w8bVxctPUWIPWiXuPQ725530JgxnN1cvYFd8CJ75PrHZNjynmzSSnqBkN3ef4R9N+RpMh8Q== dependencies: - "@opentelemetry/core" "1.24.0" - "@opentelemetry/instrumentation" "0.51.0" - "@opentelemetry/semantic-conventions" "1.24.0" + "@opentelemetry/core" "1.24.1" + "@opentelemetry/instrumentation" "0.51.1" + "@opentelemetry/semantic-conventions" "1.24.1" semver "^7.5.2" "@opentelemetry/instrumentation-ioredis@0.40.0": @@ -6347,14 +6340,14 @@ semver "^7.5.2" shimmer "^1.2.1" -"@opentelemetry/instrumentation@0.51.0", "@opentelemetry/instrumentation@^0.51.0": - version "0.51.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.51.0.tgz#93dbe96c87da539081d0ccd07475cfc0b0c61233" - integrity sha512-Eg/+Od5bEvzpvZQGhvMyKIkrzB9S7jW+6z9LHEI2VXhl/GrqQ3oBqlzJt4tA6pGtxRmqQWKWGM1wAbwDdW/gUA== +"@opentelemetry/instrumentation@0.51.1", "@opentelemetry/instrumentation@^0.51.1": + version "0.51.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.51.1.tgz#46fb2291150ec6923e50b2f094b9407bc726ca9b" + integrity sha512-JIrvhpgqY6437QIqToyozrUG1h5UhwHkaGK/WAX+fkrpyPtc+RO5FkRtUd9BH0MibabHHvqsnBGKfKVijbmp8w== dependencies: - "@opentelemetry/api-logs" "0.51.0" + "@opentelemetry/api-logs" "0.51.1" "@types/shimmer" "^1.0.2" - import-in-the-middle "1.7.1" + import-in-the-middle "1.7.4" require-in-the-middle "^7.1.1" semver "^7.5.2" shimmer "^1.2.1" @@ -6381,14 +6374,14 @@ semver "^7.5.2" shimmer "^1.2.1" -"@opentelemetry/instrumentation@^0.51.1": - version "0.51.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.51.1.tgz#46fb2291150ec6923e50b2f094b9407bc726ca9b" - integrity sha512-JIrvhpgqY6437QIqToyozrUG1h5UhwHkaGK/WAX+fkrpyPtc+RO5FkRtUd9BH0MibabHHvqsnBGKfKVijbmp8w== +"@opentelemetry/instrumentation@^0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.51.0.tgz#93dbe96c87da539081d0ccd07475cfc0b0c61233" + integrity sha512-Eg/+Od5bEvzpvZQGhvMyKIkrzB9S7jW+6z9LHEI2VXhl/GrqQ3oBqlzJt4tA6pGtxRmqQWKWGM1wAbwDdW/gUA== dependencies: - "@opentelemetry/api-logs" "0.51.1" + "@opentelemetry/api-logs" "0.51.0" "@types/shimmer" "^1.0.2" - import-in-the-middle "1.7.4" + import-in-the-middle "1.7.1" require-in-the-middle "^7.1.1" semver "^7.5.2" shimmer "^1.2.1" @@ -6449,11 +6442,6 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.23.0.tgz#627f2721b960fe586b7f72a07912cb7699f06eef" integrity sha512-MiqFvfOzfR31t8cc74CTP1OZfz7MbqpAnLCra8NqQoaHJX6ncIRTdYOQYBDQ2uFISDq0WY8Y9dDTWvsgzzBYRg== -"@opentelemetry/semantic-conventions@1.24.0": - version "1.24.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.0.tgz#f074db930a7feb4d64103a9a576c5fbad046fcac" - integrity sha512-yL0jI6Ltuz8R+Opj7jClGrul6pOoYrdfVmzQS4SITXRPH7I5IRZbrwe/6/v8v4WYMa6MYZG480S1+uc/IGfqsA== - "@opentelemetry/semantic-conventions@1.24.1": version "1.24.1" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz#d4bcebda1cb5146d47a2a53daaa7922f8e084dfb" @@ -8579,6 +8567,11 @@ resolved "https://registry.yarnpkg.com/@types/node-abi/-/node-abi-3.0.3.tgz#a8334d75fe45ccd4cdb2a6c1ae82540a7a76828c" integrity sha512-5oos6sivyXcDEuVC5oX3+wLwfgrGZu4NIOn826PGAjPCHsqp2zSPTGU7H1Tv+GZBOiDUY3nBXY1MdaofSEt4fw== +"@types/node-cron@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.11.tgz#70b7131f65038ae63cfe841354c8aba363632344" + integrity sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg== + "@types/node-fetch@^2.6.0": version "2.6.2" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" @@ -8594,6 +8587,13 @@ dependencies: "@types/node" "*" +"@types/node-schedule@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@types/node-schedule/-/node-schedule-2.1.7.tgz#79a1e61adc7bbf8d8eaabcef307e07d76cb40d82" + integrity sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA== + dependencies: + "@types/node" "*" + "@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "17.0.38" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947" @@ -13361,6 +13361,13 @@ critters@0.0.16: postcss "^8.3.7" pretty-bytes "^5.3.0" +cron-parser@^4.2.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5" + integrity sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q== + dependencies: + luxon "^3.2.1" + cron@^3.1.6: version "3.1.6" resolved "https://registry.yarnpkg.com/cron/-/cron-3.1.6.tgz#e7e1798a468e017c8d31459ecd7c2d088f97346c" @@ -21117,6 +21124,11 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== +long-timeout@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" + integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w== + long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -21219,7 +21231,7 @@ lunr@^2.3.8: resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== -luxon@~3.4.0: +luxon@^3.2.1, luxon@~3.4.0: version "3.4.4" resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== @@ -22952,6 +22964,13 @@ node-addon-api@^6.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== +node-cron@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.3.tgz#c4bc7173dd96d96c50bdb51122c64415458caff2" + integrity sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A== + dependencies: + uuid "8.3.2" + node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -23086,6 +23105,15 @@ node-releases@^2.0.6: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== +node-schedule@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-2.1.1.tgz#6958b2c5af8834954f69bb0a7a97c62b97185de3" + integrity sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ== + dependencies: + cron-parser "^4.2.0" + long-timeout "0.1.1" + sorted-array-functions "^1.3.0" + node-source-walk@^4.0.0, node-source-walk@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-4.2.0.tgz#c2efe731ea8ba9c03c562aa0a9d984e54f27bc2c" @@ -27604,6 +27632,11 @@ sort-package-json@^1.57.0: is-plain-obj "2.1.0" sort-object-keys "^1.1.3" +sorted-array-functions@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" + integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== + source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" From a4c30a2feae42346568fced87e7de0e9681aef67 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 8 May 2024 13:14:36 +0000 Subject: [PATCH 13/13] meta(changelog): Update changelog for 8.0.0-rc.2 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa3c4244d21..4769478ae150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,33 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.0.0-rc.2 + +### Important Changes + +- **feat(node): Register ESM patching hooks in init for supported Node.js versions** + +This release includes adds support for ESM when `Sentry.init()` is called within a module imported via the `--import` +Node.js flag: + +```sh +node --import ./your-file-with-sentry-init.mjs your-app.mjs +``` + +Note that the SDK only supports ESM for node versions `18.19.0` and above, and `20.6.0` above. + +### Other Changes + +- deps(node): Bump `@opentelemetry/core` to `1.24.1` and `@opentelemetry/instrumentation` to `0.51.1` (#11941) +- feat(connect): Warn if connect is not instrumented (#11936) +- feat(express): Warn if express is not instrumented (#11930) +- feat(fastify): Warn if fastify is not instrumented (#11917) +- feat(hapi): Warn if hapi is not instrumented (#11937) +- feat(koa): Warn if koa is not instrumented (#11931) +- fix(browser): Continuously record CLS web vital (#11934) +- fix(feedback): Pick user from any scope (#11928) +- fix(node): Fix cron instrumentation and add tests (#11811) + ## 8.0.0-rc.1 This release contains no changes and was done for technical purposes. This version is considered stable.