From c3b66fadcf0f3b900181f9cc4e523469af251935 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Wed, 29 May 2024 11:15:42 +0200 Subject: [PATCH 01/10] ref(react): Use WeakSet to keep track of navigation instrumented clients (#12273) --- packages/react/src/reactrouterv6.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index 1186c6388f57..7ac981f90d4c 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -47,7 +47,7 @@ let _createRoutesFromChildren: CreateRoutesFromChildren; let _matchRoutes: MatchRoutes; let _stripBasename: boolean = false; -const CLIENTS_WITH_INSTRUMENT_NAVIGATION: Client[] = []; +const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet(); interface ReactRouterOptions { useEffect: UseEffect; @@ -108,7 +108,7 @@ export function reactRouterV6BrowserTracingIntegration( } if (instrumentNavigation) { - CLIENTS_WITH_INSTRUMENT_NAVIGATION.push(client); + CLIENTS_WITH_INSTRUMENT_NAVIGATION.add(client); } }, }; @@ -222,7 +222,7 @@ function handleNavigation( const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename); const client = getClient(); - if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.includes(client)) { + if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.has(client)) { return; } From dc6bbec9705bdfae76cec405822ee3d37e3d05b3 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 29 May 2024 11:53:32 +0200 Subject: [PATCH 02/10] test: Fix flakey `inspector` test (#12274) Uses an alternative test method. Should reduce failures like [this](https://github.com/getsentry/sentry-javascript/actions/runs/9281632639/job/25538103763). --- .../LocalVariables/deny-inspector.mjs | 26 ++++++++++++------- .../suites/public-api/LocalVariables/test.ts | 10 +++---- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/deny-inspector.mjs b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/deny-inspector.mjs index 99323e91f0bc..16546a328a9a 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/deny-inspector.mjs +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/deny-inspector.mjs @@ -1,13 +1,21 @@ -import * as Sentry from '@sentry/node'; -import Hook from 'import-in-the-middle'; +import { register } from 'node:module'; -Hook((_, name) => { - if (name === 'inspector') { - throw new Error('No inspector!'); - } - if (name === 'node:inspector') { - throw new Error('No inspector!'); +const hookScript = Buffer.from(` + + `); + +register( + new URL(`data:application/javascript, +export async function resolve(specifier, context, nextResolve) { + if (specifier === 'node:inspector' || specifier === 'inspector') { + throw new Error('Should not use node:inspector module'); } -}); + + return nextResolve(specifier); +}`), + import.meta.url, +); + +const Sentry = await import('@sentry/node'); Sentry.init({}); diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts index 0ad4ddad7c5a..4c564bc7ac86 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts @@ -76,12 +76,10 @@ conditionalTest({ min: 18 })('LocalVariables integration', () => { .start(done); }); - test('Should not import inspector when not in use', done => { - createRunner(__dirname, 'deny-inspector.mjs') - .withFlags('--import=@sentry/node/import') - .ensureNoErrorOutput() - .ignore('session') - .start(done); + conditionalTest({ min: 19 })('Node v19+', () => { + test('Should not import inspector when not in use', done => { + createRunner(__dirname, 'deny-inspector.mjs').ensureNoErrorOutput().ignore('session').start(done); + }); }); test('Includes local variables for caught exceptions when enabled', done => { From a1725a17cb438b032d20c88b439cc8355d49a940 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 29 May 2024 12:20:50 +0200 Subject: [PATCH 03/10] ci: Add optional E2E tests for sending to Sentry (#12259) This adds a new E2E test `react-send-to-sentry` that is run optionally. For now this is the same as the old `standard-frontend-react` test - eventually we can/should update that test (and others) to stop sending to sentry. This test will run in a separate group that we do not block merging on when it fails. For now, there is one browser and one node test that checks that they send events successfully to Sentry - IMHO that should cover stuff decently for now. I also made the source maps debug ID test optional, as that inherently sends to Sentry. We can in follow ups get rid of all the event sending stuff from the remaining E2E tests. Part of https://github.com/getsentry/sentry-javascript/issues/11910 --- .github/workflows/build.yml | 85 +++++- .../node-express-send-to-sentry/.gitignore | 1 + .../node-express-send-to-sentry/.npmrc | 2 + .../node-express-send-to-sentry/package.json | 28 ++ .../playwright.config.mjs | 76 ++++++ .../node-express-send-to-sentry/src/app.ts | 85 ++++++ .../tests/send-to-sentry.test.ts | 45 ++++ .../node-express-send-to-sentry/tsconfig.json | 10 + .../react-send-to-sentry/.gitignore | 29 +++ .../react-send-to-sentry/.npmrc | 2 + .../react-send-to-sentry/package.json | 57 ++++ .../playwright.config.mjs | 69 +++++ .../react-send-to-sentry/public/index.html | 24 ++ .../react-send-to-sentry/src/globals.d.ts | 5 + .../react-send-to-sentry/src/index.tsx | 72 +++++ .../react-send-to-sentry/src/pages/Index.tsx | 25 ++ .../react-send-to-sentry/src/pages/User.tsx | 8 + .../src/react-app-env.d.ts | 1 + .../tests/fixtures/ReplayRecordingData.ts | 246 ++++++++++++++++++ .../tests/send-to-sentry.test.ts | 203 +++++++++++++++ .../react-send-to-sentry/tsconfig.json | 20 ++ 21 files changed, 1092 insertions(+), 1 deletion(-) create mode 100644 dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json create mode 100644 dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/src/app.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/tests/send-to-sentry.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/public/index.html create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/globals.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/index.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/pages/Index.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/pages/User.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/react-app-env.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 195e316581c3..ea3028c9fe5b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1001,7 +1001,6 @@ jobs: 'create-remix-app-v2', 'create-remix-app-express', 'create-remix-app-express-vite-dev', - 'debug-id-sourcemaps', 'node-express-esm-loader', 'node-express-esm-preload', 'node-express-esm-without-loader', @@ -1113,6 +1112,90 @@ jobs: directory: dist workingDirectory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + job_optional_e2e_tests: + name: E2E ${{ matrix.label || matrix.test-application }} Test + # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks + # We need to add the `always()` check here because the previous step has this as well :( + # See: https://github.com/actions/runner/issues/2205 + if: + always() && needs.job_e2e_prepare.result == 'success' && + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) + needs: [job_get_metadata, job_build, job_e2e_prepare] + runs-on: ubuntu-20.04 + timeout-minutes: 10 + env: + E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} + E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + # Needed because some apps expect a certain prefix + NEXT_PUBLIC_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + PUBLIC_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + REACT_APP_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks' + E2E_TEST_SENTRY_TEST_PROJECT: 'sentry-javascript-e2e-tests' + strategy: + fail-fast: false + matrix: + is_dependabot: + - ${{ github.actor == 'dependabot[bot]' }} + test-application: + [ + 'react-send-to-sentry', + 'node-express-send-to-sentry', + 'debug-id-sourcemaps', + ] + build-command: + - false + label: + - false + + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - uses: pnpm/action-setup@v2 + with: + version: 8.3.1 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'dev-packages/e2e-tests/package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + + - name: Restore tarball cache + uses: actions/cache/restore@v4 + with: + path: ${{ github.workspace }}/packages/*/*.tgz + key: ${{ env.BUILD_CACHE_TARBALL_KEY }} + + - name: Get node version + id: versions + run: | + echo "echo node=$(jq -r '.volta.node' dev-packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT + + - name: Validate Verdaccio + run: yarn test:validate + working-directory: dev-packages/e2e-tests + + - name: Prepare Verdaccio + run: yarn test:prepare + working-directory: dev-packages/e2e-tests + env: + E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }} + + - name: Build E2E app + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn ${{ matrix.build-command || 'test:build' }} + + - name: Run E2E test + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn test:assert + job_profiling_e2e_tests: name: E2E ${{ matrix.label || matrix.test-application }} Test # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/.gitignore b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/.gitignore @@ -0,0 +1 @@ +dist diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/.npmrc b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json new file mode 100644 index 000000000000..1a10d88b0bb8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json @@ -0,0 +1,28 @@ +{ + "name": "node-express-send-to-sentry-app", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "tsc", + "start": "node dist/app.js", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install && pnpm build", + "test:assert": "pnpm test" + }, + "dependencies": { + "@sentry/core": "latest || *", + "@sentry/node": "latest || *", + "@sentry/types": "latest || *", + "@types/express": "4.17.17", + "@types/node": "18.15.1", + "express": "4.19.2", + "typescript": "4.9.5" + }, + "devDependencies": { + "@playwright/test": "^1.27.1" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/playwright.config.mjs new file mode 100644 index 000000000000..4f4aacb2c47e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/playwright.config.mjs @@ -0,0 +1,76 @@ +import { devices } from '@playwright/test'; + +// Fix urls not resolving to localhost on Node v17+ +// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 +import { setDefaultResultOrder } from 'dns'; +setDefaultResultOrder('ipv4first'); + +const expressPort = 3030; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 150_000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: 0, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'list', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: `http://localhost:${expressPort}`, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + // For now we only test Chrome! + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // }, + // }, + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: [ + { + command: 'pnpm start', + port: expressPort, + }, + ], +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/src/app.ts new file mode 100644 index 000000000000..ca5d61f742d9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/src/app.ts @@ -0,0 +1,85 @@ +import * as Sentry from '@sentry/node'; + +let lastTransactionId: string | undefined; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + includeLocalVariables: true, + tracesSampleRate: 1, + beforeSendTransaction(event) { + lastTransactionId = event.event_id; + return event; + }, +}); + +import express from 'express'; + +const app = express(); +const port = 3030; + +app.get('/test-success', function (req, res) { + res.send({ version: 'v1' }); +}); + +app.get('/test-param/:param', function (req, res) { + res.send({ paramWas: req.params.param }); +}); + +app.get('/test-transaction', function (req, res) { + Sentry.withActiveSpan(null, async () => { + Sentry.startSpan({ name: 'test-transaction', op: 'e2e-test' }, () => { + Sentry.startSpan({ name: 'test-span' }, () => undefined); + }); + + await Sentry.flush(); + + res.send({ + transactionId: lastTransactionId, + }); + }); +}); + +app.get('/test-error', async function (req, res) { + const exceptionId = Sentry.captureException(new Error('This is an error')); + + await Sentry.flush(2000); + + res.send({ exceptionId }); +}); + +app.get('/test-exception/:id', function (req, _res) { + throw new Error(`This is an exception with id ${req.params.id}`); +}); + +app.get('/test-local-variables-uncaught', function (req, res) { + const randomVariableToRecord = Math.random(); + throw new Error(`Uncaught Local Variable Error - ${JSON.stringify({ randomVariableToRecord })}`); +}); + +app.get('/test-local-variables-caught', function (req, res) { + const randomVariableToRecord = Math.random(); + + let exceptionId: string; + try { + throw new Error('Local Variable Error'); + } catch (e) { + exceptionId = Sentry.captureException(e); + } + + res.send({ exceptionId, randomVariableToRecord }); +}); + +Sentry.setupExpressErrorHandler(app); + +// @ts-ignore +app.use(function onError(err, req, res, next) { + // The error id is attached to `res.sentry` to be returned + // and optionally displayed to the user for support. + res.statusCode = 500; + res.end(res.sentry + '\n'); +}); + +app.listen(port, () => { + console.log(`Example app listening on port ${port}`); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/tests/send-to-sentry.test.ts b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/tests/send-to-sentry.test.ts new file mode 100644 index 000000000000..230cda70eda8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/tests/send-to-sentry.test.ts @@ -0,0 +1,45 @@ +import { expect, test } from '@playwright/test'; + +const EVENT_POLLING_TIMEOUT = 90_000; + +const authToken = process.env.E2E_TEST_AUTH_TOKEN; +const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; +const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; + +test('Sends exception to Sentry', async ({ baseURL }) => { + const response = await fetch(`${baseURL}/test-error`); + const { exceptionId } = await response.json(); + + const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionId}/`; + + console.log(`Polling for error eventId: ${exceptionId}`); + + await expect + .poll( + async () => { + const response = await fetch(url, { headers: { Authorization: `Bearer ${authToken}` } }); + return response.status; + }, + { timeout: EVENT_POLLING_TIMEOUT }, + ) + .toBe(200); +}); + +test('Sends transaction to Sentry', async ({ baseURL }) => { + const response = await fetch(`${baseURL}/test-transaction`); + const { transactionId } = await response.json(); + + const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionId}/`; + + console.log(`Polling for transaction eventId: ${transactionId}`); + + await expect + .poll( + async () => { + const response = await fetch(url, { headers: { Authorization: `Bearer ${authToken}` } }); + return response.status; + }, + { timeout: EVENT_POLLING_TIMEOUT }, + ) + .toBe(200); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/tsconfig.json new file mode 100644 index 000000000000..8cb64e989ed9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["node"], + "esModuleInterop": true, + "lib": ["es2018"], + "strict": true, + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/.gitignore b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/.gitignore new file mode 100644 index 000000000000..84634c973eeb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/.gitignore @@ -0,0 +1,29 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +/test-results/ +/playwright-report/ +/playwright/.cache/ + +!*.d.ts diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/.npmrc b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json new file mode 100644 index 000000000000..092a5c0ac221 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json @@ -0,0 +1,57 @@ +{ + "name": "react-send-to-sentry-test", + "version": "0.1.0", + "private": true, + "dependencies": { + "@sentry/react": "latest || *", + "@testing-library/jest-dom": "5.14.1", + "@testing-library/react": "13.0.0", + "@testing-library/user-event": "13.2.1", + "@types/jest": "27.0.1", + "@types/node": "16.7.13", + "@types/react": "18.0.0", + "@types/react-dom": "18.0.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "^6.4.1", + "react-scripts": "5.0.1", + "typescript": "4.9.5", + "web-vitals": "2.1.0" + }, + "scripts": { + "build": "react-scripts build", + "start": "serve -s build", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:build-ts3.8": "pnpm install && pnpm add typescript@3.8 && npx playwright install && pnpm build", + "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && npx playwright install && pnpm build", + "test:assert": "pnpm test" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@playwright/test": "1.26.1", + "axios": "1.6.0", + "serve": "14.0.1" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/playwright.config.mjs new file mode 100644 index 000000000000..7d04e3b6dd4b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/playwright.config.mjs @@ -0,0 +1,69 @@ +import { devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 150_000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: 0, + /* Opt out of parallel tests on CI. */ + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'list', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + // For now we only test Chrome! + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // }, + // }, + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'pnpm start', + port: 3030, + env: { + PORT: '3030', + }, + }, +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/public/index.html b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/public/index.html new file mode 100644 index 000000000000..39da76522bea --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/public/index.html @@ -0,0 +1,24 @@ + + + + + + + + React App + + + +
+ + + diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/globals.d.ts b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/globals.d.ts new file mode 100644 index 000000000000..ffa61ca49acc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/globals.d.ts @@ -0,0 +1,5 @@ +interface Window { + recordedTransactions?: string[]; + capturedExceptionId?: string; + sentryReplayId?: string; +} diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/index.tsx new file mode 100644 index 000000000000..3a87a53ffdfa --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/index.tsx @@ -0,0 +1,72 @@ +import * as Sentry from '@sentry/react'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { + BrowserRouter, + Route, + Routes, + createRoutesFromChildren, + matchRoutes, + useLocation, + useNavigationType, +} from 'react-router-dom'; +import Index from './pages/Index'; +import User from './pages/User'; + +const replay = Sentry.replayIntegration(); + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.REACT_APP_E2E_TEST_DSN, + integrations: [ + Sentry.reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + replay, + ], + // We recommend adjusting this value in production, or using tracesSampler + // for finer control + tracesSampleRate: 1.0, + release: 'e2e-test', + + // Always capture replays, so we can test this properly + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, +}); + +Object.defineProperty(window, 'sentryReplayId', { + get() { + return replay['_replay'].session.id; + }, +}); + +Sentry.addEventProcessor(event => { + if ( + event.type === 'transaction' && + (event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation') + ) { + const eventId = event.event_id; + if (eventId) { + window.recordedTransactions = window.recordedTransactions || []; + window.recordedTransactions.push(eventId); + } + } + + return event; +}); + +const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( + + + } /> + } /> + + , +); diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/pages/Index.tsx new file mode 100644 index 000000000000..7789a2773224 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/pages/Index.tsx @@ -0,0 +1,25 @@ +import * as Sentry from '@sentry/react'; +// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX +import * as React from 'react'; +import { Link } from 'react-router-dom'; + +const Index = () => { + return ( + <> + { + const eventId = Sentry.captureException(new Error('I am an error!')); + window.capturedExceptionId = eventId; + }} + /> + + navigate + + + ); +}; + +export default Index; diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/pages/User.tsx b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/pages/User.tsx new file mode 100644 index 000000000000..62f0c2d17533 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/pages/User.tsx @@ -0,0 +1,8 @@ +// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX +import * as React from 'react'; + +const User = () => { + return

I am a blank page :)

; +}; + +export default User; diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/react-app-env.d.ts b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/react-app-env.d.ts new file mode 100644 index 000000000000..6431bc5fc6b2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts new file mode 100644 index 000000000000..0b454ba12214 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts @@ -0,0 +1,246 @@ +import { expect } from '@playwright/test'; + +export const ReplayRecordingData = [ + { + type: 4, + data: { href: expect.stringMatching(/http:\/\/localhost:\d+\//), width: 1280, height: 720 }, + timestamp: expect.any(Number), + }, + { + data: { + payload: { + blockAllMedia: true, + errorSampleRate: 0, + maskAllInputs: true, + maskAllText: true, + networkCaptureBodies: true, + networkDetailHasUrls: false, + networkRequestHasHeaders: true, + networkResponseHasHeaders: true, + sessionSampleRate: 1, + shouldRecordCanvas: false, + useCompression: false, + useCompressionOption: true, + }, + tag: 'options', + }, + timestamp: expect.any(Number), + type: 5, + }, + { + type: 2, + data: { + node: { + type: 0, + childNodes: [ + { type: 1, name: 'html', publicId: '', systemId: '', id: 2 }, + { + type: 2, + tagName: 'html', + attributes: { lang: 'en' }, + childNodes: [ + { + type: 2, + tagName: 'head', + attributes: {}, + childNodes: [ + { type: 2, tagName: 'meta', attributes: { charset: 'utf-8' }, childNodes: [], id: 5 }, + { + type: 2, + tagName: 'meta', + attributes: { name: 'viewport', content: 'width=device-width,initial-scale=1' }, + childNodes: [], + id: 6, + }, + { + type: 2, + tagName: 'meta', + attributes: { name: 'theme-color', content: '#000000' }, + childNodes: [], + id: 7, + }, + { + type: 2, + tagName: 'title', + attributes: {}, + childNodes: [{ type: 3, textContent: '***** ***', id: 9 }], + id: 8, + }, + ], + id: 4, + }, + { + type: 2, + tagName: 'body', + attributes: {}, + childNodes: [ + { + type: 2, + tagName: 'noscript', + attributes: {}, + childNodes: [{ type: 3, textContent: '*** **** ** ****** ********** ** *** **** ****', id: 12 }], + id: 11, + }, + { type: 2, tagName: 'div', attributes: { id: 'root' }, childNodes: [], id: 13 }, + ], + id: 10, + }, + ], + id: 3, + }, + ], + id: 1, + }, + initialOffset: { left: 0, top: 0 }, + }, + timestamp: expect.any(Number), + }, + { + type: 3, + data: { + source: 0, + texts: [], + attributes: [], + removes: [], + adds: [ + { + parentId: 13, + nextId: null, + node: { + type: 2, + tagName: 'a', + attributes: { id: 'navigation', href: expect.stringMatching(/http:\/\/localhost:\d+\/user\/5/) }, + childNodes: [], + id: 14, + }, + }, + { parentId: 14, nextId: null, node: { type: 3, textContent: '********', id: 15 } }, + { + parentId: 13, + nextId: 14, + node: { + type: 2, + tagName: 'input', + attributes: { type: 'button', id: 'exception-button', value: '******* *********' }, + childNodes: [], + id: 16, + }, + }, + ], + }, + timestamp: expect.any(Number), + }, + { + type: 3, + data: { source: 5, text: 'Capture Exception', isChecked: false, id: 16 }, + timestamp: expect.any(Number), + }, + { + type: 5, + timestamp: expect.any(Number), + data: { + tag: 'performanceSpan', + payload: { + op: 'navigation.navigate', + description: expect.stringMatching(/http:\/\/localhost:\d+\//), + startTimestamp: expect.any(Number), + endTimestamp: expect.any(Number), + data: { + decodedBodySize: expect.any(Number), + encodedBodySize: expect.any(Number), + duration: expect.any(Number), + domInteractive: expect.any(Number), + domContentLoadedEventEnd: expect.any(Number), + domContentLoadedEventStart: expect.any(Number), + loadEventStart: expect.any(Number), + loadEventEnd: expect.any(Number), + domComplete: expect.any(Number), + redirectCount: expect.any(Number), + size: expect.any(Number), + }, + }, + }, + }, + { + type: 5, + timestamp: expect.any(Number), + data: { + tag: 'performanceSpan', + payload: { + op: 'resource.script', + description: expect.stringMatching(/http:\/\/localhost:\d+\/static\/js\/main.(\w+).js/), + startTimestamp: expect.any(Number), + endTimestamp: expect.any(Number), + data: { + decodedBodySize: expect.any(Number), + encodedBodySize: expect.any(Number), + size: expect.any(Number), + }, + }, + }, + }, + { + type: 5, + timestamp: expect.any(Number), + data: { + tag: 'performanceSpan', + payload: { + op: 'paint', + description: 'first-paint', + startTimestamp: expect.any(Number), + endTimestamp: expect.any(Number), + }, + }, + }, + { + type: 5, + timestamp: expect.any(Number), + data: { + tag: 'performanceSpan', + payload: { + op: 'paint', + description: 'first-contentful-paint', + startTimestamp: expect.any(Number), + endTimestamp: expect.any(Number), + }, + }, + }, + { + type: 5, + timestamp: expect.any(Number), + data: { + tag: 'performanceSpan', + payload: { + op: 'largest-contentful-paint', + description: 'largest-contentful-paint', + startTimestamp: expect.any(Number), + endTimestamp: expect.any(Number), + data: { + value: expect.any(Number), + size: expect.any(Number), + nodeId: 16, + }, + }, + }, + }, + { + type: 5, + timestamp: expect.any(Number), + data: { + tag: 'performanceSpan', + payload: { + op: 'memory', + description: 'memory', + startTimestamp: expect.any(Number), + endTimestamp: expect.any(Number), + data: { + memory: { + jsHeapSizeLimit: expect.any(Number), + totalJSHeapSize: expect.any(Number), + usedJSHeapSize: expect.any(Number), + }, + }, + }, + }, + }, +]; diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts new file mode 100644 index 000000000000..8e61940e574c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts @@ -0,0 +1,203 @@ +import { expect, test } from '@playwright/test'; +import { ReplayRecordingData } from './fixtures/ReplayRecordingData'; + +const EVENT_POLLING_TIMEOUT = 90_000; + +const authToken = process.env.E2E_TEST_AUTH_TOKEN; +const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; +const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; + +test('Sends an exception to Sentry', async ({ page }) => { + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const exceptionIdHandle = await page.waitForFunction(() => window.capturedExceptionId); + const exceptionEventId = await exceptionIdHandle.jsonValue(); + + console.log(`Polling for error eventId: ${exceptionEventId}`); + + await expect + .poll( + async () => { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + return response.status; + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); +}); + +test('Sends a pageload transaction to Sentry', async ({ page }) => { + await page.goto('/'); + + const recordedTransactionsHandle = await page.waitForFunction(() => { + if (window.recordedTransactions && window.recordedTransactions?.length >= 1) { + return window.recordedTransactions; + } else { + return undefined; + } + }); + const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue(); + + if (recordedTransactionEventIds === undefined) { + throw new Error("Application didn't record any transaction event IDs."); + } + + let hadPageLoadTransaction = false; + + console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`); + + await Promise.all( + recordedTransactionEventIds.map(async transactionEventId => { + await expect + .poll( + async () => { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + + if (data.contexts.trace.op === 'pageload') { + hadPageLoadTransaction = true; + } + } + + return response.status; + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); + }), + ); + + expect(hadPageLoadTransaction).toBe(true); +}); + +test('Sends a navigation transaction to Sentry', async ({ page }) => { + await page.goto('/'); + + // Give pageload transaction time to finish + page.waitForTimeout(4000); + + const linkElement = page.locator('id=navigation'); + await linkElement.click(); + + const recordedTransactionsHandle = await page.waitForFunction(() => { + if (window.recordedTransactions && window.recordedTransactions?.length >= 2) { + return window.recordedTransactions; + } else { + return undefined; + } + }); + const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue(); + + if (recordedTransactionEventIds === undefined) { + throw new Error("Application didn't record any transaction event IDs."); + } + + let hadPageNavigationTransaction = false; + + console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`); + + await Promise.all( + recordedTransactionEventIds.map(async transactionEventId => { + await expect + .poll( + async () => { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'navigation') { + hadPageNavigationTransaction = true; + } + } + + return response.status; + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); + }), + ); + + expect(hadPageNavigationTransaction).toBe(true); +}); + +test('Sends a Replay recording to Sentry', async ({ browser }) => { + const context = await browser.newContext(); + const page = await context.newPage(); + + await page.goto('/'); + + const replayId = await page.waitForFunction(() => { + return window.sentryReplayId; + }); + + // Keypress event ensures LCP is finished + await page.type('body', 'Y'); + + // Wait for replay to be sent + + if (replayId === undefined) { + throw new Error("Application didn't set a replayId"); + } + + console.log(`Polling for replay with ID: ${replayId}`); + + await expect + .poll( + async () => { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + return response.status; + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); + + // now fetch the first recording segment + await expect + .poll( + async () => { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/recording-segments/?cursor=100%3A0%3A1`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + return data[0]; + } + + return response.status; + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toEqual(ReplayRecordingData); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tsconfig.json new file mode 100644 index 000000000000..4cc95dc2689a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es2018", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src", "tests"] +} From 91f677608f91e847af1528facabd0e82e42bf314 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 29 May 2024 13:27:46 +0200 Subject: [PATCH 04/10] test(e2e): Remove axios from E2E tests (#12275) No need for it anymore, and actually it is much simpler using fetch because that does not throw when encountering a 404 error! --- .../angular-17/playwright.config.ts | 5 - .../angular-18/playwright.config.ts | 5 - .../create-next-app/playwright.config.ts | 5 - .../tests/behaviour-client.test.ts | 83 ++++------- .../tests/behaviour-server.test.ts | 35 +---- .../tests/behaviour-client.test.ts | 82 ++++------- .../tests/behaviour-client.test.ts | 104 ++++--------- .../tests/behaviour-client.test.ts | 104 ++++--------- .../tests/behaviour-client.test.ts | 104 ++++--------- .../nextjs-14/playwright.config.ts | 5 - .../nextjs-15/playwright.config.ts | 5 - .../nextjs-app-dir/playwright.config.ts | 5 - .../nextjs-app-dir/tests/exceptions.test.ts | 24 +-- .../tests/server-components.test.ts | 24 +-- .../nextjs-app-dir/tests/transactions.test.ts | 24 +-- .../playwright.config.mjs | 5 - .../playwright.config.ts | 5 - .../playwright.config.mjs | 5 - .../playwright.config.ts | 5 - .../node-express/playwright.config.ts | 5 - .../react-create-hash-router/package.json | 1 - .../tests/behaviour-test.spec.ts | 137 ++++++------------ .../react-router-6-use-routes/package.json | 1 - .../tests/behaviour-test.test.ts | 131 +++++------------ .../standard-frontend-react/package.json | 1 - .../tests/behaviour-test.spec.ts | 131 +++++------------ .../svelte-5/playwright.config.ts | 5 - .../sveltekit-2-svelte-5/playwright.config.ts | 5 - .../sveltekit-2/playwright.config.ts | 5 - .../vue-3/playwright.config.ts | 5 - .../webpack-4/tests/behaviour-test.spec.ts | 23 +-- .../webpack-5/tests/behaviour-test.spec.ts | 23 +-- 32 files changed, 294 insertions(+), 813 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/angular-17/playwright.config.ts b/dev-packages/e2e-tests/test-applications/angular-17/playwright.config.ts index df7c2d9758e9..b2ee35fdc4c7 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/angular-17/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env['TEST_ENV'] || 'production'; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts b/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts index df7c2d9758e9..b2ee35fdc4c7 100644 --- a/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env['TEST_ENV'] || 'production'; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.ts b/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.ts index 7c84f56d178f..b29068c3141c 100644 --- a/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env.TEST_ENV; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts b/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts index d994f13b7060..022cfd1cb5a6 100644 --- a/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; const authToken = process.env.E2E_TEST_AUTH_TOKEN; const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; @@ -20,24 +19,12 @@ test('Sends a client-side exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -71,28 +58,19 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'pageload') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'pageload') { hadPageLoadTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -136,28 +114,19 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'navigation') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'navigation') { hadPageNavigationTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts b/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts index 6b126c3e8130..0a5fd828f55d 100644 --- a/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; const authToken = process.env.E2E_TEST_AUTH_TOKEN; const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; @@ -18,21 +17,8 @@ test('Sends a server-side exception to Sentry', async ({ baseURL }) => { await expect .poll( async () => { - try { - const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } }); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch(url, { headers: { Authorization: `Bearer ${authToken}` } }); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT }, ) @@ -53,21 +39,8 @@ test('Sends server-side transactions to Sentry', async ({ baseURL }) => { await expect .poll( async () => { - try { - const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } }); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch(url, { headers: { Authorization: `Bearer ${authToken}` } }); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT }, ) diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-client.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-client.test.ts index 2141f434708c..76d2a49a5afb 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-client.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-client.test.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; const EVENT_POLLING_TIMEOUT = 90_000; @@ -21,24 +20,11 @@ test('Sends a client-side exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -72,28 +58,19 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'pageload') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'pageload') { hadPageLoadTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -137,28 +114,19 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'navigation') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'navigation') { hadPageNavigationTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/behaviour-client.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/behaviour-client.test.ts index 944dcb07b4bd..5c347620701c 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/behaviour-client.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/behaviour-client.test.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; const EVENT_POLLING_TIMEOUT = 90_000; @@ -21,24 +20,11 @@ test('Sends a client-side exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -72,28 +58,19 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'pageload') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'pageload') { hadPageLoadTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -137,28 +114,19 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'navigation') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'navigation') { hadPageNavigationTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -182,23 +150,11 @@ test('Sends a client-side ErrorBoundary exception to Sentry', async ({ page }) = await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-client.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-client.test.ts index 944dcb07b4bd..5c347620701c 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-client.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-client.test.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; const EVENT_POLLING_TIMEOUT = 90_000; @@ -21,24 +20,11 @@ test('Sends a client-side exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -72,28 +58,19 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'pageload') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'pageload') { hadPageLoadTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -137,28 +114,19 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'navigation') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'navigation') { hadPageNavigationTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -182,23 +150,11 @@ test('Sends a client-side ErrorBoundary exception to Sentry', async ({ page }) = await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-client.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-client.test.ts index 944dcb07b4bd..5c347620701c 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-client.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-client.test.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; const EVENT_POLLING_TIMEOUT = 90_000; @@ -21,24 +20,11 @@ test('Sends a client-side exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -72,28 +58,19 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'pageload') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'pageload') { hadPageLoadTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -137,28 +114,19 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'navigation') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'navigation') { hadPageNavigationTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -182,23 +150,11 @@ test('Sends a client-side ErrorBoundary exception to Sentry', async ({ page }) = await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/playwright.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/playwright.config.ts index aedfbc9d6c61..bf1fc94f4292 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/playwright.config.ts @@ -2,11 +2,6 @@ import os from 'os'; import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env.TEST_ENV; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.ts index 0709f27158b4..ab02599b8f4f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.ts @@ -2,11 +2,6 @@ import os from 'os'; import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env.TEST_ENV; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts index 0d21b775285a..a6fbfdc882cf 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts @@ -2,11 +2,6 @@ import os from 'os'; import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env.TEST_ENV; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/exceptions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/exceptions.test.ts index b47df261ceec..f4cde4417d13 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/exceptions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/exceptions.test.ts @@ -1,6 +1,5 @@ import { expect, test } from '@playwright/test'; import { waitForError } from '@sentry-internal/event-proxy-server'; -import axios, { AxiosError } from 'axios'; const authToken = process.env.E2E_TEST_AUTH_TOKEN; const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; @@ -24,24 +23,11 @@ test('Sends a client-side exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts index 309d9c4b9fd4..7dbb1cc2281f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts @@ -1,6 +1,5 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForTransaction } from '@sentry-internal/event-proxy-server'; -import axios, { AxiosError } from 'axios'; const authToken = process.env.E2E_TEST_AUTH_TOKEN; const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; @@ -28,24 +27,11 @@ test('Sends a transaction for a server component', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts index 5f7d4dc8496d..f42922668e75 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts @@ -1,6 +1,5 @@ import { expect, test } from '@playwright/test'; import { waitForTransaction } from '@sentry-internal/event-proxy-server'; -import axios, { AxiosError } from 'axios'; const packageJson = require('../package.json'); @@ -22,24 +21,11 @@ test('Sends a pageload transaction', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/playwright.config.mjs index 59b8f10d691b..847f270e5f32 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/playwright.config.mjs +++ b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/playwright.config.mjs @@ -1,10 +1,5 @@ import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const eventProxyPort = 3031; const expressPort = 3030; diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/playwright.config.ts index 5e672ed97676..c13cac26ba9a 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const eventProxyPort = 3031; const expressPort = 3030; diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/playwright.config.mjs index 59b8f10d691b..847f270e5f32 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/playwright.config.mjs +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/playwright.config.mjs @@ -1,10 +1,5 @@ import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const eventProxyPort = 3031; const expressPort = 3030; diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/playwright.config.ts index 5e672ed97676..c13cac26ba9a 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const eventProxyPort = 3031; const expressPort = 3030; diff --git a/dev-packages/e2e-tests/test-applications/node-express/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-express/playwright.config.ts index f8bff4ab3113..5edd64b3c768 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const eventProxyPort = 3031; const expressPort = 3030; diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json index 9c2d5d24868a..a93ca8f463fb 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json @@ -47,7 +47,6 @@ }, "devDependencies": { "@playwright/test": "1.26.1", - "axios": "1.6.0", "serve": "14.0.1" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts index f288d14004d5..2e74b6d481ba 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; import { ReplayRecordingData } from './fixtures/ReplayRecordingData'; const EVENT_POLLING_TIMEOUT = 90_000; @@ -22,23 +21,11 @@ test('Sends an exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -72,29 +59,21 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'pageload') { - expect(response.data.title).toBe('/'); - hadPageLoadTransaction = true; - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; + if (response.ok) { + const data = await response.json(); + + if (data.contexts.trace.op === 'pageload') { + expect(data.title).toBe('/'); + hadPageLoadTransaction = true; } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -138,29 +117,21 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'navigation') { - expect(response.data.title).toBe('/user/:id'); - hadPageNavigationTransaction = true; - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; + if (data.contexts.trace.op === 'navigation') { + expect(data.title).toBe('/user/:id'); + hadPageNavigationTransaction = true; } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -197,24 +168,11 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -226,24 +184,17 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/recording-segments/?cursor=100%3A0%3A1`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status === 200 ? response.data[0] : response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/recording-segments/?cursor=100%3A0%3A1`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + return data[0]; } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json index 96d140adfa1d..88ebe78043ba 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json @@ -49,7 +49,6 @@ "devDependencies": { "@playwright/test": "^1.43.1", "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server", - "axios": "1.6.0", "serve": "14.0.1" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts index 1729850e778a..a4a0106d2d9c 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; import { ReplayRecordingData } from './fixtures/ReplayRecordingData'; const EVENT_POLLING_TIMEOUT = 90_000; @@ -22,23 +21,11 @@ test('Sends an exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -72,28 +59,19 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'pageload') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'pageload') { hadPageLoadTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -137,28 +115,19 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'navigation') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'navigation') { hadPageNavigationTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -195,24 +164,11 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -224,24 +180,17 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/recording-segments/?cursor=100%3A0%3A1`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status === 200 ? response.data[0] : response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/recording-segments/?cursor=100%3A0%3A1`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + return data[0]; } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/package.json b/dev-packages/e2e-tests/test-applications/standard-frontend-react/package.json index 3650e2427b44..072a856a29da 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/package.json +++ b/dev-packages/e2e-tests/test-applications/standard-frontend-react/package.json @@ -48,7 +48,6 @@ }, "devDependencies": { "@playwright/test": "1.26.1", - "axios": "1.6.0", "serve": "14.0.1" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/behaviour-test.spec.ts b/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/behaviour-test.spec.ts index 20f1c75ac222..35446d1c069c 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/behaviour-test.spec.ts +++ b/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/behaviour-test.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; import { ReplayRecordingData } from './fixtures/ReplayRecordingData'; const EVENT_POLLING_TIMEOUT = 90_000; @@ -22,23 +21,11 @@ test('Sends an exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -72,28 +59,19 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'pageload') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'pageload') { hadPageLoadTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -137,28 +115,19 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.data.contexts.trace.op === 'navigation') { + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.contexts.trace.op === 'navigation') { hadPageNavigationTransaction = true; } - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -195,24 +164,11 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, @@ -224,24 +180,17 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/recording-segments/?cursor=100%3A0%3A1`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - return response.status === 200 ? response.data[0] : response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/recording-segments/?cursor=100%3A0%3A1`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.ok) { + const data = await response.json(); + return data[0]; } + + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/svelte-5/playwright.config.ts b/dev-packages/e2e-tests/test-applications/svelte-5/playwright.config.ts index 620005a461bb..37a5c8726a36 100644 --- a/dev-packages/e2e-tests/test-applications/svelte-5/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/svelte-5/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env.TEST_ENV; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/playwright.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/playwright.config.ts index c1b17eb1a713..87f7ec8bbe32 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env.TEST_ENV; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/playwright.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/playwright.config.ts index c1661bd31389..fe5a219c8e3d 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env.TEST_ENV; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/vue-3/playwright.config.ts b/dev-packages/e2e-tests/test-applications/vue-3/playwright.config.ts index 314ef2ba09bf..63c2b137a9d7 100644 --- a/dev-packages/e2e-tests/test-applications/vue-3/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/vue-3/playwright.config.ts @@ -1,11 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - const testEnv = process.env['TEST_ENV'] || 'production'; if (!testEnv) { diff --git a/dev-packages/e2e-tests/test-applications/webpack-4/tests/behaviour-test.spec.ts b/dev-packages/e2e-tests/test-applications/webpack-4/tests/behaviour-test.spec.ts index 4f762a4028d4..2ef289910a6b 100644 --- a/dev-packages/e2e-tests/test-applications/webpack-4/tests/behaviour-test.spec.ts +++ b/dev-packages/e2e-tests/test-applications/webpack-4/tests/behaviour-test.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; const EVENT_POLLING_TIMEOUT = 90_000; @@ -18,23 +17,11 @@ test('Sends an exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, diff --git a/dev-packages/e2e-tests/test-applications/webpack-5/tests/behaviour-test.spec.ts b/dev-packages/e2e-tests/test-applications/webpack-5/tests/behaviour-test.spec.ts index 4f762a4028d4..2ef289910a6b 100644 --- a/dev-packages/e2e-tests/test-applications/webpack-5/tests/behaviour-test.spec.ts +++ b/dev-packages/e2e-tests/test-applications/webpack-5/tests/behaviour-test.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import axios, { AxiosError } from 'axios'; const EVENT_POLLING_TIMEOUT = 90_000; @@ -18,23 +17,11 @@ test('Sends an exception to Sentry', async ({ page }) => { await expect .poll( async () => { - try { - const response = await axios.get( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - } catch (e) { - if (e instanceof AxiosError && e.response) { - if (e.response.status !== 404) { - throw e; - } else { - return e.response.status; - } - } else { - throw e; - } - } + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + return response.status; }, { timeout: EVENT_POLLING_TIMEOUT, From bf0a138f556f344b0670a90731950b9a9885cbc7 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 29 May 2024 13:58:00 +0200 Subject: [PATCH 05/10] test(react): Update react-create-hash-router E2E test (#12262) Instead of just testing to send to Sentry, this now tests the actual payloads being sent. This kind of builds on top of https://github.com/getsentry/sentry-javascript/pull/12259, where we specifically test the event sending now. --- .../react-create-hash-router/package.json | 1 + ...wright.config.ts => playwright.config.mjs} | 27 +- .../react-create-hash-router/src/index.tsx | 23 +- .../src/pages/Index.tsx | 4 +- .../start-event-proxy.mjs | 6 + .../tests/behaviour-test.spec.ts | 204 --------------- .../tests/errors.test.ts | 30 +++ .../tests/fixtures/ReplayRecordingData.ts | 246 ------------------ .../tests/transactions.test.ts | 149 +++++++++++ 9 files changed, 208 insertions(+), 482 deletions(-) rename dev-packages/e2e-tests/test-applications/react-create-hash-router/{playwright.config.ts => playwright.config.mjs} (80%) create mode 100644 dev-packages/e2e-tests/test-applications/react-create-hash-router/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/errors.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/transactions.test.ts diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json index a93ca8f463fb..f028794e9830 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json @@ -46,6 +46,7 @@ ] }, "devDependencies": { + "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server", "@playwright/test": "1.26.1", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/playwright.config.ts b/dev-packages/e2e-tests/test-applications/react-create-hash-router/playwright.config.mjs similarity index 80% rename from dev-packages/e2e-tests/test-applications/react-create-hash-router/playwright.config.ts rename to dev-packages/e2e-tests/test-applications/react-create-hash-router/playwright.config.mjs index 5f93f826ebf0..4ac124380c0d 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/playwright.config.mjs @@ -1,10 +1,12 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; +const serverPort = 3030; +const eventProxyPort = 3031; + /** * See https://playwright.dev/docs/test-configuration. */ -const config: PlaywrightTestConfig = { +const config = { testDir: './tests', /* Maximum time one test can run for. */ timeout: 150_000, @@ -32,6 +34,9 @@ const config: PlaywrightTestConfig = { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: `http://localhost:${serverPort}`, }, /* Configure projects for major browsers */ @@ -58,13 +63,19 @@ const config: PlaywrightTestConfig = { ], /* Run your local dev server before starting the tests */ - webServer: { - command: 'pnpm start', - port: 3030, - env: { - PORT: '3030', + webServer: [ + { + command: 'node start-event-proxy.mjs', + port: eventProxyPort, }, - }, + { + command: 'pnpm start', + port: serverPort, + env: { + PORT: '3030', + }, + }, + ], }; export default config; diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx index 579b6f8e1cfd..dc27c8fb9ac1 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx @@ -32,6 +32,8 @@ Sentry.init({ tracesSampleRate: 1.0, release: 'e2e-test', + tunnel: 'http://localhost:3031', + // Always capture replays, so we can test this properly replaysSessionSampleRate: 1.0, replaysOnErrorSampleRate: 0.0, @@ -39,27 +41,6 @@ Sentry.init({ debug: true, }); -Object.defineProperty(window, 'sentryReplayId', { - get() { - return replay['_replay'].session.id; - }, -}); - -Sentry.addEventProcessor(event => { - if ( - event.type === 'transaction' && - (event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation') - ) { - const eventId = event.event_id; - if (eventId) { - window.recordedTransactions = window.recordedTransactions || []; - window.recordedTransactions.push(eventId); - } - } - - return event; -}); - const sentryCreateHashRouter = Sentry.wrapCreateBrowserRouter(createHashRouter); const router = sentryCreateHashRouter([ diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/pages/Index.tsx index 7789a2773224..d6b71a1d1279 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/pages/Index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/pages/Index.tsx @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/react'; // biome-ignore lint/nursery/noUnusedImports: Need React import for JSX import * as React from 'react'; import { Link } from 'react-router-dom'; @@ -11,8 +10,7 @@ const Index = () => { value="Capture Exception" id="exception-button" onClick={() => { - const eventId = Sentry.captureException(new Error('I am an error!')); - window.capturedExceptionId = eventId; + throw new Error('I am an error!'); }} /> diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/react-create-hash-router/start-event-proxy.mjs new file mode 100644 index 000000000000..0a802ff33b16 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'react-create-hash-router', +}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts deleted file mode 100644 index 2e74b6d481ba..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { ReplayRecordingData } from './fixtures/ReplayRecordingData'; - -const EVENT_POLLING_TIMEOUT = 90_000; - -const authToken = process.env.E2E_TEST_AUTH_TOKEN; -const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; -const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; - -test('Sends an exception to Sentry', async ({ page }) => { - await page.goto('/'); - - const exceptionButton = page.locator('id=exception-button'); - await exceptionButton.click(); - - const exceptionIdHandle = await page.waitForFunction(() => window.capturedExceptionId); - const exceptionEventId = await exceptionIdHandle.jsonValue(); - - console.log(`Polling for error eventId: ${exceptionEventId}`); - - await expect - .poll( - async () => { - const response = await fetch( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - }, - { - timeout: EVENT_POLLING_TIMEOUT, - }, - ) - .toBe(200); -}); - -test('Sends a pageload transaction to Sentry', async ({ page }) => { - await page.goto('/'); - - const recordedTransactionsHandle = await page.waitForFunction(() => { - if (window.recordedTransactions && window.recordedTransactions?.length >= 1) { - return window.recordedTransactions; - } else { - return undefined; - } - }); - const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue(); - - if (recordedTransactionEventIds === undefined) { - throw new Error("Application didn't record any transaction event IDs."); - } - - let hadPageLoadTransaction = false; - - console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`); - - await Promise.all( - recordedTransactionEventIds.map(async transactionEventId => { - await expect - .poll( - async () => { - const response = await fetch( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.ok) { - const data = await response.json(); - - if (data.contexts.trace.op === 'pageload') { - expect(data.title).toBe('/'); - hadPageLoadTransaction = true; - } - } - - return response.status; - }, - { - timeout: EVENT_POLLING_TIMEOUT, - }, - ) - .toBe(200); - }), - ); - - expect(hadPageLoadTransaction).toBe(true); -}); - -test('Sends a navigation transaction to Sentry', async ({ page }) => { - await page.goto('/'); - - // Give pageload transaction time to finish - page.waitForTimeout(4000); - - const linkElement = page.locator('id=navigation'); - await linkElement.click(); - - const recordedTransactionsHandle = await page.waitForFunction(() => { - if (window.recordedTransactions && window.recordedTransactions?.length >= 2) { - return window.recordedTransactions; - } else { - return undefined; - } - }); - const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue(); - - if (recordedTransactionEventIds === undefined) { - throw new Error("Application didn't record any transaction event IDs."); - } - - let hadPageNavigationTransaction = false; - - console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`); - - await Promise.all( - recordedTransactionEventIds.map(async transactionEventId => { - await expect - .poll( - async () => { - const response = await fetch( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.ok) { - const data = await response.json(); - - if (data.contexts.trace.op === 'navigation') { - expect(data.title).toBe('/user/:id'); - hadPageNavigationTransaction = true; - } - } - - return response.status; - }, - { - timeout: EVENT_POLLING_TIMEOUT, - }, - ) - .toBe(200); - }), - ); - - expect(hadPageNavigationTransaction).toBe(true); -}); - -test('Sends a Replay recording to Sentry', async ({ browser }) => { - const context = await browser.newContext(); - const page = await context.newPage(); - - await page.goto('/'); - - const replayId = await page.waitForFunction(() => { - return window.sentryReplayId; - }); - - // Keypress event ensures LCP is finished - await page.type('body', 'Y'); - - // Wait for replay to be sent - - if (replayId === undefined) { - throw new Error("Application didn't set a replayId"); - } - - console.log(`Polling for replay with ID: ${replayId}`); - - await expect - .poll( - async () => { - const response = await fetch( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - }, - { - timeout: EVENT_POLLING_TIMEOUT, - }, - ) - .toBe(200); - - // now fetch the first recording segment - await expect - .poll( - async () => { - const response = await fetch( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/recording-segments/?cursor=100%3A0%3A1`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.ok) { - const data = await response.json(); - return data[0]; - } - - return response.status; - }, - { - timeout: EVENT_POLLING_TIMEOUT, - }, - ) - .toEqual(ReplayRecordingData); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/errors.test.ts new file mode 100644 index 000000000000..80dab4ba949c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/errors.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/event-proxy-server'; + +test('Captures exception correctly', async ({ page }) => { + const errorEventPromise = waitForError('react-create-hash-router', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.request).toEqual({ + headers: expect.any(Object), + url: 'http://localhost:3030/', + }); + + expect(errorEvent.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts deleted file mode 100644 index 0b454ba12214..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { expect } from '@playwright/test'; - -export const ReplayRecordingData = [ - { - type: 4, - data: { href: expect.stringMatching(/http:\/\/localhost:\d+\//), width: 1280, height: 720 }, - timestamp: expect.any(Number), - }, - { - data: { - payload: { - blockAllMedia: true, - errorSampleRate: 0, - maskAllInputs: true, - maskAllText: true, - networkCaptureBodies: true, - networkDetailHasUrls: false, - networkRequestHasHeaders: true, - networkResponseHasHeaders: true, - sessionSampleRate: 1, - shouldRecordCanvas: false, - useCompression: false, - useCompressionOption: true, - }, - tag: 'options', - }, - timestamp: expect.any(Number), - type: 5, - }, - { - type: 2, - data: { - node: { - type: 0, - childNodes: [ - { type: 1, name: 'html', publicId: '', systemId: '', id: 2 }, - { - type: 2, - tagName: 'html', - attributes: { lang: 'en' }, - childNodes: [ - { - type: 2, - tagName: 'head', - attributes: {}, - childNodes: [ - { type: 2, tagName: 'meta', attributes: { charset: 'utf-8' }, childNodes: [], id: 5 }, - { - type: 2, - tagName: 'meta', - attributes: { name: 'viewport', content: 'width=device-width,initial-scale=1' }, - childNodes: [], - id: 6, - }, - { - type: 2, - tagName: 'meta', - attributes: { name: 'theme-color', content: '#000000' }, - childNodes: [], - id: 7, - }, - { - type: 2, - tagName: 'title', - attributes: {}, - childNodes: [{ type: 3, textContent: '***** ***', id: 9 }], - id: 8, - }, - ], - id: 4, - }, - { - type: 2, - tagName: 'body', - attributes: {}, - childNodes: [ - { - type: 2, - tagName: 'noscript', - attributes: {}, - childNodes: [{ type: 3, textContent: '*** **** ** ****** ********** ** *** **** ****', id: 12 }], - id: 11, - }, - { type: 2, tagName: 'div', attributes: { id: 'root' }, childNodes: [], id: 13 }, - ], - id: 10, - }, - ], - id: 3, - }, - ], - id: 1, - }, - initialOffset: { left: 0, top: 0 }, - }, - timestamp: expect.any(Number), - }, - { - type: 3, - data: { - source: 0, - texts: [], - attributes: [], - removes: [], - adds: [ - { - parentId: 13, - nextId: null, - node: { - type: 2, - tagName: 'a', - attributes: { id: 'navigation', href: expect.stringMatching(/http:\/\/localhost:\d+\/user\/5/) }, - childNodes: [], - id: 14, - }, - }, - { parentId: 14, nextId: null, node: { type: 3, textContent: '********', id: 15 } }, - { - parentId: 13, - nextId: 14, - node: { - type: 2, - tagName: 'input', - attributes: { type: 'button', id: 'exception-button', value: '******* *********' }, - childNodes: [], - id: 16, - }, - }, - ], - }, - timestamp: expect.any(Number), - }, - { - type: 3, - data: { source: 5, text: 'Capture Exception', isChecked: false, id: 16 }, - timestamp: expect.any(Number), - }, - { - type: 5, - timestamp: expect.any(Number), - data: { - tag: 'performanceSpan', - payload: { - op: 'navigation.navigate', - description: expect.stringMatching(/http:\/\/localhost:\d+\//), - startTimestamp: expect.any(Number), - endTimestamp: expect.any(Number), - data: { - decodedBodySize: expect.any(Number), - encodedBodySize: expect.any(Number), - duration: expect.any(Number), - domInteractive: expect.any(Number), - domContentLoadedEventEnd: expect.any(Number), - domContentLoadedEventStart: expect.any(Number), - loadEventStart: expect.any(Number), - loadEventEnd: expect.any(Number), - domComplete: expect.any(Number), - redirectCount: expect.any(Number), - size: expect.any(Number), - }, - }, - }, - }, - { - type: 5, - timestamp: expect.any(Number), - data: { - tag: 'performanceSpan', - payload: { - op: 'resource.script', - description: expect.stringMatching(/http:\/\/localhost:\d+\/static\/js\/main.(\w+).js/), - startTimestamp: expect.any(Number), - endTimestamp: expect.any(Number), - data: { - decodedBodySize: expect.any(Number), - encodedBodySize: expect.any(Number), - size: expect.any(Number), - }, - }, - }, - }, - { - type: 5, - timestamp: expect.any(Number), - data: { - tag: 'performanceSpan', - payload: { - op: 'paint', - description: 'first-paint', - startTimestamp: expect.any(Number), - endTimestamp: expect.any(Number), - }, - }, - }, - { - type: 5, - timestamp: expect.any(Number), - data: { - tag: 'performanceSpan', - payload: { - op: 'paint', - description: 'first-contentful-paint', - startTimestamp: expect.any(Number), - endTimestamp: expect.any(Number), - }, - }, - }, - { - type: 5, - timestamp: expect.any(Number), - data: { - tag: 'performanceSpan', - payload: { - op: 'largest-contentful-paint', - description: 'largest-contentful-paint', - startTimestamp: expect.any(Number), - endTimestamp: expect.any(Number), - data: { - value: expect.any(Number), - size: expect.any(Number), - nodeId: 16, - }, - }, - }, - }, - { - type: 5, - timestamp: expect.any(Number), - data: { - tag: 'performanceSpan', - payload: { - op: 'memory', - description: 'memory', - startTimestamp: expect.any(Number), - endTimestamp: expect.any(Number), - data: { - memory: { - jsHeapSizeLimit: expect.any(Number), - totalJSHeapSize: expect.any(Number), - usedJSHeapSize: expect.any(Number), - }, - }, - }, - }, - }, -]; diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/transactions.test.ts new file mode 100644 index 000000000000..971d4f31521b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/transactions.test.ts @@ -0,0 +1,149 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/event-proxy-server'; + +test('Captures a pageload transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-hash-router', event => { + return event.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/'); + + const transactionEvent = await transactionEventPromise; + expect(transactionEvent.contexts?.trace).toEqual({ + data: expect.objectContaining({ + deviceMemory: expect.any(String), + effectiveConnectionType: expect.any(String), + hardwareConcurrency: expect.any(String), + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'pageload', + span_id: expect.any(String), + trace_id: expect.any(String), + origin: 'auto.pageload.react.reactrouter_v6', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(transactionEvent.spans).toContainEqual({ + data: { + 'sentry.origin': 'auto.ui.browser.metrics', + 'sentry.op': 'browser', + }, + description: 'domContentLoadedEvent', + op: 'browser', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.ui.browser.metrics', + }); + expect(transactionEvent.spans).toContainEqual({ + data: { + 'sentry.origin': 'auto.ui.browser.metrics', + 'sentry.op': 'browser', + }, + description: 'loadEvent', + op: 'browser', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.ui.browser.metrics', + }); + expect(transactionEvent.spans).toContainEqual({ + data: { + 'sentry.origin': 'auto.ui.browser.metrics', + 'sentry.op': 'browser', + }, + description: 'connect', + op: 'browser', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.ui.browser.metrics', + }); + expect(transactionEvent.spans).toContainEqual({ + data: { + 'sentry.origin': 'auto.ui.browser.metrics', + 'sentry.op': 'browser', + }, + description: 'request', + op: 'browser', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.ui.browser.metrics', + }); + expect(transactionEvent.spans).toContainEqual({ + data: { + 'sentry.origin': 'auto.ui.browser.metrics', + 'sentry.op': 'browser', + }, + description: 'response', + op: 'browser', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.ui.browser.metrics', + }); +}); + +test('Captures a navigation transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-hash-router', event => { + return event.contexts?.trace?.op === 'navigation'; + }); + + await page.goto('/'); + const linkElement = page.locator('id=navigation'); + await linkElement.click(); + + const transactionEvent = await transactionEventPromise; + expect(transactionEvent.contexts?.trace).toEqual({ + data: expect.objectContaining({ + deviceMemory: expect.any(String), + effectiveConnectionType: expect.any(String), + hardwareConcurrency: expect.any(String), + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'navigation', + 'sentry.origin': 'auto.navigation.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'navigation', + span_id: expect.any(String), + trace_id: expect.any(String), + origin: 'auto.navigation.react.reactrouter_v6', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/user/:id', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(transactionEvent.spans).toEqual([]); +}); From 81e0fd1ad20a9dcf442e0cc39b6ccf091cc4a2e6 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 29 May 2024 14:12:20 +0200 Subject: [PATCH 06/10] build: Bump node to 18.20.3 (#12271) I noticed that locally the node-integration-tests that check versions fail, because in the SDK we use 18.18.0, we only check for "If node.major >= 18", and then get an incorrect test because actually in code we check the minor too. Instead of adjust this, IMHO it's better to update this. This also never failed on CI because there we do use latest 18 for the matrix tests... --- .size-limit.js | 4 ++-- dev-packages/e2e-tests/package.json | 3 +-- .../test-applications/node-express-cjs-preload/package.json | 3 +-- .../test-applications/node-express-esm-loader/package.json | 3 +-- .../test-applications/node-express-esm-preload/package.json | 3 +-- .../node-express-esm-without-loader/package.json | 3 +-- package.json | 2 +- 7 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 0d8af3d8d7d5..8a55b86e9b5e 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -149,7 +149,7 @@ module.exports = [ name: 'CDN Bundle (incl. Tracing, Replay)', path: createCDNPath('bundle.tracing.replay.min.js'), gzip: true, - limit: '70 KB', + limit: '72 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback)', @@ -170,7 +170,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.min.js'), gzip: false, brotli: false, - limit: '105 KB', + limit: '110 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay) - uncompressed', diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index bcab25973641..f34e41a7c2d5 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -27,8 +27,7 @@ "yaml": "2.2.2" }, "volta": { - "node": "18.20.2", - "yarn": "1.22.22", + "extends": "../../package.json", "pnpm": "8.15.8" } } diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json index 8d98a54b8d7e..660d5147bb27 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json @@ -18,7 +18,6 @@ "@playwright/test": "^1.27.1" }, "volta": { - "extends": "../../package.json", - "node": "18.19.1" + "extends": "../../package.json" } } diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json index 2d3bbb738945..e05d8711ff33 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json @@ -18,7 +18,6 @@ "@playwright/test": "^1.27.1" }, "volta": { - "extends": "../../package.json", - "node": "18.19.1" + "extends": "../../package.json" } } diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json index 20bda187d3a2..3f2310549513 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json @@ -18,7 +18,6 @@ "@playwright/test": "^1.27.1" }, "volta": { - "extends": "../../package.json", - "node": "18.19.1" + "extends": "../../package.json" } } diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json index b339fa65d2a2..8b8a34966174 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json @@ -18,7 +18,6 @@ "@playwright/test": "^1.27.1" }, "volta": { - "extends": "../../package.json", - "node": "18.19.1" + "extends": "../../package.json" } } diff --git a/package.json b/package.json index 70b7663667f4..907457557e86 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "yalc:publish": "lerna run yalc:publish" }, "volta": { - "node": "18.18.0", + "node": "18.20.3", "yarn": "1.22.19" }, "workspaces": [ From bbe7be598ecaaa37094ccc4d6c9f895bb28ef261 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 29 May 2024 14:22:20 +0200 Subject: [PATCH 07/10] build: Remove `@types/rimraf` and some rimraf usage (#12276) This was not really needed, so we may as well remove it. We still use it in package scripts, which is fine I'd say. Note that this still does not "fix" the `yarn add` issue, but one step at a time... --- dev-packages/e2e-tests/package.json | 1 - package.json | 1 - .../scripts/buildLambdaLayer.ts | 3 +- packages/nextjs/test/config/mocks.ts | 9 ++- yarn.lock | 70 ++----------------- 5 files changed, 11 insertions(+), 73 deletions(-) diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index f34e41a7c2d5..a246163e51b6 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -22,7 +22,6 @@ "dotenv": "16.0.3", "esbuild": "0.20.0", "glob": "8.0.3", - "rimraf": "^3.0.2", "ts-node": "10.9.1", "yaml": "2.2.2" }, diff --git a/package.json b/package.json index 907457557e86..b7ac85b844e9 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,6 @@ "@types/jest": "^27.4.1", "@types/jsdom": "^21.1.6", "@types/node": "^14.18.0", - "@types/rimraf": "^3.0.2", "@vitest/coverage-v8": "^1.6.0", "codecov": "^3.6.5", "deepmerge": "^4.2.2", diff --git a/packages/aws-serverless/scripts/buildLambdaLayer.ts b/packages/aws-serverless/scripts/buildLambdaLayer.ts index 310cfe606ca0..6062db947ceb 100644 --- a/packages/aws-serverless/scripts/buildLambdaLayer.ts +++ b/packages/aws-serverless/scripts/buildLambdaLayer.ts @@ -1,7 +1,6 @@ /* eslint-disable no-console */ import * as childProcess from 'child_process'; import * as fs from 'fs'; -import * as rimraf from 'rimraf'; import { version } from '../package.json'; @@ -60,6 +59,6 @@ buildLambdaLayer(); * there), but also harmless when used in CI. */ function fsForceMkdirSync(path: string): void { - rimraf.sync(path); + fs.rmSync(path, { recursive: true, force: true }); fs.mkdirSync(path); } diff --git a/packages/nextjs/test/config/mocks.ts b/packages/nextjs/test/config/mocks.ts index ddf4ce4d1553..fbc3705486c2 100644 --- a/packages/nextjs/test/config/mocks.ts +++ b/packages/nextjs/test/config/mocks.ts @@ -4,7 +4,6 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import * as rimraf from 'rimraf'; import { CLIENT_SDK_CONFIG_FILE, EDGE_SDK_CONFIG_FILE, SERVER_SDK_CONFIG_FILE } from './fixtures'; @@ -41,13 +40,13 @@ jest.spyOn(os, 'tmpdir').mockReturnValue(TEMP_DIR_PATH); // In theory, we should always land in the `else` here, but this saves the cases where the prior run got interrupted and // the `afterAll` below didn't happen. if (fs.existsSync(TEMP_DIR_PATH)) { - rimraf.sync(path.join(TEMP_DIR_PATH, '*')); -} else { - fs.mkdirSync(TEMP_DIR_PATH); + fs.rmSync(TEMP_DIR_PATH, { recursive: true, force: true }); } +fs.mkdirSync(TEMP_DIR_PATH); + afterAll(() => { - rimraf.sync(TEMP_DIR_PATH); + fs.rmSync(TEMP_DIR_PATH, { recursive: true, force: true }); }); // In order to know what to expect in the webpack config `entry` property, we need to know the path of the temporary diff --git a/yarn.lock b/yarn.lock index f08e90c12f97..e0e07a2aefae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8451,17 +8451,7 @@ dependencies: "@types/unist" "*" -"@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@*": +"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -8827,15 +8817,7 @@ "@types/history" "^3" "@types/react" "*" -"@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": +"@types/react-router-4@npm:@types/react-router@5.1.14", "@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== @@ -8889,14 +8871,6 @@ "@types/glob" "*" "@types/node" "*" -"@types/rimraf@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.2.tgz#a63d175b331748e5220ad48c901d7bbf1f44eef8" - integrity sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ== - dependencies: - "@types/glob" "*" - "@types/node" "*" - "@types/rsvp@*", "@types/rsvp@~4.0.3": version "4.0.4" resolved "https://registry.yarnpkg.com/@types/rsvp/-/rsvp-4.0.4.tgz#55e93e7054027f1ad4b4ebc1e60e59eb091e2d32" @@ -26134,7 +26108,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@npm: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== @@ -26149,13 +26123,6 @@ 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" @@ -28474,7 +28441,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-cjs@npm:string-width@^4.2.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== @@ -28500,15 +28467,6 @@ 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" @@ -28604,14 +28562,7 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"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: +"strip-ansi-cjs@npm:strip-ansi@^6.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== @@ -31238,7 +31189,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-cjs@npm:wrap-ansi@^7.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== @@ -31256,15 +31207,6 @@ 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 0d1093d0500212f1c97ddd63a7a7186044976b6b Mon Sep 17 00:00:00 2001 From: Micah Lyle <10660805+MicahLyle@users.noreply.github.com> Date: Wed, 29 May 2024 07:15:32 -0700 Subject: [PATCH 08/10] feat(react): Add TanStack Router integration (#12095) Co-authored-by: Luca Forstner --- .github/workflows/build.yml | 1 + .../tanstack-router/.gitignore | 24 + .../test-applications/tanstack-router/.npmrc | 2 + .../tanstack-router/index.html | 12 + .../tanstack-router/package.json | 34 + .../tanstack-router/playwright.config.ts | 66 ++ .../tanstack-router/src/main.tsx | 99 ++ .../tanstack-router/start-event-proxy.mjs | 6 + .../tests/routing-instrumentation.test.ts | 78 ++ .../tanstack-router/tsconfig.json | 25 + .../tanstack-router/tsconfig.node.json | 11 + .../tanstack-router/vite.config.ts | 13 + .../tanstack-router/yarn.lock | 893 ++++++++++++++++++ packages/react/src/index.ts | 1 + packages/react/src/tanstackrouter.ts | 123 +++ .../react/src/vendor/tanstackrouter-types.ts | 70 ++ 16 files changed, 1458 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/index.html create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/package.json create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/playwright.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/src/main.tsx create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/tests/routing-instrumentation.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/tsconfig.node.json create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/vite.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/yarn.lock create mode 100644 packages/react/src/tanstackrouter.ts create mode 100644 packages/react/src/vendor/tanstackrouter-types.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea3028c9fe5b..93e4b3254ccf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1017,6 +1017,7 @@ jobs: 'sveltekit', 'sveltekit-2', 'sveltekit-2-svelte-5', + 'tanstack-router', 'generic-ts3.8', 'node-fastify', 'node-hapi', diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/.gitignore b/dev-packages/e2e-tests/test-applications/tanstack-router/.gitignore new file mode 100644 index 000000000000..a547bf36d8d1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/.npmrc b/dev-packages/e2e-tests/test-applications/tanstack-router/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/index.html b/dev-packages/e2e-tests/test-applications/tanstack-router/index.html new file mode 100644 index 000000000000..15ccc61491bf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/index.html @@ -0,0 +1,12 @@ + + + + + + Tanstack Example + + +
+ + + diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json new file mode 100644 index 000000000000..2f761c7d3e71 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json @@ -0,0 +1,34 @@ +{ + "name": "tanstack-router-e2e-test-application", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "build": "vite build", + "start": "vite preview", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:assert": "pnpm test" + }, + "dependencies": { + "@sentry/react": "latest || *", + "@tanstack/react-router": "1.34.5", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "typescript": "^5.2.2", + "vite": "^5.2.0", + "@playwright/test": "^1.41.1", + "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/playwright.config.ts b/dev-packages/e2e-tests/test-applications/tanstack-router/playwright.config.ts new file mode 100644 index 000000000000..2167da6d754e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/playwright.config.ts @@ -0,0 +1,66 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +const appPort = 3030; +const eventProxyPort = 3031; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 150_000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: 0, + /* Opt out of parallel tests on CI. */ + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'list', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + baseURL: `http://localhost:${appPort}`, + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + ], + + /* Run your local dev server before starting the tests */ + + webServer: [ + { + command: 'node start-event-proxy.mjs', + port: eventProxyPort, + }, + { + command: 'pnpm start', + port: appPort, + }, + ], +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/src/main.tsx b/dev-packages/e2e-tests/test-applications/tanstack-router/src/main.tsx new file mode 100644 index 000000000000..3574d4ffb81a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/src/main.tsx @@ -0,0 +1,99 @@ +import * as Sentry from '@sentry/react'; +import { Link, Outlet, RouterProvider, createRootRoute, createRoute, createRouter } from '@tanstack/react-router'; +import { StrictMode } from 'react'; +import ReactDOM from 'react-dom/client'; + +const rootRoute = createRootRoute({ + component: () => ( + <> +
    +
  • + Home +
  • +
  • + + Post 1 + +
  • +
  • + + Post 2 + +
  • +
+
+ + + ), +}); + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: function Index() { + return ( +
+

Welcome Home!

+
+ ); + }, +}); + +const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts/', +}); + +const postIdRoute = createRoute({ + getParentRoute: () => postsRoute, + path: '$postId', + shouldReload() { + return true; + }, + loader: ({ params }) => { + return Sentry.startSpan({ name: `loading-post-${params.postId}` }, async () => { + await new Promise(resolve => setTimeout(resolve, 1000)); + }); + }, + component: function Post() { + const { postId } = postIdRoute.useParams(); + return
Post ID: {postId}
; + }, +}); + +const routeTree = rootRoute.addChildren([indexRoute, postsRoute.addChildren([postIdRoute])]); + +const router = createRouter({ routeTree }); + +declare const __APP_DSN__: string; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: __APP_DSN__, + integrations: [Sentry.tanstackRouterBrowserTracingIntegration(router)], + // We recommend adjusting this value in production, or using tracesSampler + // for finer control + tracesSampleRate: 1.0, + release: 'e2e-test', + tunnel: 'http://localhost:3031/', // proxy server + + // Always capture replays, so we can test this properly + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, +}); + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + +const rootElement = document.getElementById('root')!; +if (!rootElement.innerHTML) { + const root = ReactDOM.createRoot(rootElement); + root.render( + + + , + ); +} diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/tanstack-router/start-event-proxy.mjs new file mode 100644 index 000000000000..d65ca64f1e59 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'tanstack-router', +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/tests/routing-instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/tanstack-router/tests/routing-instrumentation.test.ts new file mode 100644 index 000000000000..3201b436faba --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/tests/routing-instrumentation.test.ts @@ -0,0 +1,78 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/event-proxy-server'; + +test('sends a pageload transaction with a parameterized URL', async ({ page }) => { + const transactionPromise = waitForTransaction('tanstack-router', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + await page.goto(`/posts/456`); + + const rootSpan = await transactionPromise; + + expect(rootSpan).toMatchObject({ + contexts: { + trace: { + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.pageload.react.tanstack_router', + 'sentry.op': 'pageload', + 'url.path.params.postId': '456', + }, + op: 'pageload', + origin: 'auto.pageload.react.tanstack_router', + }, + }, + transaction: '/posts/$postId', + transaction_info: { + source: 'route', + }, + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'loading-post-456', + }), + ]), + }); +}); + +test('sends a navigation transaction with a parameterized URL', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('tanstack-router', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('tanstack-router', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await pageloadTxnPromise; + + await page.waitForTimeout(5000); + await page.locator('#nav-link').click(); + + const navigationTxn = await navigationTxnPromise; + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.navigation.react.tanstack_router', + 'sentry.op': 'navigation', + 'url.path.params.postId': '2', + }, + op: 'navigation', + origin: 'auto.navigation.react.tanstack_router', + }, + }, + transaction: '/posts/$postId', + transaction_info: { + source: 'route', + }, + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'loading-post-2', + }), + ]), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/tsconfig.json b/dev-packages/e2e-tests/test-applications/tanstack-router/tsconfig.json new file mode 100644 index 000000000000..a7fc6fbf23de --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/tsconfig.node.json b/dev-packages/e2e-tests/test-applications/tanstack-router/tsconfig.node.json new file mode 100644 index 000000000000..97ede7ee6f2d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/vite.config.ts b/dev-packages/e2e-tests/test-applications/tanstack-router/vite.config.ts new file mode 100644 index 000000000000..bd51f2f9679a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/vite.config.ts @@ -0,0 +1,13 @@ +import react from '@vitejs/plugin-react-swc'; +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + define: { + __APP_DSN__: JSON.stringify(process.env.E2E_TEST_DSN), + }, + preview: { + port: 3030, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/yarn.lock b/dev-packages/e2e-tests/test-applications/tanstack-router/yarn.lock new file mode 100644 index 000000000000..67441f720060 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/yarn.lock @@ -0,0 +1,893 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + +"@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@rollup/rollup-android-arm-eabi@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" + integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== + +"@rollup/rollup-android-arm64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" + integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== + +"@rollup/rollup-darwin-arm64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" + integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== + +"@rollup/rollup-darwin-x64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c" + integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== + +"@rollup/rollup-linux-arm-gnueabihf@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" + integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== + +"@rollup/rollup-linux-arm-musleabihf@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" + integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== + +"@rollup/rollup-linux-arm64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" + integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== + +"@rollup/rollup-linux-arm64-musl@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" + integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== + +"@rollup/rollup-linux-powerpc64le-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" + integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== + +"@rollup/rollup-linux-riscv64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" + integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== + +"@rollup/rollup-linux-s390x-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" + integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== + +"@rollup/rollup-linux-x64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" + integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== + +"@rollup/rollup-linux-x64-musl@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d" + integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== + +"@rollup/rollup-win32-arm64-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" + integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== + +"@rollup/rollup-win32-ia32-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" + integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== + +"@rollup/rollup-win32-x64-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" + integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== + +"@sentry-internal/browser-utils@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.4.0.tgz#5b108878e93713757d75e7e8ae7780297d36ad17" + integrity sha512-Mfm3TK3KUlghhuKM3rjTeD4D5kAiB7iVNFoaDJIJBVKa67M9BvlNTnNJMDi7+9rV4RuLQYxXn0p5HEZJFYp3Zw== + dependencies: + "@sentry/core" "8.4.0" + "@sentry/types" "8.4.0" + "@sentry/utils" "8.4.0" + +"@sentry-internal/feedback@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.4.0.tgz#81067dadda249b354b72f5adba20374dea43fdf4" + integrity sha512-1/WshI2X9seZAQXrOiv6/LU08fbSSvJU0b1ZWMhn+onb/FWPomsL/UN0WufCYA65S5JZGdaWC8fUcJxWC8PATQ== + dependencies: + "@sentry/core" "8.4.0" + "@sentry/types" "8.4.0" + "@sentry/utils" "8.4.0" + +"@sentry-internal/replay-canvas@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.4.0.tgz#cf5e903d8935ba6b60a5027d0055902987353920" + integrity sha512-g+U4IPQdODCg7fQQVNvH6ix05Tl1mOQXXRexgtp+tXdys4sHQSBUYraJYZy+mY3OGnLRgKFqELM0fnffJSpuyQ== + dependencies: + "@sentry-internal/replay" "8.4.0" + "@sentry/core" "8.4.0" + "@sentry/types" "8.4.0" + "@sentry/utils" "8.4.0" + +"@sentry-internal/replay@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.4.0.tgz#8fc4a6bf1d5f480fcde2d56cd75042953e44efda" + integrity sha512-RSzQwCF/QTi5/5XAuj0VJImAhu4MheeHYvAbr/PuMSF4o1j89gBA7e3boA4u8633IqUeu5w3S5sb6jVrKaVifg== + dependencies: + "@sentry-internal/browser-utils" "8.4.0" + "@sentry/core" "8.4.0" + "@sentry/types" "8.4.0" + "@sentry/utils" "8.4.0" + +"@sentry/browser@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.4.0.tgz#f4aa381eab212432d71366884693a36c2e3a1675" + integrity sha512-hmXeIZBdN0A6yCuoMTcigGxLl42nbeb205fXtouwE7Maa0qM2HM+Ijq0sHzbhxR3zU0JXDtcJh1k6wtJOREJ3g== + dependencies: + "@sentry-internal/browser-utils" "8.4.0" + "@sentry-internal/feedback" "8.4.0" + "@sentry-internal/replay" "8.4.0" + "@sentry-internal/replay-canvas" "8.4.0" + "@sentry/core" "8.4.0" + "@sentry/types" "8.4.0" + "@sentry/utils" "8.4.0" + +"@sentry/core@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.4.0.tgz#ab3f7202f3cae82daf4c3c408f50d2c6fb913620" + integrity sha512-0eACPlJvKloFIlcT1c/vjGnvqxLxpGyGuSsU7uonrkmBqIRwLYXWtR4PoHapysKtjPVoHAn9au50ut6ymC2V8Q== + dependencies: + "@sentry/types" "8.4.0" + "@sentry/utils" "8.4.0" + +"@sentry/react@latest || *": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.4.0.tgz#95f4fed03709b231770a4f32d3c960c544b0dc3c" + integrity sha512-YnDN+szKFm1fQ9311nAulsRbboeMbqNmosMLA6PweBDEwD0HEJsovQT+ZJxXiOL220qsgWVJzk+aTPtf+oY4wA== + dependencies: + "@sentry/browser" "8.4.0" + "@sentry/core" "8.4.0" + "@sentry/types" "8.4.0" + "@sentry/utils" "8.4.0" + hoist-non-react-statics "^3.3.2" + +"@sentry/types@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.4.0.tgz#42500005a198ff8c247490434ed55e0a9f975ad1" + integrity sha512-mHUaaYEQCNukzYsTLp4rP2NNO17vUf+oSGS6qmhrsGqmGNICKw2CIwJlPPGeAkq9Y4tiUOye2m5OT1xsOtxLIw== + +"@sentry/utils@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.4.0.tgz#1b816e65d8dbf055c5e1554361aaf9a8a8a94102" + integrity sha512-oDF0RVWW0AyEnsP1x4McHUvQSAxJgx3G6wM9Sb4wc1F8rwsHnCtGHc+WRZ5Gd2AXC5EGkfbg5919+1ku/L4Dww== + dependencies: + "@sentry/types" "8.4.0" + +"@swc/core-darwin-arm64@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.7.tgz#2b5cdbd34e4162e50de6147dd1a5cb12d23b08e8" + integrity sha512-bZLVHPTpH3h6yhwVl395k0Mtx8v6CGhq5r4KQdAoPbADU974Mauz1b6ViHAJ74O0IVE5vyy7tD3OpkQxL/vMDQ== + +"@swc/core-darwin-x64@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.5.7.tgz#6aa7e3c01ab8e5e41597f8a24ff24c4e50936a46" + integrity sha512-RpUyu2GsviwTc2qVajPL0l8nf2vKj5wzO3WkLSHAHEJbiUZk83NJrZd1RVbEknIMO7+Uyjh54hEh8R26jSByaw== + +"@swc/core-linux-arm-gnueabihf@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.7.tgz#160108633b9e1d1ad05f815bedc7e9eb5d59fc2a" + integrity sha512-cTZWTnCXLABOuvWiv6nQQM0hP6ZWEkzdgDvztgHI/+u/MvtzJBN5lBQ2lue/9sSFYLMqzqff5EHKlFtrJCA9dQ== + +"@swc/core-linux-arm64-gnu@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.7.tgz#cbfa512683c73227ad25552f3b3e722b0e7fbd1d" + integrity sha512-hoeTJFBiE/IJP30Be7djWF8Q5KVgkbDtjySmvYLg9P94bHg9TJPSQoC72tXx/oXOgXvElDe/GMybru0UxhKx4g== + +"@swc/core-linux-arm64-musl@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.7.tgz#80239cb58fe57f3c86b44617fe784530ec55ee2b" + integrity sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ== + +"@swc/core-linux-x64-gnu@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.7.tgz#a699c1632de60b6a63b7fdb7abcb4fef317e57ca" + integrity sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg== + +"@swc/core-linux-x64-musl@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.7.tgz#8e4c203d6bc41e7f85d7d34d0fdf4ef751fa626c" + integrity sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg== + +"@swc/core-win32-arm64-msvc@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.7.tgz#31e3d42b8c0aa79f0ea1a980c0dd1a999d378ed7" + integrity sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA== + +"@swc/core-win32-ia32-msvc@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.7.tgz#a235285f9f62850aefcf9abb03420f2c54f63638" + integrity sha512-Xm0TfvcmmspvQg1s4+USL3x8D+YPAfX2JHygvxAnCJ0EHun8cm2zvfNBcsTlnwYb0ybFWXXY129aq1wgFC9TpQ== + +"@swc/core-win32-x64-msvc@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.7.tgz#f84641393b5223450d00d97bfff877b8b69d7c9b" + integrity sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg== + +"@swc/core@^1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.5.7.tgz#e1db7b9887d5f34eb4a3256a738d0c5f1b018c33" + integrity sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ== + dependencies: + "@swc/counter" "^0.1.2" + "@swc/types" "0.1.7" + optionalDependencies: + "@swc/core-darwin-arm64" "1.5.7" + "@swc/core-darwin-x64" "1.5.7" + "@swc/core-linux-arm-gnueabihf" "1.5.7" + "@swc/core-linux-arm64-gnu" "1.5.7" + "@swc/core-linux-arm64-musl" "1.5.7" + "@swc/core-linux-x64-gnu" "1.5.7" + "@swc/core-linux-x64-musl" "1.5.7" + "@swc/core-win32-arm64-msvc" "1.5.7" + "@swc/core-win32-ia32-msvc" "1.5.7" + "@swc/core-win32-x64-msvc" "1.5.7" + +"@swc/counter@^0.1.2", "@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/types@0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.7.tgz#ea5d658cf460abff51507ca8d26e2d391bafb15e" + integrity sha512-scHWahbHF0eyj3JsxG9CFJgFdFNaVQCNAimBlT6PzS3n/HptxqREjsm4OH6AN3lYcffZYSPxXW8ua2BEHp0lJQ== + dependencies: + "@swc/counter" "^0.1.3" + +"@tanstack/history@1.31.16": + version "1.31.16" + resolved "https://registry.yarnpkg.com/@tanstack/history/-/history-1.31.16.tgz#6b4947e967af3173ce4929d54d9cb97234646e32" + integrity sha512-rahAZXlR879P7dngDH7BZwGYiODA9D5Hqo6nUHn9GAURcqZU5IW0ZiT54dPtV5EPES7muZZmknReYueDHs7FFQ== + +"@tanstack/react-router@1.34.3": + version "1.34.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-router/-/react-router-1.34.3.tgz#1152ca7deaaec93f519971911a816bc42011b6ba" + integrity sha512-0pB+4qnp+5snHu5T93gvFmLcpXuIfSEhXZG7ipf3Rq8kmv4y0DGwcbvgFLiYKSZ/3x58faE8U0MD6F9vR9JUtg== + dependencies: + "@tanstack/history" "1.31.16" + "@tanstack/react-store" "^0.2.1" + tiny-invariant "^1.3.1" + tiny-warning "^1.0.3" + +"@tanstack/react-store@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-store/-/react-store-0.2.1.tgz#c1a04c85d403d842e56c6d0709211f013bdd1021" + integrity sha512-tEbMCQjbeVw9KOP/202LfqZMSNAVi6zYkkp1kBom8nFuMx/965Hzes3+6G6b/comCwVxoJU8Gg9IrcF8yRPthw== + dependencies: + "@tanstack/store" "0.1.3" + use-sync-external-store "^1.2.0" + +"@tanstack/store@0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.1.3.tgz#b8410435dac0a0f6d3fe77d49509f296905d4c73" + integrity sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw== + +"@types/estree@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/prop-types@*": + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== + +"@types/react-dom@^18.2.22": + version "18.3.0" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" + integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^18.2.66": + version "18.3.3" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" + integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@typescript-eslint/eslint-plugin@^7.2.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.10.0.tgz#07854a236f107bb45cbf4f62b89474cbea617f50" + integrity sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.10.0" + "@typescript-eslint/type-utils" "7.10.0" + "@typescript-eslint/utils" "7.10.0" + "@typescript-eslint/visitor-keys" "7.10.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^7.2.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.10.0.tgz#e6ac1cba7bc0400a4459e7eb5b23115bd71accfb" + integrity sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w== + dependencies: + "@typescript-eslint/scope-manager" "7.10.0" + "@typescript-eslint/types" "7.10.0" + "@typescript-eslint/typescript-estree" "7.10.0" + "@typescript-eslint/visitor-keys" "7.10.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz#054a27b1090199337a39cf755f83d9f2ce26546b" + integrity sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg== + dependencies: + "@typescript-eslint/types" "7.10.0" + "@typescript-eslint/visitor-keys" "7.10.0" + +"@typescript-eslint/type-utils@7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.10.0.tgz#8a75accce851d0a331aa9331268ef64e9b300270" + integrity sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g== + dependencies: + "@typescript-eslint/typescript-estree" "7.10.0" + "@typescript-eslint/utils" "7.10.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.10.0.tgz#da92309c97932a3a033762fd5faa8b067de84e3b" + integrity sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg== + +"@typescript-eslint/typescript-estree@7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz#6dcdc5de3149916a6a599fa89dde5c471b88b8bb" + integrity sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g== + dependencies: + "@typescript-eslint/types" "7.10.0" + "@typescript-eslint/visitor-keys" "7.10.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.10.0.tgz#8ee43e5608c9f439524eaaea8de5b358b15c51b3" + integrity sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.10.0" + "@typescript-eslint/types" "7.10.0" + "@typescript-eslint/typescript-estree" "7.10.0" + +"@typescript-eslint/visitor-keys@7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz#2af2e91e73a75dd6b70b4486c48ae9d38a485a78" + integrity sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg== + dependencies: + "@typescript-eslint/types" "7.10.0" + eslint-visitor-keys "^3.4.3" + +"@vitejs/plugin-react-swc@^3.5.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.0.tgz#e456c0a6d7f562268e1d231af9ac46b86ef47d88" + integrity sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA== + dependencies: + "@swc/core" "^1.5.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +minimatch@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-dom@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + +react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rollup@^4.13.0: + version "4.18.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.18.0.tgz#497f60f0c5308e4602cf41136339fbf87d5f5dda" + integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.18.0" + "@rollup/rollup-android-arm64" "4.18.0" + "@rollup/rollup-darwin-arm64" "4.18.0" + "@rollup/rollup-darwin-x64" "4.18.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.18.0" + "@rollup/rollup-linux-arm-musleabihf" "4.18.0" + "@rollup/rollup-linux-arm64-gnu" "4.18.0" + "@rollup/rollup-linux-arm64-musl" "4.18.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.18.0" + "@rollup/rollup-linux-riscv64-gnu" "4.18.0" + "@rollup/rollup-linux-s390x-gnu" "4.18.0" + "@rollup/rollup-linux-x64-gnu" "4.18.0" + "@rollup/rollup-linux-x64-musl" "4.18.0" + "@rollup/rollup-win32-arm64-msvc" "4.18.0" + "@rollup/rollup-win32-ia32-msvc" "4.18.0" + "@rollup/rollup-win32-x64-msvc" "4.18.0" + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + +semver@^7.6.0: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +tiny-invariant@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +typescript@^5.2.2: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +use-sync-external-store@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" + integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== + +vite@^5.2.0: + version "5.2.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.11.tgz#726ec05555431735853417c3c0bfb36003ca0cbd" + integrity sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ== + dependencies: + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index b0ee93d48677..5fe088ec448b 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -7,6 +7,7 @@ export type { ErrorBoundaryProps, FallbackRender } from './errorboundary'; export { ErrorBoundary, withErrorBoundary } from './errorboundary'; export { createReduxEnhancer } from './redux'; export { reactRouterV3BrowserTracingIntegration } from './reactrouterv3'; +export { tanstackRouterBrowserTracingIntegration } from './tanstackrouter'; export { withSentryRouting, reactRouterV4BrowserTracingIntegration, diff --git a/packages/react/src/tanstackrouter.ts b/packages/react/src/tanstackrouter.ts new file mode 100644 index 000000000000..52b0148d3042 --- /dev/null +++ b/packages/react/src/tanstackrouter.ts @@ -0,0 +1,123 @@ +import { WINDOW, startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan } from '@sentry/browser'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, +} from '@sentry/core'; + +import { browserTracingIntegration as originalBrowserTracingIntegration } from '@sentry/browser'; +import type { Integration } from '@sentry/types'; +import type { VendoredTanstackRouter, VendoredTanstackRouterRouteMatch } from './vendor/tanstackrouter-types'; + +/** + * A custom browser tracing integration for TanStack Router. + * + * The minimum compatible version of `@tanstack/router` is `1.34.5`. + * + * @param router A TanStack Router `Router` instance that should be used for routing instrumentation. + * @param options Sentry browser tracing configuration. + */ +export function tanstackRouterBrowserTracingIntegration( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + router: any, // This is `any` because we don't want any type mismatches if TanStack Router changes their types + options: Parameters[0] = {}, +): Integration { + const castRouterInstance: VendoredTanstackRouter = router; + + const browserTracingIntegrationInstance = originalBrowserTracingIntegration({ + ...options, + instrumentNavigation: false, + instrumentPageLoad: false, + }); + + const { instrumentPageLoad = true, instrumentNavigation = true } = options; + + return { + ...browserTracingIntegrationInstance, + afterAllSetup(client) { + browserTracingIntegrationInstance.afterAllSetup(client); + + const initialWindowLocation = WINDOW.location; + if (instrumentPageLoad && initialWindowLocation) { + const matchedRoutes = castRouterInstance.matchRoutes( + initialWindowLocation.pathname, + initialWindowLocation.search, + { preload: false, throwOnError: false }, + ); + + const lastMatch = matchedRoutes[matchedRoutes.length - 1]; + + startBrowserTracingPageLoadSpan(client, { + name: lastMatch ? lastMatch.routeId : initialWindowLocation.pathname, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.tanstack_router', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: lastMatch ? 'route' : 'url', + ...routeMatchToParamSpanAttributes(lastMatch), + }, + }); + } + + if (instrumentNavigation) { + // The onBeforeNavigate hook is called at the very beginning of a navigation and is only called once per navigation, even when the user is redirected + castRouterInstance.subscribe('onBeforeNavigate', onBeforeNavigateArgs => { + // onBeforeNavigate is called during pageloads. We can avoid creating navigation spans by comparing the states of the to and from arguments. + if (onBeforeNavigateArgs.toLocation.state === onBeforeNavigateArgs.fromLocation.state) { + return; + } + + const onResolvedMatchedRoutes = castRouterInstance.matchRoutes( + onBeforeNavigateArgs.toLocation.pathname, + onBeforeNavigateArgs.toLocation.search, + { preload: false, throwOnError: false }, + ); + + const onBeforeNavigateLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + + const navigationLocation = WINDOW.location; + const navigationSpan = startBrowserTracingNavigationSpan(client, { + name: onBeforeNavigateLastMatch ? onBeforeNavigateLastMatch.routeId : navigationLocation.pathname, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.tanstack_router', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateLastMatch ? 'route' : 'url', + }, + }); + + // In case the user is redirected during navigation we want to update the span with the right value. + const unsubscribeOnResolved = castRouterInstance.subscribe('onResolved', onResolvedArgs => { + unsubscribeOnResolved(); + if (navigationSpan) { + const onResolvedMatchedRoutes = castRouterInstance.matchRoutes( + onResolvedArgs.toLocation.pathname, + onResolvedArgs.toLocation.search, + { preload: false, throwOnError: false }, + ); + + const onResolvedLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + + if (onResolvedLastMatch) { + navigationSpan.updateName(onResolvedLastMatch.routeId); + navigationSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); + navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedLastMatch)); + } + } + }); + }); + } + }, + }; +} + +function routeMatchToParamSpanAttributes(match: VendoredTanstackRouterRouteMatch | undefined): Record { + if (!match) { + return {}; + } + + const paramAttributes: Record = {}; + for (const key of Object.keys(match.params)) { + paramAttributes[`url.path.params.${key}`] = match.params[key]; + } + + return paramAttributes; +} diff --git a/packages/react/src/vendor/tanstackrouter-types.ts b/packages/react/src/vendor/tanstackrouter-types.ts new file mode 100644 index 000000000000..99a4510228a3 --- /dev/null +++ b/packages/react/src/vendor/tanstackrouter-types.ts @@ -0,0 +1,70 @@ +/* + +MIT License + +Copyright (c) 2021-present Tanner Linsley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// The following types are vendored types from TanStack Router, so we don't have to depend on the actual package + +export interface VendoredTanstackRouter { + history: VendoredTanstackRouterHistory; + state: VendoredTanstackRouterState; + matchRoutes: ( + pathname: string, + // eslint-disable-next-line @typescript-eslint/ban-types + locationSearch: {}, + opts?: { + preload?: boolean; + throwOnError?: boolean; + }, + ) => Array; + subscribe( + eventType: 'onResolved' | 'onBeforeNavigate', + callback: (stateUpdate: { + toLocation: VendoredTanstackRouterLocation; + fromLocation: VendoredTanstackRouterLocation; + }) => void, + ): () => void; +} + +interface VendoredTanstackRouterLocation { + pathname: string; + // eslint-disable-next-line @typescript-eslint/ban-types + search: {}; + state: string; +} + +interface VendoredTanstackRouterHistory { + subscribe: (cb: () => void) => () => void; +} + +interface VendoredTanstackRouterState { + matches: Array; + pendingMatches?: Array; +} + +export interface VendoredTanstackRouterRouteMatch { + routeId: string; + pathname: string; + params: { [key: string]: string }; +} From d9562b9499e8497250dfbea9091035abf355e1d3 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 29 May 2024 17:14:55 +0200 Subject: [PATCH 09/10] fix(nextjs): Do not hide `sourceMappingURL` comment on client when `nextConfig.productionBrowserSourceMaps: true` is set (#12278) --- packages/nextjs/src/config/types.ts | 1 + packages/nextjs/src/config/webpack.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 57601279997c..b6207b8d67f9 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -48,6 +48,7 @@ export type NextConfigObject = { instrumentationHook?: boolean; clientTraceMetadata?: string[]; }; + productionBrowserSourceMaps?: boolean; }; export type SentryBuildOptions = { diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 14cdad42a25e..44537ede59f8 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -329,7 +329,8 @@ export function constructWebpackConfigFunction( // the browser won't look for them and throw errors into the console when it can't find them. Because this is a // front-end-only problem, and because `sentry-cli` handles sourcemaps more reliably with the comment than // without, the option to use `hidden-source-map` only applies to the client-side build. - newConfig.devtool = !isServer ? 'hidden-source-map' : 'source-map'; + newConfig.devtool = + isServer || userNextConfig.productionBrowserSourceMaps ? 'source-map' : 'hidden-source-map'; } newConfig.plugins = newConfig.plugins || []; From 4aaaba7a3abd27c82fe9966363a150e202c66876 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 29 May 2024 17:32:18 +0200 Subject: [PATCH 10/10] meta: Add Changelog entry for 8.7.0 --- CHANGELOG.md | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b9edfc5bd99..5a0a90df7089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,89 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.7.0 + +### Important Changes + +- **feat(react): Add TanStack Router integration (#12095)** + + This release adds instrumentation for TanStack router with a new `tanstackRouterBrowserTracingIntegration` in the + `@sentry/react` SDK: + + ```javascript + import * as Sentry from '@sentry/react'; + import { createRouter } from '@tanstack/react-router'; + + const router = createRouter({ + // Your router options... + }); + + Sentry.init({ + dsn: '___PUBLIC_DSN___', + integrations: [Sentry.tanstackRouterBrowserTracingIntegration(router)], + tracesSampleRate: 1.0, + }); + ``` + +### Other Changes + +- fix(nextjs): Do not hide `sourceMappingURL` comment on client when `nextConfig.productionBrowserSourceMaps: true` is + set (#12278) + +## 8.6.0 + +### Important Changes + +- **feat(metrics): Add `timings` method to metrics (#12226)** + + This introduces a new method, `metrics.timing()`, which can be used in two ways: + + 1. With a numeric value, to simplify creating a distribution metric. This will default to `second` as unit: + + ```js + Sentry.metrics.timing('myMetric', 100); + ``` + + 2. With a callback, which will wrap the duration of the callback. This can accept a sync or async callback. It will + create an inactive span around the callback and at the end emit a metric with the duration of the span in seconds: + + ```js + const returnValue = Sentry.metrics.timing('myMetric', measureThisFunction); + ``` + +- **feat(react): Add `Sentry.reactErrorHandler` (#12147)** + + This PR introduces `Sentry.reactErrorHandler`, which you can use in React 19 as follows: + + ```js + import * as Sentry from '@sentry/react'; + import { hydrateRoot } from 'react-dom/client'; + + ReactDOM.hydrateRoot( + document.getElementById('root'), + + + , + { + onUncaughtError: Sentry.reactErrorHandler(), + onCaughtError: Sentry.reactErrorHandler((error, errorInfo) => { + // optional callback if users want custom config. + }), + }, + ); + ``` + + For more details, take a look at [the PR](https://github.com/getsentry/sentry-javascript/pull/12147). Our + documentation will be updated soon! + +### Other Changes + +- feat(sveltekit): Add request data to server-side events (#12254) +- fix(core): Pass in cron monitor config correctly (#12248) +- fix(nextjs): Don't capture suspense errors in server components (#12261) +- fix(tracing): Ensure sent spans are limited to 1000 (#12252) +- ref(core): Use versioned carrier on global object (#12206) + ## 8.5.0 ### Important Changes