diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml new file mode 100644 index 000000000000..8cb80ac7440e --- /dev/null +++ b/.github/actions/install-dependencies/action.yml @@ -0,0 +1,29 @@ +name: "Install yarn dependencies" +description: "Installs yarn dependencies and caches them." + +outputs: + cache_key: + description: "The dependency cache key" + value: ${{ steps.compute_lockfile_hash.outputs.hash }} + +runs: + using: "composite" + steps: + # we use a hash of yarn.lock as our cache key, because if it hasn't changed, our dependencies haven't changed, + # so no need to reinstall them + - name: Compute dependency cache key + id: compute_lockfile_hash + run: echo "hash=dependencies-${{ hashFiles('yarn.lock', 'packages/*/package.json', 'dev-packages/*/package.json') }}" >> "$GITHUB_OUTPUT" + shell: bash + + - name: Check dependency cache + uses: actions/cache@v4 + id: cache_dependencies + with: + path: ${{ env.CACHED_DEPENDENCY_PATHS }} + key: ${{ steps.compute_lockfile_hash.outputs.hash }} + + - name: Install dependencies + if: steps.cache_dependencies.outputs.cache-hit != 'true' + run: yarn install --ignore-engines --frozen-lockfile + shell: bash diff --git a/.github/actions/install-playwright/action.yml b/.github/actions/install-playwright/action.yml index 7f85f5e743ba..9de6e1a2b104 100644 --- a/.github/actions/install-playwright/action.yml +++ b/.github/actions/install-playwright/action.yml @@ -13,14 +13,23 @@ runs: run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT shell: bash - - name: Cache playwright binaries - uses: actions/cache@v4 + - name: Restore cached playwright binaries + uses: actions/cache/restore@v4 id: playwright-cache with: path: | ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + # Only store cache on develop branch + - name: Store cached playwright binaries + uses: actions/cache/save@v4 + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + with: + path: | + ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + # We always install all browsers, if uncached - name: Install Playwright dependencies (uncached) run: npx playwright install chromium webkit firefox --with-deps diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a956f585b414..516b1d47b624 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -146,22 +146,9 @@ jobs: with: node-version-file: 'package.json' - # we use a hash of yarn.lock as our cache key, because if it hasn't changed, our dependencies haven't changed, - # so no need to reinstall them - - name: Compute dependency cache key - id: compute_lockfile_hash - run: echo "hash=${{ hashFiles('yarn.lock', '**/package.json') }}" >> "$GITHUB_OUTPUT" - - - name: Check dependency cache - uses: actions/cache@v4 - id: cache_dependencies - with: - path: ${{ env.CACHED_DEPENDENCY_PATHS }} - key: ${{ steps.compute_lockfile_hash.outputs.hash }} - - - name: Install dependencies - if: steps.cache_dependencies.outputs.cache-hit != 'true' - run: yarn install --ignore-engines --frozen-lockfile + - name: Install Dependencies + uses: ./.github/actions/install-dependencies + id: install_dependencies - name: Check for Affected Nx Projects uses: dkhunt27/action-nx-affected-list@v5.3 @@ -200,7 +187,7 @@ jobs: run: yarn build outputs: - dependency_cache_key: ${{ steps.compute_lockfile_hash.outputs.hash }} + dependency_cache_key: ${{ steps.install_dependencies.outputs.cache_key }} changed_node_integration: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry-internal/node-integration-tests') }} changed_remix: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry/remix') }} changed_node: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry/node') }} @@ -293,22 +280,9 @@ jobs: with: node-version-file: 'package.json' - # we use a hash of yarn.lock as our cache key, because if it hasn't changed, our dependencies haven't changed, - # so no need to reinstall them - - name: Compute dependency cache key - id: compute_lockfile_hash - run: echo "hash=${{ hashFiles('yarn.lock', '**/package.json') }}" >> "$GITHUB_OUTPUT" - - - name: Check dependency cache - uses: actions/cache@v4 - id: cache_dependencies - with: - path: ${{ env.CACHED_DEPENDENCY_PATHS }} - key: ${{ steps.compute_lockfile_hash.outputs.hash }} - - - name: Install dependencies - if: steps.cache_dependencies.outputs.cache-hit != 'true' - run: yarn install --ignore-engines --frozen-lockfile + - name: Install Dependencies + uses: ./.github/actions/install-dependencies + id: install_dependencies - name: Check file formatting run: yarn lint:prettier && yarn lint:biome @@ -873,6 +847,7 @@ jobs: [ 'angular-17', 'angular-18', + 'astro-4', 'aws-lambda-layer-cjs', 'aws-serverless-esm', 'node-express', diff --git a/CHANGELOG.md b/CHANGELOG.md index 931d1462da47..7864efac6871 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,60 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +Work in this release was contributed by @charpeni. Thank you for your contribution! + +## 8.26.0 + +### Important Changes + +- **feat(node): Add `fsInstrumentation` (#13291)** + + This release adds `fsIntegration`, an integration that instruments the `fs` API to the Sentry Node SDK. The + integration creates spans with naming patterns of `fs.readFile`, `fs.unlink`, and so on. + + This integration is not enabled by default and needs to be registered in your `Sentry.init` call. You can configure + via options whether to include path arguments or error messages as span attributes when an fs call fails: + + ```js + Sentry.init({ + integrations: [ + Sentry.fsIntegration({ + recordFilePaths: true, + recordErrorMessagesAsSpanAttributes: true, + }), + ], + }); + ``` + + **WARNING:** This integration may add significant overhead to your application. Especially in scenarios with a lot of + file I/O, like for example when running a framework dev server, including this integration can massively slow down + your application. + +### Other Changes + +- feat(browser): Add spotlightBrowser integration (#13263) +- feat(browser): Allow sentry in safari extension background page (#13209) +- feat(browser): Send CLS as standalone span (experimental) (#13056) +- feat(core): Add OpenTelemetry-specific `getTraceData` implementation (#13281) +- feat(nextjs): Always add `browserTracingIntegration` (#13324) +- feat(nextjs): Always transmit trace data to the client (#13337) +- feat(nextjs): export SentryBuildOptions (#13296) +- feat(nextjs): Update `experimental_captureRequestError` to reflect `RequestInfo.path` change in Next.js canary + (#13344) + +- feat(nuxt): Always add tracing meta tags (#13273) +- feat(nuxt): Set transaction name for server error (#13292) +- feat(replay): Add a replay-specific logger (#13256) +- feat(sveltekit): Add bundle size optimizations to plugin options (#13318) +- feat(sveltekit): Always add browserTracingIntegration (#13322) +- feat(tracing): Make long animation frames opt-out (#13255) +- fix(astro): Correctly extract request data (#13315) +- fix(astro): Only track access request headers in dynamic page requests (#13306) +- fix(nuxt): Add import line for disabled `autoImport` (#13342) +- fix(nuxt): Add vue to excludeEsmLoaderHooks array (#13346) +- fix(opentelemetry): Do not overwrite http span name if kind is internal (#13282) +- fix(remix): Ensure `origin` is correctly set for remix server spans (#13305) + Work in this release was contributed by @MonstraG, @undead-voron and @Zen-cronic. Thank you for your contributions! ## 8.25.0 diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts index 44eca49888be..62456093a9a6 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +import * as fs from 'fs'; +import * as path from 'path'; import { expect } from '@playwright/test'; import { TEST_HOST, sentryTest } from '../../../../utils/fixtures'; @@ -28,6 +28,8 @@ sentryTest('it does not download the SDK if the SDK was loaded in the meanwhile' }); }); + const tmpDir = await getLocalTestUrl({ testDir: __dirname, skipRouteHandler: true }); + await page.route(`${TEST_HOST}/*.*`, route => { const file = route.request().url().split('/').pop(); @@ -35,12 +37,13 @@ sentryTest('it does not download the SDK if the SDK was loaded in the meanwhile' cdnLoadedCount++; } - const filePath = path.resolve(__dirname, `./dist/${file}`); + const filePath = path.resolve(tmpDir, `./${file}`); return fs.existsSync(filePath) ? route.fulfill({ path: filePath }) : route.continue(); }); - const url = await getLocalTestUrl({ testDir: __dirname, skipRouteHandler: true }); + const url = `${TEST_HOST}/index.html`; + const req = await waitForErrorRequestOnUrl(page, url); const eventData = envelopeRequestParser(req); diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 84b90b6045a9..2e2e6cab12ab 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/browser-integration-tests", - "version": "8.25.0", + "version": "8.26.0", "main": "index.js", "license": "MIT", "engines": { @@ -43,7 +43,7 @@ "@babel/preset-typescript": "^7.16.7", "@playwright/test": "^1.44.1", "@sentry-internal/rrweb": "2.11.0", - "@sentry/browser": "8.25.0", + "@sentry/browser": "8.26.0", "axios": "1.6.7", "babel-loader": "^8.2.2", "html-webpack-plugin": "^5.5.0", diff --git a/dev-packages/browser-integration-tests/playwright.config.ts b/dev-packages/browser-integration-tests/playwright.config.ts index b03f758e11dc..45a548311eef 100644 --- a/dev-packages/browser-integration-tests/playwright.config.ts +++ b/dev-packages/browser-integration-tests/playwright.config.ts @@ -31,6 +31,7 @@ const config: PlaywrightTestConfig = { ], globalSetup: require.resolve('./playwright.setup.ts'), + globalTeardown: require.resolve('./playwright.teardown.ts'), }; export default config; diff --git a/dev-packages/browser-integration-tests/playwright.teardown.ts b/dev-packages/browser-integration-tests/playwright.teardown.ts new file mode 100644 index 000000000000..3afbf63f0aa1 --- /dev/null +++ b/dev-packages/browser-integration-tests/playwright.teardown.ts @@ -0,0 +1,5 @@ +import * as childProcess from 'child_process'; + +export default function globalTeardown(): void { + childProcess.execSync('yarn clean', { stdio: 'inherit', cwd: process.cwd() }); +} diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/init.js b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/init.js new file mode 100644 index 000000000000..067dbec23fd4 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/init.js @@ -0,0 +1,19 @@ +import * as Sentry from '@sentry/browser'; +// Import this separately so that generatePlugin can handle it for CDN scenarios +import { feedbackIntegration } from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + feedbackIntegration({ tags: { from: 'integration init' }, styleNonce: 'foo1234', scriptNonce: 'foo1234' }), + ], +}); + +document.addEventListener('securitypolicyviolation', () => { + const container = document.querySelector('#csp-violation'); + if (container) { + container.innerText = 'CSP Violation'; + } +}); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/template.html b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/template.html new file mode 100644 index 000000000000..919f372ef468 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/template.html @@ -0,0 +1,13 @@ + + + + + + + +
+ + diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts new file mode 100644 index 000000000000..95a8a2eacee8 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts @@ -0,0 +1,84 @@ +import { expect } from '@playwright/test'; + +import { TEST_HOST, sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, getEnvelopeType, shouldSkipFeedbackTest } from '../../../utils/helpers'; + +sentryTest('should capture feedback', async ({ getLocalTestUrl, page }) => { + if (shouldSkipFeedbackTest()) { + sentryTest.skip(); + } + + const feedbackRequestPromise = page.waitForResponse(res => { + const req = res.request(); + + const postData = req.postData(); + if (!postData) { + return false; + } + + try { + return getEnvelopeType(req) === 'feedback'; + } catch (err) { + return false; + } + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + await page.getByText('Report a Bug').click(); + expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + await page.locator('[data-sentry-feedback] .btn--primary').click(); + + const feedbackEvent = envelopeRequestParser((await feedbackRequestPromise).request()); + expect(feedbackEvent).toEqual({ + type: 'feedback', + breadcrumbs: expect.any(Array), + contexts: { + feedback: { + contact_email: 'janedoe@example.org', + message: 'my example feedback', + name: 'Jane Doe', + source: 'widget', + url: `${TEST_HOST}/index.html`, + }, + trace: { + trace_id: expect.stringMatching(/\w{32}/), + span_id: expect.stringMatching(/\w{16}/), + }, + }, + level: 'info', + tags: { + from: 'integration init', + }, + timestamp: expect.any(Number), + event_id: expect.stringMatching(/\w{32}/), + environment: 'production', + sdk: { + integrations: expect.arrayContaining(['Feedback']), + version: expect.any(String), + name: 'sentry.javascript.browser', + packages: expect.anything(), + }, + request: { + url: `${TEST_HOST}/index.html`, + headers: { + 'User-Agent': expect.stringContaining(''), + }, + }, + platform: 'javascript', + }); + const cspContainer = await page.locator('#csp-violation'); + expect(cspContainer).not.toContainText('CSP Violation'); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js index f6e1e21e4611..1cbadc6e36e6 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js @@ -1,3 +1,3 @@ -fetch('http://localhost:7654/foo').then(() => { +fetch('http://sentry-test.io/foo').then(() => { Sentry.captureException('test error'); }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts index f4e173940bc4..9ecf29c31014 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts @@ -31,7 +31,7 @@ sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestUrl data: { method: 'GET', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/subject.js index 0ca20f1b5acb..2c014f821a43 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/subject.js @@ -1,3 +1,3 @@ -fetch(new Request('http://localhost:7654/foo')).then(() => { +fetch(new Request('http://sentry-test.io/foo')).then(() => { Sentry.captureException('test error'); }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts index 3ffa68776fd2..8d545ea6f566 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts @@ -31,7 +31,7 @@ sentryTest('captures Breadcrumb for basic GET request that uses request object', data: { method: 'GET', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js index ea1bf44bc905..e1545e8060c6 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js @@ -1,4 +1,4 @@ -fetch('http://localhost:7654/foo', { +fetch('http://sentry-test.io/foo', { method: 'POST', body: '{"my":"body"}', headers: { diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts index 9267c1099bfd..fd27c71e6ad7 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts @@ -31,7 +31,7 @@ sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestUrl, pag data: { method: 'POST', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/subject.js index f95bcb45c166..8202bb03803b 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/subject.js @@ -1,6 +1,6 @@ const xhr = new XMLHttpRequest(); -xhr.open('GET', 'http://localhost:7654/foo'); +xhr.open('GET', 'http://sentry-test.io/foo'); xhr.send(); xhr.addEventListener('readystatechange', function () { diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts index 858392b38444..9c4b14e89033 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts @@ -32,7 +32,7 @@ sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestUrl data: { method: 'GET', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/subject.js index f8779b681d58..888d961ae9c5 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/subject.js @@ -1,6 +1,6 @@ const xhr = new XMLHttpRequest(); -xhr.open('POST', 'http://localhost:7654/foo'); +xhr.open('POST', 'http://sentry-test.io/foo'); xhr.setRequestHeader('Accept', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send('{"my":"body"}'); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts index 8df0e468a12b..985e7e316244 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts @@ -31,7 +31,7 @@ sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestUrl, pag data: { method: 'POST', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/axios/subject.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/axios/subject.js index 5cac9d385e15..1e881c5f7181 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/axios/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/axios/subject.js @@ -1,7 +1,7 @@ import axios from 'axios'; axios - .get('http://localhost:7654/foo', { + .get('http://sentry-test.io/foo', { headers: { Accept: 'application/json', 'Content-Type': 'application/json', Cache: 'no-cache' }, }) .then(response => { diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/axios/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/axios/test.ts index 3560a2598160..6318b337c165 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/axios/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/axios/test.ts @@ -44,7 +44,7 @@ sentryTest( ], }, request: { - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', method: 'GET', headers: { accept: 'application/json', diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/subject.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/subject.js index 94ab60d92ed9..93da60f0e2e6 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/subject.js @@ -1,4 +1,4 @@ -fetch('http://localhost:7654/foo', { +fetch('http://sentry-test.io/foo', { method: 'GET', credentials: 'include', headers: { diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/test.ts index bc56277c0f71..6cf8d7c7187f 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/test.ts @@ -46,7 +46,7 @@ sentryTest( ], }, request: { - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', method: 'GET', headers: { accept: 'application/json', diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/subject.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/subject.js index 78028b473ad7..6cb0fe3156fe 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/subject.js @@ -10,7 +10,7 @@ const startFetch = e => { forceTransaction: true, }, async () => { - await fetch('http://localhost:7654/foo', { signal }) + await fetch('http://sentry-test.io/foo', { signal }) .then(response => response.json()) .then(data => { console.log('Fetch succeeded:', data); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequest/subject.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequest/subject.js index 07b538291b73..af5a031c9a61 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequest/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequest/subject.js @@ -1,4 +1,4 @@ -const request = new Request('http://localhost:7654/foo', { +const request = new Request('http://sentry-test.io/foo', { method: 'POST', credentials: 'include', headers: { diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequest/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequest/test.ts index 3caa6bd19652..e2dcd308417a 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequest/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequest/test.ts @@ -42,7 +42,7 @@ sentryTest('works with a Request passed in', async ({ getLocalTestPath, page }) ], }, request: { - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', method: 'POST', headers: { accept: 'application/json', diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndBodyAndOptions/subject.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndBodyAndOptions/subject.js index 4659addc56d2..7972dd9eb291 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndBodyAndOptions/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndBodyAndOptions/subject.js @@ -1,4 +1,4 @@ -const request = new Request('http://localhost:7654/foo', { +const request = new Request('http://sentry-test.io/foo', { method: 'POST', credentials: 'include', headers: { diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndBodyAndOptions/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndBodyAndOptions/test.ts index 1ae88e8c5f5b..98519cd32129 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndBodyAndOptions/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndBodyAndOptions/test.ts @@ -44,7 +44,7 @@ sentryTest( ], }, request: { - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', method: 'POST', headers: { accept: 'application/json', diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndOptions/subject.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndOptions/subject.js index 96e3194dcfe2..8c2f763d2352 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndOptions/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndOptions/subject.js @@ -1,4 +1,4 @@ -const request = new Request('http://localhost:7654/foo', { +const request = new Request('http://sentry-test.io/foo', { method: 'POST', credentials: 'include', headers: { diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndOptions/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndOptions/test.ts index 9840f91ba272..4d3fe8458bac 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndOptions/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withRequestAndOptions/test.ts @@ -42,7 +42,7 @@ sentryTest('works with a Request (without body) & options passed in', async ({ g ], }, request: { - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', method: 'POST', headers: { accept: 'application/json', diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/subject.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/subject.js index 7a2e3cdd28c0..563b069e66cc 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/subject.js @@ -1,6 +1,6 @@ const xhr = new XMLHttpRequest(); -xhr.open('GET', 'http://localhost:7654/foo', true); +xhr.open('GET', 'http://sentry-test.io/foo', true); xhr.withCredentials = true; xhr.setRequestHeader('Accept', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json'); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/test.ts index 8bf8efa34cc4..f064a8652b48 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/test.ts @@ -42,7 +42,7 @@ sentryTest('works with httpClientIntegration', async ({ getLocalTestPath, page } ], }, request: { - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', method: 'GET', headers: { accept: 'application/json', diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/subject.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/subject.js index 7a2e3cdd28c0..563b069e66cc 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/subject.js @@ -1,6 +1,6 @@ const xhr = new XMLHttpRequest(); -xhr.open('GET', 'http://localhost:7654/foo', true); +xhr.open('GET', 'http://sentry-test.io/foo', true); xhr.withCredentials = true; xhr.setRequestHeader('Accept', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json'); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/test.ts index a3283be9cc00..6595fc0a7399 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/test.ts @@ -44,7 +44,7 @@ sentryTest( ], }, request: { - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', method: 'GET', headers: { accept: 'application/json', diff --git a/dev-packages/browser-integration-tests/suites/public-api/init/built-pkg/test.ts b/dev-packages/browser-integration-tests/suites/public-api/init/built-pkg/test.ts index 82b08fad52b6..a33a138927f5 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/init/built-pkg/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/init/built-pkg/test.ts @@ -6,14 +6,14 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; // Regression test against https://github.com/getsentry/sentry-javascript/pull/1896 -sentryTest('should not contain tslib_1__default', async ({ getLocalTestPath }) => { - await getLocalTestPath({ testDir: __dirname }); +sentryTest('should not contain tslib_1__default', async ({ getLocalTestUrl }) => { + const tmpDir = await getLocalTestUrl({ testDir: __dirname, skipRouteHandler: true }); - const initBundle = fs.readFileSync(path.join(__dirname, 'dist', 'init.bundle.js'), 'utf-8'); + const initBundle = fs.readFileSync(path.join(tmpDir, 'init.bundle.js'), 'utf-8'); expect(initBundle.length).toBeGreaterThan(0); expect(initBundle).not.toContain('tslib_1__default'); - const subjectBundle = fs.readFileSync(path.join(__dirname, 'dist', 'subject.bundle.js'), 'utf-8'); + const subjectBundle = fs.readFileSync(path.join(tmpDir, 'subject.bundle.js'), 'utf-8'); expect(subjectBundle.length).toBeGreaterThan(0); expect(subjectBundle).not.toContain('tslib_1__default'); }); diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts index 0b1e056123e8..b2a7fa6dc3ac 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts @@ -34,7 +34,7 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT timestamp: expect.any(Number), error_ids: [], trace_ids: [], - urls: [expect.stringContaining('/dist/index.html')], + urls: [expect.stringMatching(/\/dist\/([\w-]+)\/index\.html$/)], replay_id: expect.stringMatching(/\w{32}/), replay_start_timestamp: expect.any(Number), segment_id: 0, @@ -57,7 +57,7 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT name: 'sentry.javascript.browser', }, request: { - url: expect.stringContaining('/dist/index.html'), + url: expect.stringMatching(/\/dist\/([\w-]+)\/index\.html$/), headers: { 'User-Agent': expect.stringContaining(''), }, @@ -94,7 +94,7 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT name: 'sentry.javascript.browser', }, request: { - url: expect.stringContaining('/dist/index.html'), + url: expect.stringMatching(/\/dist\/([\w-]+)\/index\.html$/), headers: { 'User-Agent': expect.stringContaining(''), }, diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts index 58260c4c577f..6267413ff84e 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts @@ -34,7 +34,7 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g timestamp: expect.any(Number), error_ids: [], trace_ids: [], - urls: [expect.stringContaining('/dist/index.html')], + urls: [expect.stringMatching(/\/dist\/([\w-]+)\/index\.html$/)], replay_id: expect.stringMatching(/\w{32}/), replay_start_timestamp: expect.any(Number), segment_id: 0, @@ -57,7 +57,7 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g name: 'sentry.javascript.browser', }, request: { - url: expect.stringContaining('/dist/index.html'), + url: expect.stringMatching(/\/dist\/([\w-]+)\/index\.html$/), headers: { 'User-Agent': expect.stringContaining(''), }, @@ -94,7 +94,7 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g name: 'sentry.javascript.browser', }, request: { - url: expect.stringContaining('/dist/index.html'), + url: expect.stringMatching(/\/dist\/([\w-]+)\/index\.html$/), headers: { 'User-Agent': expect.stringContaining(''), }, diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/init.js b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/init.js index c43d5713f173..9db232f536cd 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/init.js @@ -6,7 +6,7 @@ window.Replay = Sentry.replayIntegration({ flushMaxDelay: 200, minReplayDuration: 0, - networkDetailAllowUrls: ['http://localhost:7654/foo', 'http://sentry-test.io/foo'], + networkDetailAllowUrls: ['http://sentry-test.io/foo'], networkCaptureBodies: true, }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts index bd8050b740aa..f8b48ffec598 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts @@ -8,14 +8,14 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures text request body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures text request body', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -34,19 +34,17 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', body: 'input body', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; @@ -63,7 +61,7 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse method: 'POST', request_body_size: 10, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -80,7 +78,7 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse }, response: additionalHeaders ? { headers: additionalHeaders } : undefined, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -88,14 +86,14 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse ]); }); -sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures JSON request body', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -114,19 +112,17 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', body: '{"foo":"bar"}', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; @@ -143,7 +139,7 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse method: 'POST', request_body_size: 13, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -160,7 +156,7 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse }, response: additionalHeaders ? { headers: additionalHeaders } : undefined, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -168,14 +164,14 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse ]); }); -sentryTest('captures non-text request body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures non-text request body', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -194,7 +190,7 @@ sentryTest('captures non-text request body', async ({ getLocalTestPath, page, br return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { @@ -202,15 +198,13 @@ sentryTest('captures non-text request body', async ({ getLocalTestPath, page, br body.append('name', 'Anne'); body.append('age', '32'); - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', body: body, }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; @@ -227,7 +221,7 @@ sentryTest('captures non-text request body', async ({ getLocalTestPath, page, br method: 'POST', request_body_size: 16, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -244,7 +238,7 @@ sentryTest('captures non-text request body', async ({ getLocalTestPath, page, br }, response: additionalHeaders ? { headers: additionalHeaders } : undefined, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -259,7 +253,7 @@ sentryTest('captures text request body when matching relative URL', async ({ get const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -282,7 +276,6 @@ sentryTest('captures text request body when matching relative URL', async ({ get await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ fetch('/foo', { method: 'POST', body: 'input body', @@ -290,7 +283,6 @@ sentryTest('captures text request body when matching relative URL', async ({ get // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; @@ -332,12 +324,12 @@ sentryTest('captures text request body when matching relative URL', async ({ get ]); }); -sentryTest('does not capture request body when URL does not match', async ({ getLocalTestPath, page }) => { +sentryTest('does not capture request body when URL does not match', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } - await page.route('**/bar', route => { + await page.route('http://sentry-test.io/bar', route => { return route.fulfill({ status: 200, }); @@ -356,19 +348,17 @@ sentryTest('does not capture request body when URL does not match', async ({ get return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/bar', { + fetch('http://sentry-test.io/bar', { method: 'POST', body: 'input body', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; @@ -385,7 +375,7 @@ sentryTest('does not capture request body when URL does not match', async ({ get method: 'POST', request_body_size: 10, status_code: 200, - url: 'http://localhost:7654/bar', + url: 'http://sentry-test.io/bar', }, }); @@ -409,7 +399,7 @@ sentryTest('does not capture request body when URL does not match', async ({ get }, }, }, - description: 'http://localhost:7654/bar', + description: 'http://sentry-test.io/bar', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/init.js b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/init.js index 8e8006759bee..5a97a87c2571 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/init.js @@ -6,7 +6,7 @@ window.Replay = Sentry.replayIntegration({ flushMaxDelay: 200, minReplayDuration: 0, - networkDetailAllowUrls: ['http://localhost:7654/foo'], + networkDetailAllowUrls: ['http://sentry-test.io/foo'], networkRequestHeaders: ['X-Test-Header'], }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts index 19e85e8b03db..f1952da08f0a 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts @@ -8,14 +8,14 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('handles empty/missing request headers', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('handles empty/missing request headers', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -32,19 +32,17 @@ sentryTest('handles empty/missing request headers', async ({ getLocalTestPath, p return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request, { replayRecordingSnapshots }] = await Promise.all([ page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, replayRequestPromise, @@ -62,7 +60,7 @@ sentryTest('handles empty/missing request headers', async ({ getLocalTestPath, p data: { method: 'POST', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -73,7 +71,7 @@ sentryTest('handles empty/missing request headers', async ({ getLocalTestPath, p statusCode: 200, response: additionalHeaders ? { headers: additionalHeaders } : undefined, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -81,14 +79,14 @@ sentryTest('handles empty/missing request headers', async ({ getLocalTestPath, p ]); }); -sentryTest('captures request headers as POJO', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures request headers as POJO', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -107,13 +105,12 @@ sentryTest('captures request headers as POJO', async ({ getLocalTestPath, page, return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request, { replayRecordingSnapshots }] = await Promise.all([ page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', headers: { Accept: 'application/json', @@ -126,7 +123,6 @@ sentryTest('captures request headers as POJO', async ({ getLocalTestPath, page, // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, replayRequestPromise, @@ -144,7 +140,7 @@ sentryTest('captures request headers as POJO', async ({ getLocalTestPath, page, data: { method: 'POST', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -162,7 +158,7 @@ sentryTest('captures request headers as POJO', async ({ getLocalTestPath, page, }, response: additionalHeaders ? { headers: additionalHeaders } : undefined, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -170,14 +166,14 @@ sentryTest('captures request headers as POJO', async ({ getLocalTestPath, page, ]); }); -sentryTest('captures request headers on Request', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures request headers on Request', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -194,12 +190,12 @@ sentryTest('captures request headers on Request', async ({ getLocalTestPath, pag return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request, { replayRecordingSnapshots }] = await Promise.all([ page.evaluate(() => { - const request = new Request('http://localhost:7654/foo', { + const request = new Request('http://sentry-test.io/foo', { method: 'POST', headers: { Accept: 'application/json', @@ -208,12 +204,11 @@ sentryTest('captures request headers on Request', async ({ getLocalTestPath, pag 'X-Custom-Header': 'foo', }, }); - /* eslint-disable */ + fetch(request).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, replayRequestPromise, @@ -231,7 +226,7 @@ sentryTest('captures request headers on Request', async ({ getLocalTestPath, pag data: { method: 'POST', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -248,7 +243,7 @@ sentryTest('captures request headers on Request', async ({ getLocalTestPath, pag }, response: additionalHeaders ? { headers: additionalHeaders } : undefined, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -256,14 +251,14 @@ sentryTest('captures request headers on Request', async ({ getLocalTestPath, pag ]); }); -sentryTest('captures request headers as Headers instance', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures request headers as Headers instance', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -280,7 +275,7 @@ sentryTest('captures request headers as Headers instance', async ({ getLocalTest return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); @@ -292,15 +287,13 @@ sentryTest('captures request headers as Headers instance', async ({ getLocalTest headers.append('Cache', 'no-cache'); headers.append('X-Custom-Header', 'foo'); - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', headers, }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, replayRequestPromise, @@ -318,7 +311,7 @@ sentryTest('captures request headers as Headers instance', async ({ getLocalTest data: { method: 'POST', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -335,7 +328,7 @@ sentryTest('captures request headers as Headers instance', async ({ getLocalTest }, response: additionalHeaders ? { headers: additionalHeaders } : undefined, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -343,12 +336,12 @@ sentryTest('captures request headers as Headers instance', async ({ getLocalTest ]); }); -sentryTest('does not captures request headers if URL does not match', async ({ getLocalTestPath, page }) => { +sentryTest('does not captures request headers if URL does not match', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } - await page.route('**/bar', route => { + await page.route('http://sentry-test.io/bar', route => { return route.fulfill({ status: 200, }); @@ -367,13 +360,12 @@ sentryTest('does not captures request headers if URL does not match', async ({ g return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request, { replayRecordingSnapshots }] = await Promise.all([ page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/bar', { + fetch('http://sentry-test.io/bar', { method: 'POST', headers: { Accept: 'application/json', @@ -386,7 +378,6 @@ sentryTest('does not captures request headers if URL does not match', async ({ g // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, replayRequestPromise, @@ -404,7 +395,7 @@ sentryTest('does not captures request headers if URL does not match', async ({ g data: { method: 'POST', status_code: 200, - url: 'http://localhost:7654/bar', + url: 'http://sentry-test.io/bar', }, }); @@ -426,7 +417,7 @@ sentryTest('does not captures request headers if URL does not match', async ({ g }, }, }, - description: 'http://localhost:7654/bar', + description: 'http://sentry-test.io/bar', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts index dafa896f2cbd..4da3c7142996 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts @@ -8,12 +8,12 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures request body size when body is sent', async ({ getLocalTestPath, page }) => { +sentryTest('captures request body size when body is sent', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -32,20 +32,18 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request, { replayRecordingSnapshots }] = await Promise.all([ page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', body: '{"foo":"bar"}', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, replayRequestPromise, @@ -64,7 +62,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest method: 'POST', request_body_size: 13, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -87,7 +85,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -95,7 +93,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest ]); }); -sentryTest('captures request size from non-text request body', async ({ getLocalTestPath, page }) => { +sentryTest('captures request size from non-text request body', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } @@ -119,22 +117,20 @@ sentryTest('captures request size from non-text request body', async ({ getLocal return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request, { replayRecordingSnapshots }] = await Promise.all([ page.evaluate(() => { - /* eslint-disable */ const blob = new Blob(['Hello world!!'], { type: 'text/html' }); - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', body: blob, }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, replayRequestPromise, @@ -153,7 +149,7 @@ sentryTest('captures request size from non-text request body', async ({ getLocal method: 'POST', request_body_size: 26, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -176,7 +172,7 @@ sentryTest('captures request size from non-text request body', async ({ getLocal }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/init.js b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/init.js index f2eb62b8c051..9db232f536cd 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/init.js @@ -6,7 +6,7 @@ window.Replay = Sentry.replayIntegration({ flushMaxDelay: 200, minReplayDuration: 0, - networkDetailAllowUrls: ['http://localhost:7654/foo'], + networkDetailAllowUrls: ['http://sentry-test.io/foo'], networkCaptureBodies: true, }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts index 5e58b63218ef..7ce8ecb87748 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts @@ -8,14 +8,14 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures text response body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures text response body', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, body: 'response body', @@ -35,18 +35,16 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; @@ -63,7 +61,7 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows method: 'POST', response_body_size: 13, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -82,7 +80,7 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows body: 'response body', }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -90,14 +88,14 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows ]); }); -sentryTest('captures JSON response body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures JSON response body', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, body: JSON.stringify({ res: 'this' }), @@ -117,18 +115,16 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; @@ -145,7 +141,7 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows method: 'POST', response_body_size: 14, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -164,7 +160,7 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows body: { res: 'this' }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -172,14 +168,14 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows ]); }); -sentryTest('captures non-text response body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures non-text response body', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'application/octet-stream' } : {}; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, body: Buffer.from('Hello world'), @@ -199,18 +195,16 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; @@ -227,7 +221,7 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b method: 'POST', response_body_size: 24, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -246,7 +240,7 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b body: 'Hello world', }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -256,12 +250,12 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b // This test is flaky // See: https://github.com/getsentry/sentry-javascript/issues/11136 -sentryTest.skip('does not capture response body when URL does not match', async ({ getLocalTestPath, page }) => { +sentryTest.skip('does not capture response body when URL does not match', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } - await page.route('**/bar', route => { + await page.route('http://sentry-test.io/bar', route => { return route.fulfill({ status: 200, body: 'response body', @@ -281,18 +275,16 @@ sentryTest.skip('does not capture response body when URL does not match', async return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/bar', { + fetch('http://sentry-test.io/bar', { method: 'POST', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; @@ -309,7 +301,7 @@ sentryTest.skip('does not capture response body when URL does not match', async method: 'POST', response_body_size: 13, status_code: 200, - url: 'http://localhost:7654/bar', + url: 'http://sentry-test.io/bar', }, }); @@ -333,7 +325,7 @@ sentryTest.skip('does not capture response body when URL does not match', async }, }, }, - description: 'http://localhost:7654/bar', + description: 'http://sentry-test.io/bar', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/init.js b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/init.js index a2a7a1680123..82bc5a045870 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/init.js @@ -6,7 +6,7 @@ window.Replay = Sentry.replayIntegration({ flushMaxDelay: 200, minReplayDuration: 0, - networkDetailAllowUrls: ['http://localhost:7654/foo'], + networkDetailAllowUrls: ['http://sentry-test.io/foo'], networkResponseHeaders: ['X-Test-Header'], }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts index 8486cf541a18..ec4a26dd199f 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts @@ -8,14 +8,14 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('handles empty headers', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('handles empty headers', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } const additionalHeaders = browserName === 'webkit' ? { 'content-type': 'text/plain' } : undefined; - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -34,12 +34,12 @@ sentryTest('handles empty headers', async ({ getLocalTestPath, page, browserName return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request] = await Promise.all([ page.evaluate(() => { - fetch('http://localhost:7654/foo').then(() => { + fetch('http://sentry-test.io/foo').then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); @@ -59,7 +59,7 @@ sentryTest('handles empty headers', async ({ getLocalTestPath, page, browserName data: { method: 'GET', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -71,7 +71,7 @@ sentryTest('handles empty headers', async ({ getLocalTestPath, page, browserName statusCode: 200, response: additionalHeaders ? { headers: additionalHeaders } : undefined, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -79,12 +79,12 @@ sentryTest('handles empty headers', async ({ getLocalTestPath, page, browserName ]); }); -sentryTest('captures response headers', async ({ getLocalTestPath, page }) => { +sentryTest('captures response headers', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, headers: { @@ -110,12 +110,12 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page }) => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request] = await Promise.all([ page.evaluate(() => { - fetch('http://localhost:7654/foo').then(() => { + fetch('http://sentry-test.io/foo').then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); @@ -135,7 +135,7 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page }) => { data: { method: 'GET', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -153,7 +153,7 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page }) => { }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -161,12 +161,12 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page }) => { ]); }); -sentryTest('does not capture response headers if URL does not match', async ({ getLocalTestPath, page }) => { +sentryTest('does not capture response headers if URL does not match', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } - await page.route('**/bar', route => { + await page.route('http://sentry-test.io/bar', route => { return route.fulfill({ status: 200, headers: { @@ -192,12 +192,12 @@ sentryTest('does not capture response headers if URL does not match', async ({ g return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request] = await Promise.all([ page.evaluate(() => { - fetch('http://localhost:7654/bar').then(() => { + fetch('http://sentry-test.io/bar').then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); @@ -217,7 +217,7 @@ sentryTest('does not capture response headers if URL does not match', async ({ g data: { method: 'GET', status_code: 200, - url: 'http://localhost:7654/bar', + url: 'http://sentry-test.io/bar', }, }); @@ -236,7 +236,7 @@ sentryTest('does not capture response headers if URL does not match', async ({ g _meta: { warnings: ['URL_SKIPPED'] }, }, }, - description: 'http://localhost:7654/bar', + description: 'http://sentry-test.io/bar', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts index 63e3611d9d8f..0191617d46da 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts @@ -8,12 +8,12 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures response size from Content-Length header if available', async ({ getLocalTestPath, page }) => { +sentryTest('captures response size from Content-Length header if available', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, body: JSON.stringify({ @@ -40,17 +40,15 @@ sentryTest('captures response size from Content-Length header if available', asy return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request] = await Promise.all([ page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo').then(() => { + fetch('http://sentry-test.io/foo').then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, ]); @@ -68,7 +66,7 @@ sentryTest('captures response size from Content-Length header if available', asy method: 'GET', response_body_size: 789, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -92,7 +90,7 @@ sentryTest('captures response size from Content-Length header if available', asy }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -100,12 +98,12 @@ sentryTest('captures response size from Content-Length header if available', asy ]); }); -sentryTest('captures response size without Content-Length header', async ({ getLocalTestPath, page }) => { +sentryTest('captures response size without Content-Length header', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, body: JSON.stringify({ @@ -132,17 +130,15 @@ sentryTest('captures response size without Content-Length header', async ({ getL return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request] = await Promise.all([ page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo').then(() => { + fetch('http://sentry-test.io/foo').then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, ]); @@ -160,7 +156,7 @@ sentryTest('captures response size without Content-Length header', async ({ getL method: 'GET', status_code: 200, // NOT set here from body, as this would be async - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -184,7 +180,7 @@ sentryTest('captures response size without Content-Length header', async ({ getL }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), @@ -192,7 +188,7 @@ sentryTest('captures response size without Content-Length header', async ({ getL ]); }); -sentryTest('captures response size from non-text response body', async ({ getLocalTestPath, page }) => { +sentryTest('captures response size from non-text response body', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } @@ -221,19 +217,17 @@ sentryTest('captures response size from non-text response body', async ({ getLoc return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request, { replayRecordingSnapshots }] = await Promise.all([ page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }), requestPromise, replayRequestPromise, @@ -251,7 +245,7 @@ sentryTest('captures response size from non-text response body', async ({ getLoc data: { method: 'POST', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -274,7 +268,7 @@ sentryTest('captures response size from non-text response body', async ({ getLoc }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.fetch', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts index cce931062770..d77796f92fc2 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts @@ -8,13 +8,13 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures correct timestamps', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures correct timestamps', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -34,19 +34,17 @@ sentryTest('captures correct timestamps', async ({ getLocalTestPath, page, brows return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ - fetch('http://localhost:7654/foo', { + fetch('http://sentry-test.io/foo', { method: 'POST', body: '{"foo":"bar"}', }).then(() => { // @ts-expect-error Sentry is a global Sentry.captureException('test error'); }); - /* eslint-enable */ }); const request = await requestPromise; diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/init.js b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/init.js index c43d5713f173..9db232f536cd 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/init.js @@ -6,7 +6,7 @@ window.Replay = Sentry.replayIntegration({ flushMaxDelay: 200, minReplayDuration: 0, - networkDetailAllowUrls: ['http://localhost:7654/foo', 'http://sentry-test.io/foo'], + networkDetailAllowUrls: ['http://sentry-test.io/foo'], networkCaptureBodies: true, }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts index cd19ba50dd99..d12d74381fc2 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts @@ -8,13 +8,13 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures text request body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures text request body', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -33,14 +33,13 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); void page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.send('input body'); xhr.addEventListener('readystatechange', function () { @@ -49,7 +48,6 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -66,7 +64,7 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse method: 'POST', request_body_size: 10, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -82,7 +80,7 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse body: 'input body', }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -90,13 +88,13 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse ]); }); -sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures JSON request body', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -115,14 +113,13 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); void page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.send('{"foo":"bar"}'); xhr.addEventListener('readystatechange', function () { @@ -131,7 +128,6 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -148,7 +144,7 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse method: 'POST', request_body_size: 13, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -164,7 +160,7 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse body: { foo: 'bar' }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -172,7 +168,7 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse ]); }); -sentryTest('captures non-text request body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures non-text request body', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); @@ -197,18 +193,17 @@ sentryTest('captures non-text request body', async ({ getLocalTestPath, page, br return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); const body = new URLSearchParams(); body.append('name', 'Anne'); body.append('age', '32'); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.send(body); xhr.addEventListener('readystatechange', function () { @@ -217,7 +212,6 @@ sentryTest('captures non-text request body', async ({ getLocalTestPath, page, br setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -234,7 +228,7 @@ sentryTest('captures non-text request body', async ({ getLocalTestPath, page, br method: 'POST', request_body_size: 16, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -250,7 +244,7 @@ sentryTest('captures non-text request body', async ({ getLocalTestPath, page, br body: 'name=Anne&age=32', }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -264,7 +258,7 @@ sentryTest('captures text request body when matching relative URL', async ({ get sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -287,7 +281,6 @@ sentryTest('captures text request body when matching relative URL', async ({ get await page.goto(url); void page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); xhr.open('POST', '/foo'); @@ -299,7 +292,6 @@ sentryTest('captures text request body when matching relative URL', async ({ get setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -340,13 +332,13 @@ sentryTest('captures text request body when matching relative URL', async ({ get ]); }); -sentryTest('does not capture request body when URL does not match', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('does not capture request body when URL does not match', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/bar', route => { + await page.route('http://sentry-test.io/bar', route => { return route.fulfill({ status: 200, }); @@ -365,14 +357,13 @@ sentryTest('does not capture request body when URL does not match', async ({ get return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); void page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/bar'); + xhr.open('POST', 'http://sentry-test.io/bar'); xhr.send('input body'); xhr.addEventListener('readystatechange', function () { @@ -381,7 +372,6 @@ sentryTest('does not capture request body when URL does not match', async ({ get setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -398,7 +388,7 @@ sentryTest('does not capture request body when URL does not match', async ({ get method: 'POST', request_body_size: 10, status_code: 200, - url: 'http://localhost:7654/bar', + url: 'http://sentry-test.io/bar', }, }); @@ -422,7 +412,7 @@ sentryTest('does not capture request body when URL does not match', async ({ get }, }, }, - description: 'http://localhost:7654/bar', + description: 'http://sentry-test.io/bar', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/init.js b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/init.js index 8e8006759bee..5a97a87c2571 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/init.js @@ -6,7 +6,7 @@ window.Replay = Sentry.replayIntegration({ flushMaxDelay: 200, minReplayDuration: 0, - networkDetailAllowUrls: ['http://localhost:7654/foo'], + networkDetailAllowUrls: ['http://sentry-test.io/foo'], networkRequestHeaders: ['X-Test-Header'], }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts index c9dd8c455b41..be6ac82d4b5c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts @@ -8,13 +8,13 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures request headers', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures request headers', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -33,14 +33,13 @@ sentryTest('captures request headers', async ({ getLocalTestPath, page, browserN return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); void page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.setRequestHeader('Accept', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Cache', 'no-cache'); @@ -53,7 +52,6 @@ sentryTest('captures request headers', async ({ getLocalTestPath, page, browserN setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -69,7 +67,7 @@ sentryTest('captures request headers', async ({ getLocalTestPath, page, browserN data: { method: 'POST', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -87,7 +85,7 @@ sentryTest('captures request headers', async ({ getLocalTestPath, page, browserN }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -95,98 +93,93 @@ sentryTest('captures request headers', async ({ getLocalTestPath, page, browserN ]); }); -sentryTest( - 'does not capture request headers if URL does not match', - async ({ getLocalTestPath, page, browserName }) => { - // These are a bit flaky on non-chromium browsers - if (shouldSkipReplayTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - await page.route('**/bar', route => { - return route.fulfill({ - status: 200, - }); - }); +sentryTest('does not capture request headers if URL does not match', async ({ getLocalTestUrl, page, browserName }) => { + // These are a bit flaky on non-chromium browsers + if (shouldSkipReplayTest() || browserName !== 'chromium') { + sentryTest.skip(); + } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); + await page.route('http://sentry-test.io/bar', route => { + return route.fulfill({ + status: 200, }); + }); - const requestPromise = waitForErrorRequest(page); - const replayRequestPromise = collectReplayRequests(page, recordingEvents => { - return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), }); + }); + + const requestPromise = waitForErrorRequest(page); + const replayRequestPromise = collectReplayRequests(page, recordingEvents => { + return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + void page.evaluate(() => { + const xhr = new XMLHttpRequest(); - const url = await getLocalTestPath({ testDir: __dirname }); - await page.goto(url); - - void page.evaluate(() => { - /* eslint-disable */ - const xhr = new XMLHttpRequest(); - - xhr.open('POST', 'http://localhost:7654/bar'); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.setRequestHeader('Cache', 'no-cache'); - xhr.setRequestHeader('X-Test-Header', 'test-value'); - xhr.send(); - - xhr.addEventListener('readystatechange', function () { - if (xhr.readyState === 4) { - // @ts-expect-error Sentry is a global - setTimeout(() => Sentry.captureException('test error', 0)); - } - }); - /* eslint-enable */ + xhr.open('POST', 'http://sentry-test.io/bar'); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.setRequestHeader('Cache', 'no-cache'); + xhr.setRequestHeader('X-Test-Header', 'test-value'); + xhr.send(); + + xhr.addEventListener('readystatechange', function () { + if (xhr.readyState === 4) { + // @ts-expect-error Sentry is a global + setTimeout(() => Sentry.captureException('test error', 0)); + } }); + }); - const [request] = await Promise.all([requestPromise]); + const [request] = await Promise.all([requestPromise]); - const eventData = envelopeRequestParser(request); + const eventData = envelopeRequestParser(request); + + expect(eventData.exception?.values).toHaveLength(1); - expect(eventData.exception?.values).toHaveLength(1); + expect(eventData?.breadcrumbs?.length).toBe(1); + expect(eventData!.breadcrumbs![0]).toEqual({ + timestamp: expect.any(Number), + category: 'xhr', + type: 'http', + data: { + method: 'POST', + status_code: 200, + url: 'http://sentry-test.io/bar', + }, + }); - expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ - timestamp: expect.any(Number), - category: 'xhr', - type: 'http', + const { replayRecordingSnapshots } = await replayRequestPromise; + expect(getReplayPerformanceSpans(replayRecordingSnapshots).filter(span => span.op === 'resource.xhr')).toEqual([ + { data: { method: 'POST', - status_code: 200, - url: 'http://localhost:7654/bar', - }, - }); - - const { replayRecordingSnapshots } = await replayRequestPromise; - expect(getReplayPerformanceSpans(replayRecordingSnapshots).filter(span => span.op === 'resource.xhr')).toEqual([ - { - data: { - method: 'POST', - statusCode: 200, - request: { - headers: {}, - _meta: { - warnings: ['URL_SKIPPED'], - }, + statusCode: 200, + request: { + headers: {}, + _meta: { + warnings: ['URL_SKIPPED'], }, - response: { - headers: {}, - _meta: { - warnings: ['URL_SKIPPED'], - }, + }, + response: { + headers: {}, + _meta: { + warnings: ['URL_SKIPPED'], }, }, - description: 'http://localhost:7654/bar', - endTimestamp: expect.any(Number), - op: 'resource.xhr', - startTimestamp: expect.any(Number), }, - ]); - }, -); + description: 'http://sentry-test.io/bar', + endTimestamp: expect.any(Number), + op: 'resource.xhr', + startTimestamp: expect.any(Number), + }, + ]); +}); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts index 50709dc37705..73d063ea1b47 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts @@ -8,13 +8,13 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures request body size when body is sent', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures request body size when body is sent', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -33,15 +33,14 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); const [, request] = await Promise.all([ page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.send('{"foo":"bar"}'); xhr.addEventListener('readystatechange', function () { @@ -50,7 +49,6 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }), requestPromise, ]); @@ -68,7 +66,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest method: 'POST', request_body_size: 13, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -92,7 +90,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -100,7 +98,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest ]); }); -sentryTest('captures request size from non-text request body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures request size from non-text request body', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); @@ -125,16 +123,15 @@ sentryTest('captures request size from non-text request body', async ({ getLocal return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); const blob = new Blob(['Hello world!!'], { type: 'text/html' }); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.send(blob); xhr.addEventListener('readystatechange', function () { @@ -143,7 +140,6 @@ sentryTest('captures request size from non-text request body', async ({ getLocal setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -160,7 +156,7 @@ sentryTest('captures request size from non-text request body', async ({ getLocal method: 'POST', request_body_size: 26, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -184,7 +180,7 @@ sentryTest('captures request size from non-text request body', async ({ getLocal }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/init.js b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/init.js index f2eb62b8c051..9db232f536cd 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/init.js @@ -6,7 +6,7 @@ window.Replay = Sentry.replayIntegration({ flushMaxDelay: 200, minReplayDuration: 0, - networkDetailAllowUrls: ['http://localhost:7654/foo'], + networkDetailAllowUrls: ['http://sentry-test.io/foo'], networkCaptureBodies: true, }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts index 97e9bcd749fa..afa2c669f204 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts @@ -8,13 +8,13 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures text response body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures text response body', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, body: 'response body', @@ -37,14 +37,13 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); void page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.send(); xhr.addEventListener('readystatechange', function () { @@ -53,7 +52,6 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -70,7 +68,7 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows method: 'POST', response_body_size: 13, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -86,7 +84,7 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows body: 'response body', }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -94,13 +92,13 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows ]); }); -sentryTest('captures JSON response body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures JSON response body', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, body: JSON.stringify({ res: 'this' }), @@ -123,14 +121,13 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); void page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.send(); xhr.addEventListener('readystatechange', function () { @@ -139,7 +136,6 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -156,7 +152,7 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows method: 'POST', response_body_size: 14, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -172,7 +168,7 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows body: { res: 'this' }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -180,13 +176,13 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows ]); }); -sentryTest('captures JSON response body when responseType=json', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures JSON response body when responseType=json', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, body: JSON.stringify({ res: 'this' }), @@ -209,14 +205,13 @@ sentryTest('captures JSON response body when responseType=json', async ({ getLoc return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); void page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); // Setting this to json ensures that xhr.response returns a POJO xhr.responseType = 'json'; xhr.send(); @@ -227,7 +222,6 @@ sentryTest('captures JSON response body when responseType=json', async ({ getLoc setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -244,7 +238,7 @@ sentryTest('captures JSON response body when responseType=json', async ({ getLoc method: 'POST', response_body_size: 14, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -260,7 +254,7 @@ sentryTest('captures JSON response body when responseType=json', async ({ getLoc body: { res: 'this' }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -268,13 +262,13 @@ sentryTest('captures JSON response body when responseType=json', async ({ getLoc ]); }); -sentryTest('captures non-text response body', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures non-text response body', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', async route => { + await page.route('http://sentry-test.io/foo', async route => { return route.fulfill({ status: 200, body: Buffer.from('Hello world'), @@ -297,14 +291,13 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.send(); xhr.addEventListener('readystatechange', function () { @@ -313,7 +306,6 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -330,7 +322,7 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b method: 'POST', response_body_size: 24, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -346,7 +338,7 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b body: 'Hello world', }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -354,99 +346,94 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b ]); }); -sentryTest( - 'does not capture response body when URL does not match', - async ({ getLocalTestPath, page, browserName }) => { - // These are a bit flaky on non-chromium browsers - if (shouldSkipReplayTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - await page.route('**/bar', route => { - return route.fulfill({ - status: 200, - body: 'response body', - headers: { - 'Content-Length': '', - }, - }); - }); +sentryTest('does not capture response body when URL does not match', async ({ getLocalTestUrl, page, browserName }) => { + // These are a bit flaky on non-chromium browsers + if (shouldSkipReplayTest() || browserName !== 'chromium') { + sentryTest.skip(); + } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); + await page.route('http://sentry-test.io/bar', route => { + return route.fulfill({ + status: 200, + body: 'response body', + headers: { + 'Content-Length': '', + }, }); + }); - const requestPromise = waitForErrorRequest(page); - const replayRequestPromise = collectReplayRequests(page, recordingEvents => { - return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), }); + }); - const url = await getLocalTestPath({ testDir: __dirname }); - await page.goto(url); + const requestPromise = waitForErrorRequest(page); + const replayRequestPromise = collectReplayRequests(page, recordingEvents => { + return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); + }); - void page.evaluate(() => { - /* eslint-disable */ - const xhr = new XMLHttpRequest(); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); - xhr.open('POST', 'http://localhost:7654/bar'); - xhr.send(); + void page.evaluate(() => { + const xhr = new XMLHttpRequest(); - xhr.addEventListener('readystatechange', function () { - if (xhr.readyState === 4) { - // @ts-expect-error Sentry is a global - setTimeout(() => Sentry.captureException('test error', 0)); - } - }); - /* eslint-enable */ + xhr.open('POST', 'http://sentry-test.io/bar'); + xhr.send(); + + xhr.addEventListener('readystatechange', function () { + if (xhr.readyState === 4) { + // @ts-expect-error Sentry is a global + setTimeout(() => Sentry.captureException('test error', 0)); + } }); + }); - const request = await requestPromise; - const eventData = envelopeRequestParser(request); + const request = await requestPromise; + const eventData = envelopeRequestParser(request); - expect(eventData.exception?.values).toHaveLength(1); + expect(eventData.exception?.values).toHaveLength(1); - expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ - timestamp: expect.any(Number), - category: 'xhr', - type: 'http', + expect(eventData?.breadcrumbs?.length).toBe(1); + expect(eventData!.breadcrumbs![0]).toEqual({ + timestamp: expect.any(Number), + category: 'xhr', + type: 'http', + data: { + method: 'POST', + response_body_size: 13, + status_code: 200, + url: 'http://sentry-test.io/bar', + }, + }); + + const { replayRecordingSnapshots } = await replayRequestPromise; + expect(getReplayPerformanceSpans(replayRecordingSnapshots).filter(span => span.op === 'resource.xhr')).toEqual([ + { data: { method: 'POST', - response_body_size: 13, - status_code: 200, - url: 'http://localhost:7654/bar', - }, - }); - - const { replayRecordingSnapshots } = await replayRequestPromise; - expect(getReplayPerformanceSpans(replayRecordingSnapshots).filter(span => span.op === 'resource.xhr')).toEqual([ - { - data: { - method: 'POST', - statusCode: 200, - request: { - headers: {}, - _meta: { - warnings: ['URL_SKIPPED'], - }, + statusCode: 200, + request: { + headers: {}, + _meta: { + warnings: ['URL_SKIPPED'], }, - response: { - size: 13, - headers: {}, - _meta: { - warnings: ['URL_SKIPPED'], - }, + }, + response: { + size: 13, + headers: {}, + _meta: { + warnings: ['URL_SKIPPED'], }, }, - description: 'http://localhost:7654/bar', - endTimestamp: expect.any(Number), - op: 'resource.xhr', - startTimestamp: expect.any(Number), }, - ]); - }, -); + description: 'http://sentry-test.io/bar', + endTimestamp: expect.any(Number), + op: 'resource.xhr', + startTimestamp: expect.any(Number), + }, + ]); +}); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/init.js b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/init.js index a2a7a1680123..82bc5a045870 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/init.js @@ -6,7 +6,7 @@ window.Replay = Sentry.replayIntegration({ flushMaxDelay: 200, minReplayDuration: 0, - networkDetailAllowUrls: ['http://localhost:7654/foo'], + networkDetailAllowUrls: ['http://sentry-test.io/foo'], networkResponseHeaders: ['X-Test-Header'], }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts index 754c2adf588f..4726f69c641d 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts @@ -8,13 +8,13 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures response headers', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures response headers', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, headers: { @@ -40,14 +40,13 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page, browser return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('GET', 'http://localhost:7654/foo'); + xhr.open('GET', 'http://sentry-test.io/foo'); xhr.send(); xhr.addEventListener('readystatechange', function () { @@ -56,7 +55,6 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page, browser setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -72,7 +70,7 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page, browser data: { method: 'GET', status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -90,7 +88,7 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page, browser }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -100,13 +98,13 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page, browser sentryTest( 'does not capture response headers if URL does not match', - async ({ getLocalTestPath, page, browserName }) => { + async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/bar', route => { + await page.route('http://sentry-test.io/bar', route => { return route.fulfill({ status: 200, headers: { @@ -132,14 +130,13 @@ sentryTest( return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('GET', 'http://localhost:7654/bar'); + xhr.open('GET', 'http://sentry-test.io/bar'); xhr.send(); xhr.addEventListener('readystatechange', function () { @@ -148,7 +145,6 @@ sentryTest( setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -164,7 +160,7 @@ sentryTest( data: { method: 'GET', status_code: 200, - url: 'http://localhost:7654/bar', + url: 'http://sentry-test.io/bar', }, }); @@ -187,7 +183,7 @@ sentryTest( }, }, }, - description: 'http://localhost:7654/bar', + description: 'http://sentry-test.io/bar', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts index 5024e65741e9..dda9e4e642c1 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts @@ -10,13 +10,13 @@ import { sentryTest( 'captures response size from Content-Length header if available', - async ({ getLocalTestPath, page, browserName }) => { + async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, headers: { @@ -38,14 +38,13 @@ sentryTest( return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('GET', 'http://localhost:7654/foo'); + xhr.open('GET', 'http://sentry-test.io/foo'); xhr.send(); xhr.addEventListener('readystatechange', function () { @@ -54,7 +53,6 @@ sentryTest( setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -71,7 +69,7 @@ sentryTest( method: 'GET', response_body_size: 789, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -95,7 +93,7 @@ sentryTest( }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -104,13 +102,13 @@ sentryTest( }, ); -sentryTest('captures response size without Content-Length header', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures response size without Content-Length header', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, body: JSON.stringify({ @@ -135,14 +133,13 @@ sentryTest('captures response size without Content-Length header', async ({ getL return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('GET', 'http://localhost:7654/foo'); + xhr.open('GET', 'http://sentry-test.io/foo'); xhr.send(); xhr.addEventListener('readystatechange', function () { @@ -151,7 +148,6 @@ sentryTest('captures response size without Content-Length header', async ({ getL setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -159,8 +155,8 @@ sentryTest('captures response size without Content-Length header', async ({ getL expect(eventData.exception?.values).toHaveLength(1); - expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs?.length).toBe(1); + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -168,7 +164,7 @@ sentryTest('captures response size without Content-Length header', async ({ getL method: 'GET', response_body_size: 29, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -192,7 +188,7 @@ sentryTest('captures response size without Content-Length header', async ({ getL }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), @@ -200,13 +196,13 @@ sentryTest('captures response size without Content-Length header', async ({ getL ]); }); -sentryTest('captures response size for non-string bodies', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures response size for non-string bodies', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', async route => { + await page.route('http://sentry-test.io/foo', async route => { return route.fulfill({ status: 200, body: Buffer.from('Hello world'), @@ -229,14 +225,13 @@ sentryTest('captures response size for non-string bodies', async ({ getLocalTest return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.send(); xhr.addEventListener('readystatechange', function () { @@ -245,7 +240,6 @@ sentryTest('captures response size for non-string bodies', async ({ getLocalTest setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; @@ -262,7 +256,7 @@ sentryTest('captures response size for non-string bodies', async ({ getLocalTest method: 'POST', response_body_size: 24, status_code: 200, - url: 'http://localhost:7654/foo', + url: 'http://sentry-test.io/foo', }, }); @@ -286,7 +280,7 @@ sentryTest('captures response size for non-string bodies', async ({ getLocalTest }, }, }, - description: 'http://localhost:7654/foo', + description: 'http://sentry-test.io/foo', endTimestamp: expect.any(Number), op: 'resource.xhr', startTimestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts index d5d065f83ec5..e89fa6e4a24e 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts @@ -8,13 +8,13 @@ import { shouldSkipReplayTest, } from '../../../../../utils/replayHelpers'; -sentryTest('captures correct timestamps', async ({ getLocalTestPath, page, browserName }) => { +sentryTest('captures correct timestamps', async ({ getLocalTestUrl, page, browserName }) => { // These are a bit flaky on non-chromium browsers if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } - await page.route('**/foo', route => { + await page.route('http://sentry-test.io/foo', route => { return route.fulfill({ status: 200, }); @@ -34,14 +34,13 @@ sentryTest('captures correct timestamps', async ({ getLocalTestPath, page, brows return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); void page.evaluate(() => { - /* eslint-disable */ const xhr = new XMLHttpRequest(); - xhr.open('POST', 'http://localhost:7654/foo'); + xhr.open('POST', 'http://sentry-test.io/foo'); xhr.setRequestHeader('Accept', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Cache', 'no-cache'); @@ -54,7 +53,6 @@ sentryTest('captures correct timestamps', async ({ getLocalTestPath, page, brows setTimeout(() => Sentry.captureException('test error', 0)); } }); - /* eslint-enable */ }); const request = await requestPromise; diff --git a/dev-packages/browser-integration-tests/suites/sessions/initial-scope/test.ts b/dev-packages/browser-integration-tests/suites/sessions/initial-scope/test.ts index 8a9ba80cdbc4..9d7b07f1cfd1 100644 --- a/dev-packages/browser-integration-tests/suites/sessions/initial-scope/test.ts +++ b/dev-packages/browser-integration-tests/suites/sessions/initial-scope/test.ts @@ -16,14 +16,10 @@ sentryTest('should start a new session on pageload.', async ({ getLocalTestPath, expect(session.did).toBe('1337'); }); -sentryTest('should start a new session with navigation.', async ({ getLocalTestPath, page, browserName }) => { - // Navigations get CORS error on Firefox and WebKit as we're using `file://` protocol. - if (browserName !== 'chromium') { - sentryTest.skip(); - } +sentryTest('should start a new session with navigation.', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); - const url = await getLocalTestPath({ testDir: __dirname }); - await page.route('**/foo', (route: Route) => route.fulfill({ path: `${__dirname}/dist/index.html` })); + await page.route('**/foo', (route: Route) => route.continue({ url })); await page.route('https://dsn.ingest.sentry.io/**/*', route => { return route.fulfill({ diff --git a/dev-packages/browser-integration-tests/suites/sessions/start-session/test.ts b/dev-packages/browser-integration-tests/suites/sessions/start-session/test.ts index 62c969d2e76d..65f9eef8e9ae 100644 --- a/dev-packages/browser-integration-tests/suites/sessions/start-session/test.ts +++ b/dev-packages/browser-integration-tests/suites/sessions/start-session/test.ts @@ -15,14 +15,9 @@ sentryTest('should start a new session on pageload.', async ({ getLocalTestPath, expect(session.status).toBe('ok'); }); -sentryTest('should start a new session with navigation.', async ({ getLocalTestPath, page, browserName }) => { - // Navigations get CORS error on Firefox and WebKit as we're using `file://` protocol. - if (browserName !== 'chromium') { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - await page.route('**/foo', (route: Route) => route.fulfill({ path: `${__dirname}/dist/index.html` })); +sentryTest('should start a new session with navigation.', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.route('**/foo', (route: Route) => route.continue({ url })); await page.route('https://dsn.ingest.sentry.io/**/*', route => { return route.fulfill({ diff --git a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts b/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts index 62c969d2e76d..65f9eef8e9ae 100644 --- a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts +++ b/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts @@ -15,14 +15,9 @@ sentryTest('should start a new session on pageload.', async ({ getLocalTestPath, expect(session.status).toBe('ok'); }); -sentryTest('should start a new session with navigation.', async ({ getLocalTestPath, page, browserName }) => { - // Navigations get CORS error on Firefox and WebKit as we're using `file://` protocol. - if (browserName !== 'chromium') { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - await page.route('**/foo', (route: Route) => route.fulfill({ path: `${__dirname}/dist/index.html` })); +sentryTest('should start a new session with navigation.', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.route('**/foo', (route: Route) => route.continue({ url })); await page.route('https://dsn.ingest.sentry.io/**/*', route => { return route.fulfill({ diff --git a/dev-packages/browser-integration-tests/utils/fixtures.ts b/dev-packages/browser-integration-tests/utils/fixtures.ts index cf34c9b7e693..e154ddc25988 100644 --- a/dev-packages/browser-integration-tests/utils/fixtures.ts +++ b/dev-packages/browser-integration-tests/utils/fixtures.ts @@ -1,3 +1,4 @@ +import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; /* eslint-disable no-empty-pattern */ @@ -11,16 +12,19 @@ export const TEST_HOST = 'http://sentry-test.io'; const getAsset = (assetDir: string, asset: string): string => { const assetPath = `${assetDir}/${asset}`; + // Try to find the asset in the same directory if (fs.existsSync(assetPath)) { return assetPath; } + // Else, try to find it in the parent directory const parentDirAssetPath = `${path.dirname(assetDir)}/${asset}`; if (fs.existsSync(parentDirAssetPath)) { return parentDirAssetPath; } + // Else use a static asset return `utils/defaults/${asset}`; }; @@ -54,44 +58,50 @@ const sentryTest = base.extend({ return use(async ({ testDir, skipRouteHandler = false }) => { const pagePath = `${TEST_HOST}/index.html`; - await build(testDir); - - // Serve all assets under - if (!skipRouteHandler) { - await page.route(`${TEST_HOST}/*.*`, route => { - const file = route.request().url().split('/').pop(); - const filePath = path.resolve(testDir, `./dist/${file}`); - - return fs.existsSync(filePath) ? route.fulfill({ path: filePath }) : route.continue(); - }); - - // Ensure feedback can be lazy loaded - await page.route(`https://browser.sentry-cdn.com/${SDK_VERSION}/feedback-modal.min.js`, route => { - const filePath = path.resolve(testDir, './dist/feedback-modal.bundle.js'); - if (!fs.existsSync(filePath)) { - throw new Error(`Feedback modal bundle (${filePath}) not found`); - } - return route.fulfill({ path: filePath }); - }); - - await page.route(`https://browser.sentry-cdn.com/${SDK_VERSION}/feedback-screenshot.min.js`, route => { - const filePath = path.resolve(testDir, './dist/feedback-screenshot.bundle.js'); - if (!fs.existsSync(filePath)) { - throw new Error(`Feedback screenshot bundle (${filePath}) not found`); - } - return route.fulfill({ path: filePath }); - }); + const tmpDir = path.join(testDir, 'dist', crypto.randomUUID()); + + await build(testDir, tmpDir); + + // If skipping route handlers we return the tmp dir instead of adding the handler + // This way, this can be handled by the caller manually + if (skipRouteHandler) { + return tmpDir; } + await page.route(`${TEST_HOST}/*.*`, route => { + const file = route.request().url().split('/').pop(); + const filePath = path.resolve(tmpDir, `./${file}`); + + return fs.existsSync(filePath) ? route.fulfill({ path: filePath }) : route.continue(); + }); + + // Ensure feedback can be lazy loaded + await page.route(`https://browser.sentry-cdn.com/${SDK_VERSION}/feedback-modal.min.js`, route => { + const filePath = path.resolve(tmpDir, './feedback-modal.bundle.js'); + if (!fs.existsSync(filePath)) { + throw new Error(`Feedback modal bundle (${filePath}) not found`); + } + return route.fulfill({ path: filePath }); + }); + + await page.route(`https://browser.sentry-cdn.com/${SDK_VERSION}/feedback-screenshot.min.js`, route => { + const filePath = path.resolve(tmpDir, './feedback-screenshot.bundle.js'); + if (!fs.existsSync(filePath)) { + throw new Error(`Feedback screenshot bundle (${filePath}) not found`); + } + return route.fulfill({ path: filePath }); + }); + return pagePath; }); }, getLocalTestPath: ({}, use) => { return use(async ({ testDir }) => { - const pagePath = `file:///${path.resolve(testDir, './dist/index.html')}`; + const tmpDir = path.join(testDir, 'dist', crypto.randomUUID()); + const pagePath = `file:///${path.resolve(tmpDir, './index.html')}`; - await build(testDir); + await build(testDir, tmpDir); return pagePath; }); @@ -139,12 +149,12 @@ const sentryTest = base.extend({ export { sentryTest }; -async function build(testDir: string): Promise { +async function build(testDir: string, tmpDir: string): Promise { const subject = getAsset(testDir, 'subject.js'); const template = getAsset(testDir, 'template.html'); const init = getAsset(testDir, 'init.js'); - await generatePage(init, subject, template, testDir); + await generatePage(init, subject, template, tmpDir); const additionalPages = fs .readdirSync(testDir) @@ -155,6 +165,6 @@ async function build(testDir: string): Promise { const subject = getAsset(testDir, 'subject.js'); const pageFile = getAsset(testDir, pageFilename); const init = getAsset(testDir, 'init.js'); - await generatePage(init, subject, pageFile, testDir, pageFilename); + await generatePage(init, subject, pageFile, tmpDir, pageFilename); } } diff --git a/dev-packages/browser-integration-tests/utils/generatePage.ts b/dev-packages/browser-integration-tests/utils/generatePage.ts index 47be50c707e4..987bb4e3d178 100644 --- a/dev-packages/browser-integration-tests/utils/generatePage.ts +++ b/dev-packages/browser-integration-tests/utils/generatePage.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync } from 'fs'; +import { mkdirSync } from 'fs'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import webpack from 'webpack'; @@ -12,49 +12,42 @@ export async function generatePage( outPath: string, outPageName: string = 'index.html', ): Promise { - const localPath = `${outPath}/dist`; - const bundlePath = `${localPath}/${outPageName}}`; + mkdirSync(outPath, { recursive: true }); - if (!existsSync(localPath)) { - mkdirSync(localPath, { recursive: true }); - } + await new Promise((resolve, reject) => { + const compiler = webpack( + webpackConfig({ + entry: { + init: initPath, + subject: subjectPath, + }, + output: { + path: outPath, + filename: '[name].bundle.js', + }, + plugins: [ + new SentryScenarioGenerationPlugin(outPath), + new HtmlWebpackPlugin({ + filename: outPageName, + template: templatePath, + inject: 'body', + }), + ], + }), + ); - if (!existsSync(bundlePath)) { - await new Promise((resolve, reject) => { - const compiler = webpack( - webpackConfig({ - entry: { - init: initPath, - subject: subjectPath, - }, - output: { - path: localPath, - filename: '[name].bundle.js', - }, - plugins: [ - new SentryScenarioGenerationPlugin(localPath), - new HtmlWebpackPlugin({ - filename: outPageName, - template: templatePath, - inject: 'body', - }), - ], - }), - ); + compiler.run(err => { + if (err) { + reject(err); + } - compiler.run(err => { + compiler.close(err => { if (err) { reject(err); } - compiler.close(err => { - if (err) { - reject(err); - } - - resolve(); - }); + resolve(); }); }); - } + }); } diff --git a/dev-packages/browser-integration-tests/utils/staticAssets.ts b/dev-packages/browser-integration-tests/utils/staticAssets.ts index 4b13159c58a4..447a3ad337f7 100644 --- a/dev-packages/browser-integration-tests/utils/staticAssets.ts +++ b/dev-packages/browser-integration-tests/utils/staticAssets.ts @@ -27,16 +27,7 @@ export function addStaticAssetSymlink(localOutPath: string, originalPath: string // Only copy files once if (!fs.existsSync(newPath)) { - try { - fs.symlinkSync(originalPath, newPath); - } catch (error) { - // There must be some race condition here as some of our tests flakey - // because the file already exists. Let's catch and ignore - // only ignore these kind of errors - if (!`${error}`.includes('file already exists')) { - throw error; - } - } + fs.symlinkSync(originalPath, newPath); } symlinkAsset(newPath, path.join(localOutPath, fileName)); @@ -49,12 +40,5 @@ function symlinkAsset(originalPath: string, targetPath: string): void { // ignore errors here } - try { - fs.linkSync(originalPath, targetPath); - } catch (error) { - // only ignore these kind of errors - if (!`${error}`.includes('file already exists')) { - throw error; - } - } + fs.linkSync(originalPath, targetPath); } diff --git a/dev-packages/bundle-analyzer-scenarios/package.json b/dev-packages/bundle-analyzer-scenarios/package.json index bcf269754bf9..a9f2c9265e72 100644 --- a/dev-packages/bundle-analyzer-scenarios/package.json +++ b/dev-packages/bundle-analyzer-scenarios/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/bundle-analyzer-scenarios", - "version": "8.25.0", + "version": "8.26.0", "description": "Scenarios to test bundle analysis with", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/dev-packages/bundle-analyzer-scenarios", diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index 2f4bd429ca17..69211cd141a8 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/e2e-tests", - "version": "8.25.0", + "version": "8.26.0", "license": "MIT", "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/astro-4/.gitignore b/dev-packages/e2e-tests/test-applications/astro-4/.gitignore new file mode 100644 index 000000000000..560782d47d98 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/.gitignore @@ -0,0 +1,26 @@ +# build output +dist/ + +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store + +# jetbrains setting folder +.idea/ + +test-results diff --git a/dev-packages/e2e-tests/test-applications/astro-4/.npmrc b/dev-packages/e2e-tests/test-applications/astro-4/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/.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/astro-4/README.md b/dev-packages/e2e-tests/test-applications/astro-4/README.md new file mode 100644 index 000000000000..28e41344b910 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/README.md @@ -0,0 +1,6 @@ +# Astro 4 E2E test app + +- Astro 4.x +- Output mode `hybrid` (== opt into SSR routes) +- Node adapter +- Configured for Tracing and Performance diff --git a/dev-packages/e2e-tests/test-applications/astro-4/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-4/astro.config.mjs new file mode 100644 index 000000000000..96db58759ba2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/astro.config.mjs @@ -0,0 +1,29 @@ +import node from '@astrojs/node'; +import sentry from '@sentry/astro'; +import { defineConfig } from 'astro/config'; + +import spotlightjs from '@spotlightjs/astro'; + +// https://astro.build/config +export default defineConfig({ + output: 'hybrid', + integrations: [ + sentry({ + debug: true, + sourceMapsUploadOptions: { + enabled: false, + }, + }), + spotlightjs(), + ], + adapter: node({ + mode: 'standalone', + }), + vite: { + build: { + rollupOptions: { + external: ['https'], + }, + }, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/astro-4/package.json b/dev-packages/e2e-tests/test-applications/astro-4/package.json new file mode 100644 index 000000000000..5bc5233ef7cc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/package.json @@ -0,0 +1,27 @@ +{ + "name": "astro-hybrid-with-static-routes", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev --force", + "start": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "astro": "astro", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:assert": "TEST_ENV=production playwright test" + }, + "dependencies": { + "@astrojs/check": "^0.9.2", + "@astrojs/node": "^8.3.2", + "@playwright/test": "^1.46.0", + "@sentry/astro": "* || latest", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@spotlightjs/astro": "^2.1.6", + "astro": "^4.13.3", + "typescript": "^5.5.4" + }, + "devDependencies": { + "@astrojs/internal-helpers": "^0.4.1" + } +} diff --git a/dev-packages/e2e-tests/test-applications/astro-4/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/astro-4/playwright.config.mjs new file mode 100644 index 000000000000..cd6ed611fb4a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/playwright.config.mjs @@ -0,0 +1,13 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const testEnv = process.env.TEST_ENV; + +if (!testEnv) { + throw new Error('No test env defined'); +} + +const config = getPlaywrightConfig({ + startCommand: 'node ./dist/server/entry.mjs', +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/astro-4/public/favicon.svg b/dev-packages/e2e-tests/test-applications/astro-4/public/favicon.svg new file mode 100644 index 000000000000..f157bd1c5e28 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/public/favicon.svg @@ -0,0 +1,9 @@ + + + + diff --git a/dev-packages/e2e-tests/test-applications/astro-4/sentry.client.config.js b/dev-packages/e2e-tests/test-applications/astro-4/sentry.client.config.js new file mode 100644 index 000000000000..2b79ec0ed337 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/sentry.client.config.js @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/astro'; + +Sentry.init({ + dsn: import.meta.env.PUBLIC_E2E_TEST_DSN, + environment: 'qa', + tracesSampleRate: 1.0, + tunnel: 'http://localhost:3031/', // proxy server +}); diff --git a/dev-packages/e2e-tests/test-applications/astro-4/sentry.server.config.js b/dev-packages/e2e-tests/test-applications/astro-4/sentry.server.config.js new file mode 100644 index 000000000000..0662d678dc7c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/sentry.server.config.js @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/astro'; + +Sentry.init({ + dsn: import.meta.env.PUBLIC_E2E_TEST_DSN, + environment: 'qa', + tracesSampleRate: 1.0, + spotlight: true, + tunnel: 'http://localhost:3031/', // proxy server +}); diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/env.d.ts b/dev-packages/e2e-tests/test-applications/astro-4/src/env.d.ts new file mode 100644 index 000000000000..f964fe0cffd8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/layouts/Layout.astro b/dev-packages/e2e-tests/test-applications/astro-4/src/layouts/Layout.astro new file mode 100644 index 000000000000..c4e54b834656 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/layouts/Layout.astro @@ -0,0 +1,39 @@ +--- +interface Props { + title: string; +} + +const { title } = Astro.props; +--- + + + + + + + + + + {title} + + + + + + diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/client-error/index.astro b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/client-error/index.astro new file mode 100644 index 000000000000..facd6f077a6e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/client-error/index.astro @@ -0,0 +1,11 @@ +--- +import Layout from "../../layouts/Layout.astro"; +--- + + + + + + diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/endpoint-error/api.ts b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/endpoint-error/api.ts new file mode 100644 index 000000000000..a76accdba010 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/endpoint-error/api.ts @@ -0,0 +1,15 @@ +import type { APIRoute } from 'astro'; + +export const prerender = false; + +export const GET: APIRoute = ({ request, url }) => { + if (url.searchParams.has('error')) { + throw new Error('Endpoint Error'); + } + return new Response( + JSON.stringify({ + search: url.search, + sp: url.searchParams, + }), + ); +}; diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/endpoint-error/index.astro b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/endpoint-error/index.astro new file mode 100644 index 000000000000..f025c76f8365 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/endpoint-error/index.astro @@ -0,0 +1,9 @@ +--- +import Layout from "../../layouts/Layout.astro"; + +export const prerender = false; +--- + + + + diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/index.astro b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/index.astro new file mode 100644 index 000000000000..088205fc4028 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/index.astro @@ -0,0 +1,36 @@ +--- +import Layout from '../layouts/Layout.astro'; +--- + + +
+

Astro E2E Test App

+ +
+
+ + diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/ssr-error/index.astro b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/ssr-error/index.astro new file mode 100644 index 000000000000..4ecb7466de70 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/ssr-error/index.astro @@ -0,0 +1,13 @@ +--- +import Layout from "../../layouts/Layout.astro"; + +const a = {} as any; +console.log(a.foo.x); +export const prerender = false; +--- + + + +

Page with SSR error

+ +
diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/test-ssr/index.astro b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/test-ssr/index.astro new file mode 100644 index 000000000000..58f5d80198d7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/test-ssr/index.astro @@ -0,0 +1,15 @@ +--- +import Layout from "../../layouts/Layout.astro" + +export const prerender = false +--- + + + +

+ This is a server page +

+ + + +
diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/test-static/index.astro b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/test-static/index.astro new file mode 100644 index 000000000000..f71bf00c9adf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/test-static/index.astro @@ -0,0 +1,15 @@ +--- +import Layout from "../../layouts/Layout.astro"; + +export const prerender = true; +--- + + + +

+ This is a static page +

+ + + +
diff --git a/dev-packages/e2e-tests/test-applications/astro-4/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/astro-4/start-event-proxy.mjs new file mode 100644 index 000000000000..a657dae0f425 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'astro-4', +}); diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.client.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.client.test.ts new file mode 100644 index 000000000000..4cbf4bf36604 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.client.test.ts @@ -0,0 +1,79 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test.describe('client-side errors', () => { + test('captures error thrown on click', async ({ page }) => { + const errorEventPromise = waitForError('astro-4', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'client error'; + }); + + await page.goto('/client-error'); + + await page.getByText('Throw Error').click(); + + const errorEvent = await errorEventPromise; + + const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; + + expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( + expect.objectContaining({ + colno: expect.any(Number), + lineno: expect.any(Number), + filename: expect.stringContaining('/client-error'), + function: 'HTMLButtonElement.onclick', + in_app: true, + }), + ); + + expect(errorEvent).toMatchObject({ + exception: { + values: [ + { + mechanism: { + handled: false, + type: 'onerror', + }, + type: 'Error', + value: 'client error', + stacktrace: expect.any(Object), // detailed check above + }, + ], + }, + level: 'error', + platform: 'javascript', + request: { + url: expect.stringContaining('/client-error'), + headers: { + 'User-Agent': expect.any(String), + }, + }, + event_id: expect.stringMatching(/[a-f0-9]{32}/), + timestamp: expect.any(Number), + sdk: { + integrations: expect.arrayContaining([ + 'InboundFilters', + 'FunctionToString', + 'BrowserApiErrors', + 'Breadcrumbs', + 'GlobalHandlers', + 'LinkedErrors', + 'Dedupe', + 'HttpContext', + 'BrowserTracing', + ]), + name: 'sentry.javascript.astro', + version: expect.any(String), + packages: expect.any(Array), + }, + transaction: '/client-error', + contexts: { + trace: { + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + }, + }, + environment: 'qa', + }); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.server.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.server.test.ts new file mode 100644 index 000000000000..d5f07ebe239a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.server.test.ts @@ -0,0 +1,115 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test.describe('server-side errors', () => { + test('captures SSR error', async ({ page }) => { + const errorEventPromise = waitForError('astro-4', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === "Cannot read properties of undefined (reading 'x')"; + }); + + await page.goto('/ssr-error'); + + const errorEvent = await errorEventPromise; + + expect(errorEvent).toMatchObject({ + contexts: { + app: expect.any(Object), + cloud_resource: expect.any(Object), + culture: expect.any(Object), + device: expect.any(Object), + os: expect.any(Object), + runtime: expect.any(Object), + trace: { + span_id: '', //TODO: This is a bug! We should expect.stringMatching(/[a-f0-9]{16}/) instead of '' + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + environment: 'qa', + event_id: expect.stringMatching(/[a-f0-9]{32}/), + exception: { + values: [ + { + mechanism: { + data: { + function: 'astroMiddleware', + }, + handled: false, + type: 'astro', + }, + stacktrace: expect.any(Object), + type: 'TypeError', + value: "Cannot read properties of undefined (reading 'x')", + }, + ], + }, + platform: 'node', + request: { + cookies: {}, + headers: expect.objectContaining({ + // demonstrates that requestData integration is getting data + host: 'localhost:3030', + 'user-agent': expect.any(String), + }), + method: 'GET', + url: expect.stringContaining('/ssr-error'), + }, + sdk: { + integrations: expect.any(Array), + name: 'sentry.javascript.astro', + packages: expect.any(Array), + version: expect.any(String), + }, + server_name: expect.any(String), + timestamp: expect.any(Number), + transaction: 'GET /ssr-error', + }); + }); + + test('captures endpoint error', async ({ page }) => { + const errorEventPromise = waitForError('astro-4', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'Endpoint Error'; + }); + + await page.goto('/endpoint-error'); + await page.getByText('Get Data').click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent).toMatchObject({ + contexts: { + trace: { + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + exception: { + values: [ + { + mechanism: { + data: { + function: 'astroMiddleware', + }, + handled: false, + type: 'astro', + }, + stacktrace: expect.any(Object), + type: 'Error', + value: 'Endpoint Error', + }, + ], + }, + platform: 'node', + request: { + cookies: {}, + headers: expect.objectContaining({ + accept: expect.any(String), + }), + method: 'GET', + query_string: 'error=1', + url: expect.stringContaining('endpoint-error/api?error=1'), + }, + transaction: 'GET /endpoint-error/api', + }); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts new file mode 100644 index 000000000000..9a295f677d96 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts @@ -0,0 +1,123 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test.describe('tracing in dynamically rendered (ssr) routes', () => { + test('sends server and client pageload spans with the same trace id', async ({ page }) => { + const clientPageloadTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction === '/test-ssr'; + }); + + const serverPageRequestTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction === 'GET /test-ssr'; + }); + + await page.goto('/test-ssr'); + + const clientPageloadTxn = await clientPageloadTxnPromise; + const serverPageRequestTxn = await serverPageRequestTxnPromise; + + const clientPageloadTraceId = clientPageloadTxn.contexts?.trace?.trace_id; + const clientPageloadParentSpanId = clientPageloadTxn.contexts?.trace?.parent_span_id; + + const serverPageRequestTraceId = serverPageRequestTxn.contexts?.trace?.trace_id; + const serverPageloadSpanId = serverPageRequestTxn.contexts?.trace?.span_id; + + expect(clientPageloadTraceId).toEqual(serverPageRequestTraceId); + expect(clientPageloadParentSpanId).toEqual(serverPageloadSpanId); + + expect(clientPageloadTxn).toMatchObject({ + contexts: { + trace: { + data: expect.objectContaining({ + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.browser', + 'sentry.sample_rate': 1, + 'sentry.source': 'url', + }), + op: 'pageload', + origin: 'auto.pageload.browser', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + environment: 'qa', + event_id: expect.stringMatching(/[a-f0-9]{32}/), + measurements: expect.any(Object), + platform: 'javascript', + request: expect.any(Object), + sdk: { + integrations: expect.any(Array), + name: 'sentry.javascript.astro', + packages: expect.any(Array), + version: expect.any(String), + }, + spans: expect.any(Array), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + transaction: '/test-ssr', + transaction_info: { + source: 'url', + }, + type: 'transaction', + }); + + expect(serverPageRequestTxn).toMatchObject({ + breadcrumbs: expect.any(Array), + contexts: { + app: expect.any(Object), + cloud_resource: expect.any(Object), + culture: expect.any(Object), + device: expect.any(Object), + os: expect.any(Object), + otel: expect.any(Object), + runtime: expect.any(Object), + trace: { + data: { + 'http.response.status_code': 200, + method: 'GET', + 'sentry.op': 'http.server', + 'sentry.origin': 'auto.http.astro', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + url: expect.stringContaining('/test-ssr'), + }, + op: 'http.server', + origin: 'auto.http.astro', + status: 'ok', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + environment: 'qa', + event_id: expect.stringMatching(/[a-f0-9]{32}/), + platform: 'node', + request: { + cookies: {}, + headers: expect.objectContaining({ + // demonstrates that request data integration can extract headers + accept: expect.any(String), + 'accept-encoding': expect.any(String), + 'user-agent': expect.any(String), + }), + method: 'GET', + url: expect.stringContaining('/test-ssr'), + }, + sdk: { + integrations: expect.any(Array), + name: 'sentry.javascript.astro', + packages: expect.any(Array), + version: expect.any(String), + }, + server_name: expect.any(String), + spans: expect.any(Array), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + transaction: 'GET /test-ssr', + transaction_info: { + source: 'route', + }, + type: 'transaction', + }); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.static.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.static.test.ts new file mode 100644 index 000000000000..8817b2b22aa7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.static.test.ts @@ -0,0 +1,62 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test.describe('tracing in static/pre-rendered routes', () => { + test('only sends client pageload span with traceId from pre-rendered tags', async ({ page }) => { + const clientPageloadTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction === '/test-static'; + }); + + waitForTransaction('astro-4', evt => { + if (evt.platform !== 'javascript') { + throw new Error('Server transaction should not be sent'); + } + return false; + }); + + await page.goto('/test-static'); + + const clientPageloadTxn = await clientPageloadTxnPromise; + + const clientPageloadTraceId = clientPageloadTxn.contexts?.trace?.trace_id; + const clientPageloadParentSpanId = clientPageloadTxn.contexts?.trace?.parent_span_id; + + const sentryTraceMetaTagContent = await page.locator('meta[name="sentry-trace"]').getAttribute('content'); + const baggageMetaTagContent = await page.locator('meta[name="baggage"]').getAttribute('content'); + + const [metaTraceId, metaParentSpanId, metaSampled] = sentryTraceMetaTagContent?.split('-') || []; + + expect(clientPageloadTraceId).toMatch(/[a-f0-9]{32}/); + expect(clientPageloadParentSpanId).toMatch(/[a-f0-9]{16}/); + expect(metaSampled).toBe('1'); + + expect(clientPageloadTxn).toMatchObject({ + contexts: { + trace: { + data: expect.objectContaining({ + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.browser', + 'sentry.sample_rate': 1, + 'sentry.source': 'url', + }), + op: 'pageload', + origin: 'auto.pageload.browser', + parent_span_id: metaParentSpanId, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: metaTraceId, + }, + }, + platform: 'javascript', + transaction: '/test-static', + transaction_info: { + source: 'url', + }, + type: 'transaction', + }); + + expect(baggageMetaTagContent).toContain('sentry-transaction=GET%20%2Ftest-static%2F'); // URL-encoded for 'GET /test-static/' + expect(baggageMetaTagContent).toContain('sentry-sampled=true'); + + await page.waitForTimeout(1000); // wait another sec to ensure no server transaction is sent + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tsconfig.json b/dev-packages/e2e-tests/test-applications/astro-4/tsconfig.json new file mode 100644 index 000000000000..77da9dd00982 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/strict" +} \ No newline at end of file diff --git a/dev-packages/e2e-tests/test-applications/default-browser/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/default-browser/tests/performance.test.ts index 7013fb43ecef..0407d9965389 100644 --- a/dev-packages/e2e-tests/test-applications/default-browser/tests/performance.test.ts +++ b/dev-packages/e2e-tests/test-applications/default-browser/tests/performance.test.ts @@ -10,7 +10,7 @@ test('captures a pageload transaction', async ({ page }) => { const pageLoadTransaction = await transactionPromise; - expect(pageLoadTransaction).toEqual({ + expect(pageLoadTransaction).toMatchObject({ contexts: { trace: { data: expect.objectContaining({ @@ -28,32 +28,7 @@ test('captures a pageload transaction', async ({ page }) => { }, environment: 'qa', event_id: expect.stringMatching(/[a-f0-9]{32}/), - measurements: { - 'connection.rtt': { - unit: 'millisecond', - value: expect.any(Number), - }, - fcp: { - unit: 'millisecond', - value: expect.any(Number), - }, - fp: { - unit: 'millisecond', - value: expect.any(Number), - }, - lcp: { - unit: 'millisecond', - value: expect.any(Number), - }, - ttfb: { - unit: 'millisecond', - value: expect.any(Number), - }, - 'ttfb.requestTime': { - unit: 'millisecond', - value: expect.any(Number), - }, - }, + measurements: expect.any(Object), platform: 'javascript', release: 'e2e-test', request: { diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts index ec0a921da2c4..9cda3c96f9a6 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts @@ -1,10 +1,14 @@ -import { Controller, Get, Param, ParseIntPipe, UseGuards, UseInterceptors } from '@nestjs/common'; +import { Controller, Get, Param, ParseIntPipe, UseFilters, UseGuards, UseInterceptors } from '@nestjs/common'; import { flush } from '@sentry/nestjs'; import { AppService } from './app.service'; +import { ExampleExceptionGlobalFilter } from './example-global-filter.exception'; +import { ExampleExceptionLocalFilter } from './example-local-filter.exception'; +import { ExampleLocalFilter } from './example-local.filter'; import { ExampleGuard } from './example.guard'; import { ExampleInterceptor } from './example.interceptor'; @Controller() +@UseFilters(ExampleLocalFilter) export class AppController { constructor(private readonly appService: AppService) {} @@ -74,4 +78,14 @@ export class AppController { async flush() { await flush(); } + + @Get('example-exception-global-filter') + async exampleExceptionGlobalFilter() { + throw new ExampleExceptionGlobalFilter(); + } + + @Get('example-exception-local-filter') + async exampleExceptionLocalFilter() { + throw new ExampleExceptionLocalFilter(); + } } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts index b2aad014c745..3de3c82dc925 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts @@ -1,14 +1,26 @@ import { MiddlewareConsumer, Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; import { ScheduleModule } from '@nestjs/schedule'; -import { SentryModule } from '@sentry/nestjs/setup'; +import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { ExampleGlobalFilter } from './example-global.filter'; import { ExampleMiddleware } from './example.middleware'; @Module({ imports: [SentryModule.forRoot(), ScheduleModule.forRoot()], controllers: [AppController], - providers: [AppService], + providers: [ + AppService, + { + provide: APP_FILTER, + useClass: SentryGlobalFilter, + }, + { + provide: APP_FILTER, + useClass: ExampleGlobalFilter, + }, + ], }) export class AppModule { configure(consumer: MiddlewareConsumer): void { diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-global-filter.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-global-filter.exception.ts new file mode 100644 index 000000000000..41981ba748fe --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-global-filter.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionGlobalFilter extends Error { + constructor() { + super('Original global example exception!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-global.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-global.filter.ts new file mode 100644 index 000000000000..988696d0e13d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-global.filter.ts @@ -0,0 +1,19 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common'; +import { Request, Response } from 'express'; +import { ExampleExceptionGlobalFilter } from './example-global-filter.exception'; + +@Catch(ExampleExceptionGlobalFilter) +export class ExampleGlobalFilter implements ExceptionFilter { + catch(exception: BadRequestException, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + response.status(400).json({ + statusCode: 400, + timestamp: new Date().toISOString(), + path: request.url, + message: 'Example exception was handled by global filter!', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-local-filter.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-local-filter.exception.ts new file mode 100644 index 000000000000..8f76520a3b94 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-local-filter.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionLocalFilter extends Error { + constructor() { + super('Original local example exception!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-local.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-local.filter.ts new file mode 100644 index 000000000000..505217f5dcbd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/example-local.filter.ts @@ -0,0 +1,19 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common'; +import { Request, Response } from 'express'; +import { ExampleExceptionLocalFilter } from './example-local-filter.exception'; + +@Catch(ExampleExceptionLocalFilter) +export class ExampleLocalFilter implements ExceptionFilter { + catch(exception: BadRequestException, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + response.status(400).json({ + statusCode: 400, + timestamp: new Date().toISOString(), + path: request.url, + message: 'Example exception was handled by local filter!', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts index 34e626cb8c52..ee7d12dd22ca 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts @@ -94,3 +94,73 @@ test('Does not send RpcExceptions to Sentry', async ({ baseURL }) => { expect(errorEventOccurred).toBe(false); }); + +test('Global exception filter registered in main module is applied and exception is not sent to Sentry', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-basic', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by global filter!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-exception-global-filter'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-basic', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-exception-global-filter'; + }); + + const response = await fetch(`${baseURL}/example-exception-global-filter`); + const responseBody = await response.json(); + + expect(response.status).toBe(400); + expect(responseBody).toEqual({ + statusCode: 400, + timestamp: expect.any(String), + path: '/example-exception-global-filter', + message: 'Example exception was handled by global filter!', + }); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Local exception filter registered in main module is applied and exception is not sent to Sentry', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-basic', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by local filter!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-exception-local-filter'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-basic', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-exception-local-filter'; + }); + + const response = await fetch(`${baseURL}/example-exception-local-filter`); + const responseBody = await response.json(); + + expect(response.status).toBe(400); + expect(responseBody).toEqual({ + statusCode: 400, + timestamp: expect.any(String), + path: '/example-exception-local-filter', + message: 'Example exception was handled by local filter!', + }); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/.gitignore b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/.gitignore new file mode 100644 index 000000000000..4b56acfbebf4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/.npmrc b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/.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/nestjs-with-submodules-decorator/nest-cli.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/nest-cli.json new file mode 100644 index 000000000000..f9aa683b1ad5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json new file mode 100644 index 000000000000..9cc3641d2322 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json @@ -0,0 +1,47 @@ +{ + "name": "nestjs-with-submodules-decorator", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test": "playwright test", + "test:build": "pnpm install", + "test:assert": "pnpm test" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@sentry/nestjs": "latest || *", + "@sentry/types": "latest || *", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/node": "18.15.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^6.3.3", + "ts-loader": "^9.4.3", + "tsconfig-paths": "^4.2.0", + "typescript": "^4.9.5" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/playwright.config.mjs @@ -0,0 +1,7 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/app.controller.ts new file mode 100644 index 000000000000..efce824a20c3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/app.controller.ts @@ -0,0 +1,37 @@ +import { Controller, Get, Param, UseFilters } from '@nestjs/common'; +import { flush } from '@sentry/nestjs'; +import { AppService } from './app.service'; +import { ExampleExceptionLocalFilter } from './example-local.exception'; +import { ExampleLocalFilter } from './example-local.filter'; +import { ExampleExceptionSpecificFilter } from './example-specific.exception'; + +@Controller() +@UseFilters(ExampleLocalFilter) +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get('test-exception/:id') + async testException(@Param('id') id: string) { + return this.appService.testException(id); + } + + @Get('test-expected-exception/:id') + async testExpectedException(@Param('id') id: string) { + return this.appService.testExpectedException(id); + } + + @Get('flush') + async flush() { + await flush(); + } + + @Get('example-exception-specific-filter') + async exampleExceptionGlobalFilter() { + throw new ExampleExceptionSpecificFilter(); + } + + @Get('example-exception-local-filter') + async exampleExceptionLocalFilter() { + throw new ExampleExceptionLocalFilter(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/app.module.ts new file mode 100644 index 000000000000..77cf85a4fa9c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/app.module.ts @@ -0,0 +1,32 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { SentryModule } from '@sentry/nestjs/setup'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { ExampleWrappedGlobalFilter } from './example-global.filter'; +import { ExampleModuleGlobalFilterRegisteredFirst } from './example-module-global-filter-registered-first/example.module'; +import { ExampleModuleGlobalFilter } from './example-module-global-filter/example.module'; +import { ExampleModuleLocalFilter } from './example-module-local-filter/example.module'; +import { ExampleSpecificFilter } from './example-specific.filter'; + +@Module({ + imports: [ + ExampleModuleGlobalFilterRegisteredFirst, + SentryModule.forRoot(), + ExampleModuleGlobalFilter, + ExampleModuleLocalFilter, + ], + controllers: [AppController], + providers: [ + AppService, + { + provide: APP_FILTER, + useClass: ExampleWrappedGlobalFilter, + }, + { + provide: APP_FILTER, + useClass: ExampleSpecificFilter, + }, + ], +}) +export class AppModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/app.service.ts new file mode 100644 index 000000000000..242408023586 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/app.service.ts @@ -0,0 +1,14 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + constructor() {} + + testException(id: string) { + throw new Error(`This is an exception with id ${id}`); + } + + testExpectedException(id: string) { + throw new HttpException(`This is an expected exception with id ${id}`, HttpStatus.FORBIDDEN); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-global.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-global.filter.ts new file mode 100644 index 000000000000..cee50d0d2c7c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-global.filter.ts @@ -0,0 +1,20 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common'; +import { WithSentry } from '@sentry/nestjs'; +import { Request, Response } from 'express'; + +@Catch() +export class ExampleWrappedGlobalFilter implements ExceptionFilter { + @WithSentry() + catch(exception: BadRequestException, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + response.status(501).json({ + statusCode: 501, + timestamp: new Date().toISOString(), + path: request.url, + message: 'Example exception was handled by global filter!', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-local.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-local.exception.ts new file mode 100644 index 000000000000..8f76520a3b94 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-local.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionLocalFilter extends Error { + constructor() { + super('Original local example exception!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-local.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-local.filter.ts new file mode 100644 index 000000000000..0e93e5f7fac2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-local.filter.ts @@ -0,0 +1,19 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common'; +import { Request, Response } from 'express'; +import { ExampleExceptionLocalFilter } from './example-local.exception'; + +@Catch(ExampleExceptionLocalFilter) +export class ExampleLocalFilter implements ExceptionFilter { + catch(exception: BadRequestException, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + response.status(400).json({ + statusCode: 400, + timestamp: new Date().toISOString(), + path: request.url, + message: 'Example exception was handled by local filter!', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.controller.ts similarity index 63% rename from dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.controller.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.controller.ts index 028af4a43f87..967886948a25 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.controller.ts @@ -1,13 +1,13 @@ import { Controller, Get } from '@nestjs/common'; -import { ExampleExceptionWrongRegistrationOrder } from './example.exception'; +import { ExampleExceptionRegisteredFirst } from './example.exception'; -@Controller('example-module-wrong-order') +@Controller('example-module-registered-first') export class ExampleController { constructor() {} @Get('/expected-exception') getCaughtException(): string { - throw new ExampleExceptionWrongRegistrationOrder(); + throw new ExampleExceptionRegisteredFirst(); } @Get('/unexpected-exception') diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.exception.ts similarity index 54% rename from dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.exception.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.exception.ts index 0e4f58314fa2..9bb3b5948343 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.exception.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.exception.ts @@ -1,4 +1,4 @@ -export class ExampleExceptionWrongRegistrationOrder extends Error { +export class ExampleExceptionRegisteredFirst extends Error { constructor() { super('Something went wrong in the example module!'); } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.filter.ts similarity index 52% rename from dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.filter.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.filter.ts index 6ecdf88937aa..6c3b81d395b5 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.filter.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.filter.ts @@ -1,11 +1,11 @@ import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; -import { ExampleExceptionWrongRegistrationOrder } from './example.exception'; +import { ExampleExceptionRegisteredFirst } from './example.exception'; -@Catch(ExampleExceptionWrongRegistrationOrder) -export class ExampleExceptionFilterWrongRegistrationOrder extends BaseExceptionFilter { +@Catch(ExampleExceptionRegisteredFirst) +export class ExampleExceptionFilterRegisteredFirst extends BaseExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { - if (exception instanceof ExampleExceptionWrongRegistrationOrder) { + if (exception instanceof ExampleExceptionRegisteredFirst) { return super.catch(new BadRequestException(exception.message), host); } return super.catch(exception, host); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.module.ts similarity index 56% rename from dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.module.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.module.ts index c98a5757b01c..8751856f99cd 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter-registered-first/example.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { ExampleController } from './example.controller'; -import { ExampleExceptionFilterWrongRegistrationOrder } from './example.filter'; +import { ExampleExceptionFilterRegisteredFirst } from './example.filter'; @Module({ imports: [], @@ -9,8 +9,8 @@ import { ExampleExceptionFilterWrongRegistrationOrder } from './example.filter'; providers: [ { provide: APP_FILTER, - useClass: ExampleExceptionFilterWrongRegistrationOrder, + useClass: ExampleExceptionFilterRegisteredFirst, }, ], }) -export class ExampleModuleGlobalFilterWrongRegistrationOrder {} +export class ExampleModuleGlobalFilterRegisteredFirst {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.controller.ts new file mode 100644 index 000000000000..53356e906130 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; +import { ExampleException } from './example.exception'; + +@Controller('example-module') +export class ExampleController { + constructor() {} + + @Get('/expected-exception') + getCaughtException(): string { + throw new ExampleException(); + } + + @Get('/unexpected-exception') + getUncaughtException(): string { + throw new Error(`This is an uncaught exception!`); + } + + @Get('/transaction') + testTransaction() { + Sentry.startSpan({ name: 'test-span' }, () => { + Sentry.startSpan({ name: 'child-span' }, () => {}); + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.exception.ts new file mode 100644 index 000000000000..ac43dddfa8dc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.exception.ts @@ -0,0 +1,5 @@ +export class ExampleException extends Error { + constructor() { + super('Something went wrong in the example module!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.filter.ts new file mode 100644 index 000000000000..848441caf855 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.filter.ts @@ -0,0 +1,13 @@ +import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { ExampleException } from './example.exception'; + +@Catch(ExampleException) +export class ExampleExceptionFilter extends BaseExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + if (exception instanceof ExampleException) { + return super.catch(new BadRequestException(exception.message), host); + } + return super.catch(exception, host); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.module.ts new file mode 100644 index 000000000000..8052cb137aac --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-global-filter/example.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { ExampleController } from './example.controller'; +import { ExampleExceptionFilter } from './example.filter'; + +@Module({ + imports: [], + controllers: [ExampleController], + providers: [ + { + provide: APP_FILTER, + useClass: ExampleExceptionFilter, + }, + ], +}) +export class ExampleModuleGlobalFilter {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.controller.ts new file mode 100644 index 000000000000..11a0470ace17 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.controller.ts @@ -0,0 +1,19 @@ +import { Controller, Get, UseFilters } from '@nestjs/common'; +import { LocalExampleException } from './example.exception'; +import { LocalExampleExceptionFilter } from './example.filter'; + +@Controller('example-module-local-filter') +@UseFilters(LocalExampleExceptionFilter) +export class ExampleControllerLocalFilter { + constructor() {} + + @Get('/expected-exception') + getCaughtException() { + throw new LocalExampleException(); + } + + @Get('/unexpected-exception') + getUncaughtException(): string { + throw new Error(`This is an uncaught exception!`); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.exception.ts new file mode 100644 index 000000000000..85a64d26d75e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.exception.ts @@ -0,0 +1,5 @@ +export class LocalExampleException extends Error { + constructor() { + super('Something went wrong in the example module with local filter!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.filter.ts new file mode 100644 index 000000000000..9b6797c95e44 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.filter.ts @@ -0,0 +1,13 @@ +import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { LocalExampleException } from './example.exception'; + +@Catch(LocalExampleException) +export class LocalExampleExceptionFilter extends BaseExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + if (exception instanceof LocalExampleException) { + return super.catch(new BadRequestException(exception.message), host); + } + return super.catch(exception, host); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.module.ts new file mode 100644 index 000000000000..91d229a553c1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-module-local-filter/example.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ExampleControllerLocalFilter } from './example.controller'; + +@Module({ + imports: [], + controllers: [ExampleControllerLocalFilter], + providers: [], +}) +export class ExampleModuleLocalFilter {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-specific.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-specific.exception.ts new file mode 100644 index 000000000000..a26cb1a26d52 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-specific.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionSpecificFilter extends Error { + constructor() { + super('Original specific example exception!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-specific.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-specific.filter.ts new file mode 100644 index 000000000000..bf85385a9a86 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/example-specific.filter.ts @@ -0,0 +1,19 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common'; +import { Request, Response } from 'express'; +import { ExampleExceptionSpecificFilter } from './example-specific.exception'; + +@Catch(ExampleExceptionSpecificFilter) +export class ExampleSpecificFilter implements ExceptionFilter { + catch(exception: BadRequestException, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + response.status(400).json({ + statusCode: 400, + timestamp: new Date().toISOString(), + path: request.url, + message: 'Example exception was handled by specific filter!', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/instrument.ts new file mode 100644 index 000000000000..4f16ebb36d11 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/instrument.ts @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/nestjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1, + transportOptions: { + // We expect the app to send a lot of events in a short time + bufferSize: 1000, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/main.ts new file mode 100644 index 000000000000..71ce685f4d61 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/src/main.ts @@ -0,0 +1,15 @@ +// Import this first +import './instrument'; + +// Import other modules +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +const PORT = 3030; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(PORT); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/start-event-proxy.mjs new file mode 100644 index 000000000000..3c205b59a2d1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nestjs-with-submodules-decorator', +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/errors.test.ts new file mode 100644 index 000000000000..94c742dd210a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/errors.test.ts @@ -0,0 +1,266 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends unexpected exception to Sentry if thrown in module with global filter', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs-with-submodules-decorator', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an uncaught exception!'; + }); + + const response = await fetch(`${baseURL}/example-module/unexpected-exception`); + const responseBody = await response.json(); + + expect(response.status).toBe(501); + expect(responseBody).toEqual({ + statusCode: 501, + timestamp: expect.any(String), + path: '/example-module/unexpected-exception', + message: 'Example exception was handled by global filter!', + }); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an uncaught exception!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/example-module/unexpected-exception', + }); + + expect(errorEvent.transaction).toEqual('GET /example-module/unexpected-exception'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + +test('Sends unexpected exception to Sentry if thrown in module with local filter', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs-with-submodules-decorator', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an uncaught exception!'; + }); + + const response = await fetch(`${baseURL}/example-module-local-filter/unexpected-exception`); + const responseBody = await response.json(); + + expect(response.status).toBe(501); + expect(responseBody).toEqual({ + statusCode: 501, + timestamp: expect.any(String), + path: '/example-module-local-filter/unexpected-exception', + message: 'Example exception was handled by global filter!', + }); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an uncaught exception!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/example-module-local-filter/unexpected-exception', + }); + + expect(errorEvent.transaction).toEqual('GET /example-module-local-filter/unexpected-exception'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + +test('Sends unexpected exception to Sentry if thrown in module that was registered before Sentry', async ({ + baseURL, +}) => { + const errorEventPromise = waitForError('nestjs-with-submodules-decorator', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an uncaught exception!'; + }); + + const response = await fetch(`${baseURL}/example-module-registered-first/unexpected-exception`); + const responseBody = await response.json(); + + expect(response.status).toBe(501); + expect(responseBody).toEqual({ + statusCode: 501, + timestamp: expect.any(String), + path: '/example-module-registered-first/unexpected-exception', + message: 'Example exception was handled by global filter!', + }); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an uncaught exception!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/example-module-registered-first/unexpected-exception', + }); + + expect(errorEvent.transaction).toEqual('GET /example-module-registered-first/unexpected-exception'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + +test('Does not send exception to Sentry if user-defined global exception filter already catches the exception', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-with-submodules-decorator', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the example module!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-module/expected-exception'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-with-submodules-decorator', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-module/expected-exception'; + }); + + const response = await fetch(`${baseURL}/example-module/expected-exception`); + expect(response.status).toBe(400); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Does not send exception to Sentry if user-defined local exception filter already catches the exception', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-with-submodules-decorator', event => { + if ( + !event.type && + event.exception?.values?.[0]?.value === 'Something went wrong in the example module with local filter!' + ) { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-module-local-filter/expected-exception'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-with-submodules-decorator', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-module-local-filter/expected-exception'; + }); + + const response = await fetch(`${baseURL}/example-module-local-filter/expected-exception`); + expect(response.status).toBe(400); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Does not send expected exception to Sentry if exception is thrown in module registered before Sentry', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-with-submodules-decorator', event => { + if (!event.type && event.exception?.values?.[0].value === 'Something went wrong in the example module!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-module-registered-first/expected-exception'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-with-submodules-decorator', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-module-registered-first/expected-exception'; + }); + + const response = await fetch(`${baseURL}/example-module-registered-first/expected-exception`); + expect(response.status).toBe(400); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Global specific exception filter registered in main module is applied and exception is not sent to Sentry', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-with-submodules-decorator', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by specific filter!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-exception-specific-filter'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-with-submodules-decorator', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-exception-specific-filter'; + }); + + const response = await fetch(`${baseURL}/example-exception-specific-filter`); + const responseBody = await response.json(); + + expect(response.status).toBe(400); + expect(responseBody).toEqual({ + statusCode: 400, + timestamp: expect.any(String), + path: '/example-exception-specific-filter', + message: 'Example exception was handled by specific filter!', + }); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Local specific exception filter registered in main module is applied and exception is not sent to Sentry', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-with-submodules-decorator', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by local filter!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-exception-local-filter'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-with-submodules-decorator', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-exception-local-filter'; + }); + + const response = await fetch(`${baseURL}/example-exception-local-filter`); + const responseBody = await response.json(); + + expect(response.status).toBe(400); + expect(responseBody).toEqual({ + statusCode: 400, + timestamp: expect.any(String), + path: '/example-exception-local-filter', + message: 'Example exception was handled by local filter!', + }); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts new file mode 100644 index 000000000000..740ab6f5650c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts @@ -0,0 +1,240 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends an API route transaction from module', async ({ baseURL }) => { + const pageloadTransactionEventPromise = waitForTransaction('nestjs-with-submodules-decorator', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /example-module/transaction' + ); + }); + + await fetch(`${baseURL}/example-module/transaction`); + + const transactionEvent = await pageloadTransactionEventPromise; + + expect(transactionEvent.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: 'http://localhost:3030/example-module/transaction', + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': 'http://localhost:3030/example-module/transaction', + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': '/example-module/transaction', + 'http.user_agent': 'node', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/example-module/transaction', + }, + op: 'http.server', + span_id: expect.any(String), + status: 'ok', + trace_id: expect.any(String), + origin: 'auto.http.otel.http', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + data: { + 'express.name': '/example-module/transaction', + 'express.type': 'request_handler', + 'http.route': '/example-module/transaction', + 'sentry.origin': 'auto.http.otel.express', + 'sentry.op': 'request_handler.express', + }, + op: 'request_handler.express', + description: '/example-module/transaction', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.http.otel.express', + }, + { + data: { + 'sentry.origin': 'manual', + }, + description: 'test-span', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'manual', + }, + { + data: { + 'sentry.origin': 'manual', + }, + description: 'child-span', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'manual', + }, + { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.origin': 'auto.http.otel.nestjs', + 'sentry.op': 'handler.nestjs', + component: '@nestjs/core', + 'nestjs.version': expect.any(String), + 'nestjs.type': 'handler', + 'nestjs.callback': 'testTransaction', + }, + description: 'testTransaction', + parent_span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'auto.http.otel.nestjs', + op: 'handler.nestjs', + }, + ]), + transaction: 'GET /example-module/transaction', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); +}); + +test('API route transaction includes exception filter span for global filter in module registered after Sentry', async ({ + baseURL, +}) => { + const transactionEventPromise = waitForTransaction('nestjs-with-submodules-decorator', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /example-module/expected-exception' && + transactionEvent?.request?.url?.includes('/example-module/expected-exception') + ); + }); + + const response = await fetch(`${baseURL}/example-module/expected-exception`); + expect(response.status).toBe(400); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'ExampleExceptionFilter', + parent_span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); +}); + +test('API route transaction includes exception filter span for local filter in module registered after Sentry', async ({ + baseURL, +}) => { + const transactionEventPromise = waitForTransaction('nestjs-with-submodules-decorator', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /example-module-local-filter/expected-exception' && + transactionEvent?.request?.url?.includes('/example-module-local-filter/expected-exception') + ); + }); + + const response = await fetch(`${baseURL}/example-module-local-filter/expected-exception`); + expect(response.status).toBe(400); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'LocalExampleExceptionFilter', + parent_span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); +}); + +test('API route transaction includes exception filter span for global filter in module registered before Sentry', async ({ + baseURL, +}) => { + const transactionEventPromise = waitForTransaction('nestjs-with-submodules-decorator', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /example-module-registered-first/expected-exception' && + transactionEvent?.request?.url?.includes('/example-module-registered-first/expected-exception') + ); + }); + + const response = await fetch(`${baseURL}/example-module-registered-first/expected-exception`); + expect(response.status).toBe(400); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'ExampleExceptionFilterRegisteredFirst', + parent_span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tsconfig.build.json new file mode 100644 index 000000000000..26c30d4eddf2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist"] +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tsconfig.json new file mode 100644 index 000000000000..95f5641cf7f3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts index 0d2c46e90da2..efce824a20c3 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts @@ -1,8 +1,12 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Controller, Get, Param, UseFilters } from '@nestjs/common'; import { flush } from '@sentry/nestjs'; import { AppService } from './app.service'; +import { ExampleExceptionLocalFilter } from './example-local.exception'; +import { ExampleLocalFilter } from './example-local.filter'; +import { ExampleExceptionSpecificFilter } from './example-specific.exception'; @Controller() +@UseFilters(ExampleLocalFilter) export class AppController { constructor(private readonly appService: AppService) {} @@ -20,4 +24,14 @@ export class AppController { async flush() { await flush(); } + + @Get('example-exception-specific-filter') + async exampleExceptionGlobalFilter() { + throw new ExampleExceptionSpecificFilter(); + } + + @Get('example-exception-local-filter') + async exampleExceptionLocalFilter() { + throw new ExampleExceptionLocalFilter(); + } } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index 212b17a3556b..2a93747ac075 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -1,19 +1,31 @@ import { Module } from '@nestjs/common'; -import { SentryModule } from '@sentry/nestjs/setup'; +import { APP_FILTER } from '@nestjs/core'; +import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { ExampleModuleGlobalFilterWrongRegistrationOrder } from './example-module-global-filter-wrong-registration-order/example.module'; +import { ExampleModuleGlobalFilterRegisteredFirst } from './example-module-global-filter-registered-first/example.module'; import { ExampleModuleGlobalFilter } from './example-module-global-filter/example.module'; import { ExampleModuleLocalFilter } from './example-module-local-filter/example.module'; +import { ExampleSpecificFilter } from './example-specific.filter'; @Module({ imports: [ - ExampleModuleGlobalFilterWrongRegistrationOrder, + ExampleModuleGlobalFilterRegisteredFirst, SentryModule.forRoot(), ExampleModuleGlobalFilter, ExampleModuleLocalFilter, ], controllers: [AppController], - providers: [AppService], + providers: [ + AppService, + { + provide: APP_FILTER, + useClass: SentryGlobalFilter, + }, + { + provide: APP_FILTER, + useClass: ExampleSpecificFilter, + }, + ], }) export class AppModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-local.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-local.exception.ts new file mode 100644 index 000000000000..8f76520a3b94 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-local.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionLocalFilter extends Error { + constructor() { + super('Original local example exception!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-local.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-local.filter.ts new file mode 100644 index 000000000000..0e93e5f7fac2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-local.filter.ts @@ -0,0 +1,19 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common'; +import { Request, Response } from 'express'; +import { ExampleExceptionLocalFilter } from './example-local.exception'; + +@Catch(ExampleExceptionLocalFilter) +export class ExampleLocalFilter implements ExceptionFilter { + catch(exception: BadRequestException, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + response.status(400).json({ + statusCode: 400, + timestamp: new Date().toISOString(), + path: request.url, + message: 'Example exception was handled by local filter!', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.controller.ts new file mode 100644 index 000000000000..967886948a25 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get } from '@nestjs/common'; +import { ExampleExceptionRegisteredFirst } from './example.exception'; + +@Controller('example-module-registered-first') +export class ExampleController { + constructor() {} + + @Get('/expected-exception') + getCaughtException(): string { + throw new ExampleExceptionRegisteredFirst(); + } + + @Get('/unexpected-exception') + getUncaughtException(): string { + throw new Error(`This is an uncaught exception!`); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.exception.ts new file mode 100644 index 000000000000..9bb3b5948343 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionRegisteredFirst extends Error { + constructor() { + super('Something went wrong in the example module!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.filter.ts new file mode 100644 index 000000000000..6c3b81d395b5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.filter.ts @@ -0,0 +1,13 @@ +import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { ExampleExceptionRegisteredFirst } from './example.exception'; + +@Catch(ExampleExceptionRegisteredFirst) +export class ExampleExceptionFilterRegisteredFirst extends BaseExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + if (exception instanceof ExampleExceptionRegisteredFirst) { + return super.catch(new BadRequestException(exception.message), host); + } + return super.catch(exception, host); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.module.ts new file mode 100644 index 000000000000..8751856f99cd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-registered-first/example.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { ExampleController } from './example.controller'; +import { ExampleExceptionFilterRegisteredFirst } from './example.filter'; + +@Module({ + imports: [], + controllers: [ExampleController], + providers: [ + { + provide: APP_FILTER, + useClass: ExampleExceptionFilterRegisteredFirst, + }, + ], +}) +export class ExampleModuleGlobalFilterRegisteredFirst {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts index 41d75d6eaf89..11a0470ace17 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts @@ -11,4 +11,9 @@ export class ExampleControllerLocalFilter { getCaughtException() { throw new LocalExampleException(); } + + @Get('/unexpected-exception') + getUncaughtException(): string { + throw new Error(`This is an uncaught exception!`); + } } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-specific.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-specific.exception.ts new file mode 100644 index 000000000000..a26cb1a26d52 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-specific.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionSpecificFilter extends Error { + constructor() { + super('Original specific example exception!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-specific.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-specific.filter.ts new file mode 100644 index 000000000000..bf85385a9a86 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-specific.filter.ts @@ -0,0 +1,19 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common'; +import { Request, Response } from 'express'; +import { ExampleExceptionSpecificFilter } from './example-specific.exception'; + +@Catch(ExampleExceptionSpecificFilter) +export class ExampleSpecificFilter implements ExceptionFilter { + catch(exception: BadRequestException, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + response.status(400).json({ + statusCode: 400, + timestamp: new Date().toISOString(), + path: request.url, + message: 'Example exception was handled by specific filter!', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts index 6fbc9f2c1f32..03d4679fa3c0 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -29,6 +29,34 @@ test('Sends unexpected exception to Sentry if thrown in module with global filte }); }); +test('Sends unexpected exception to Sentry if thrown in module with local filter', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs-with-submodules', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an uncaught exception!'; + }); + + const response = await fetch(`${baseURL}/example-module-local-filter/unexpected-exception`); + expect(response.status).toBe(500); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an uncaught exception!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/example-module-local-filter/unexpected-exception', + }); + + expect(errorEvent.transaction).toEqual('GET /example-module-local-filter/unexpected-exception'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + test('Sends unexpected exception to Sentry if thrown in module that was registered before Sentry', async ({ baseURL, }) => { @@ -36,7 +64,7 @@ test('Sends unexpected exception to Sentry if thrown in module that was register return !event.type && event.exception?.values?.[0]?.value === 'This is an uncaught exception!'; }); - const response = await fetch(`${baseURL}/example-module-wrong-order/unexpected-exception`); + const response = await fetch(`${baseURL}/example-module-registered-first/unexpected-exception`); expect(response.status).toBe(500); const errorEvent = await errorEventPromise; @@ -48,10 +76,10 @@ test('Sends unexpected exception to Sentry if thrown in module that was register method: 'GET', cookies: {}, headers: expect.any(Object), - url: 'http://localhost:3030/example-module-wrong-order/unexpected-exception', + url: 'http://localhost:3030/example-module-registered-first/unexpected-exception', }); - expect(errorEvent.transaction).toEqual('GET /example-module-wrong-order/unexpected-exception'); + expect(errorEvent.transaction).toEqual('GET /example-module-registered-first/unexpected-exception'); expect(errorEvent.contexts?.trace).toEqual({ trace_id: expect.any(String), @@ -116,33 +144,99 @@ test('Does not send exception to Sentry if user-defined local exception filter a expect(errorEventOccurred).toBe(false); }); -test('Does not handle expected exception if exception is thrown in module registered before Sentry', async ({ +test('Does not send expected exception to Sentry if exception is thrown in module registered before Sentry', async ({ baseURL, }) => { - const errorEventPromise = waitForError('nestjs-with-submodules', event => { - return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the example module!'; + let errorEventOccurred = false; + + waitForError('nestjs-with-submodules', event => { + if (!event.type && event.exception?.values?.[0].value === 'Something went wrong in the example module!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-module-registered-first/expected-exception'; }); - const response = await fetch(`${baseURL}/example-module-wrong-order/expected-exception`); - expect(response.status).toBe(500); // should be 400 + const transactionEventPromise = waitForTransaction('nestjs-with-submodules', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-module-registered-first/expected-exception'; + }); - // should never arrive, but does because the exception is not handled properly - const errorEvent = await errorEventPromise; + const response = await fetch(`${baseURL}/example-module-registered-first/expected-exception`); + expect(response.status).toBe(400); - expect(errorEvent.exception?.values).toHaveLength(1); - expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the example module!'); + await transactionEventPromise; - expect(errorEvent.request).toEqual({ - method: 'GET', - cookies: {}, - headers: expect.any(Object), - url: 'http://localhost:3030/example-module-wrong-order/expected-exception', + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Global specific exception filter registered in main module is applied and exception is not sent to Sentry', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-with-submodules', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by specific filter!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-exception-specific-filter'; }); - expect(errorEvent.transaction).toEqual('GET /example-module-wrong-order/expected-exception'); + const transactionEventPromise = waitForTransaction('nestjs-with-submodules', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-exception-specific-filter'; + }); - expect(errorEvent.contexts?.trace).toEqual({ - trace_id: expect.any(String), - span_id: expect.any(String), + const response = await fetch(`${baseURL}/example-exception-specific-filter`); + const responseBody = await response.json(); + + expect(response.status).toBe(400); + expect(responseBody).toEqual({ + statusCode: 400, + timestamp: expect.any(String), + path: '/example-exception-specific-filter', + message: 'Example exception was handled by specific filter!', }); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Local specific exception filter registered in main module is applied and exception is not sent to Sentry', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-with-submodules', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by local filter!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-exception-local-filter'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-with-submodules', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-exception-local-filter'; + }); + + const response = await fetch(`${baseURL}/example-exception-local-filter`); + const responseBody = await response.json(); + + expect(response.status).toBe(400); + expect(responseBody).toEqual({ + statusCode: 400, + timestamp: expect.any(String), + path: '/example-exception-local-filter', + message: 'Example exception was handled by local filter!', + }); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); }); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts index 9217249faad0..a2ea1db9c506 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts @@ -122,7 +122,9 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { ); }); -test('API route transaction includes exception filter span for global filter', async ({ baseURL }) => { +test('API route transaction includes exception filter span for global filter in module registered after Sentry', async ({ + baseURL, +}) => { const transactionEventPromise = waitForTransaction('nestjs-with-submodules', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && @@ -159,7 +161,9 @@ test('API route transaction includes exception filter span for global filter', a ); }); -test('API route transaction includes exception filter span for local filter', async ({ baseURL }) => { +test('API route transaction includes exception filter span for local filter in module registered after Sentry', async ({ + baseURL, +}) => { const transactionEventPromise = waitForTransaction('nestjs-with-submodules', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && @@ -195,3 +199,42 @@ test('API route transaction includes exception filter span for local filter', as }), ); }); + +test('API route transaction includes exception filter span for global filter in module registered before Sentry', async ({ + baseURL, +}) => { + const transactionEventPromise = waitForTransaction('nestjs-with-submodules', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /example-module-registered-first/expected-exception' && + transactionEvent?.request?.url?.includes('/example-module-registered-first/expected-exception') + ); + }); + + const response = await fetch(`${baseURL}/example-module-registered-first/expected-exception`); + expect(response.status).toBe(400); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'ExampleExceptionFilterRegisteredFirst', + parent_span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/routes/back-navigation.tsx b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/back-navigation.tsx new file mode 100644 index 000000000000..ddd970944bf3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/back-navigation.tsx @@ -0,0 +1,9 @@ +import { A } from '@solidjs/router'; + +export default function BackNavigation() { + return ( + + User 6 + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/routes/index.tsx b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/index.tsx index 873d542e4bae..eed722cba4e3 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/src/routes/index.tsx +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/index.tsx @@ -20,9 +20,7 @@ export default function Home() {
  • - - User 6 - + Test back navigation
  • diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts b/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts index 6e5f43e016c8..52d9cb219401 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts @@ -54,8 +54,8 @@ test('updates the transaction when using the back button', async ({ page }) => { return transactionEvent?.transaction === '/users/6' && transactionEvent.contexts?.trace?.op === 'navigation'; }); - await page.goto(`/`); - await page.locator('#navLinkUserBack').click(); + await page.goto(`/back-navigation`); + await page.locator('#navLink').click(); const navigationTxn = await navigationTxnPromise; expect(navigationTxn).toMatchObject({ @@ -72,7 +72,9 @@ test('updates the transaction when using the back button', async ({ page }) => { }); const backNavigationTxnPromise = waitForTransaction('solidstart', async transactionEvent => { - return transactionEvent?.transaction === '/' && transactionEvent.contexts?.trace?.op === 'navigation'; + return ( + transactionEvent?.transaction === '/back-navigation' && transactionEvent.contexts?.trace?.op === 'navigation' + ); }); await page.goBack(); @@ -85,7 +87,7 @@ test('updates the transaction when using the back button', async ({ page }) => { origin: 'auto.navigation.solidstart.solidrouter', }, }, - transaction: '/', + transaction: '/back-navigation', transaction_info: { source: 'url', }, diff --git a/dev-packages/external-contributor-gh-action/package.json b/dev-packages/external-contributor-gh-action/package.json index c5502135c384..3d81148be51a 100644 --- a/dev-packages/external-contributor-gh-action/package.json +++ b/dev-packages/external-contributor-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/external-contributor-gh-action", "description": "An internal Github Action to add external contributors to the CHANGELOG.md file.", - "version": "8.25.0", + "version": "8.26.0", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index c9ddd75d6402..e381dc99e5d6 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/node-integration-tests", - "version": "8.25.0", + "version": "8.26.0", "license": "MIT", "engines": { "node": ">=14.18" @@ -31,10 +31,10 @@ "@nestjs/core": "^10.3.3", "@nestjs/platform-express": "^10.3.3", "@prisma/client": "5.9.1", - "@sentry/aws-serverless": "8.25.0", - "@sentry/node": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0", + "@sentry/aws-serverless": "8.26.0", + "@sentry/node": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0", "@types/mongodb": "^3.6.20", "@types/mysql": "^2.15.21", "@types/pg": "^8.6.5", diff --git a/dev-packages/node-integration-tests/suites/tracing/genericPool/scenario.js b/dev-packages/node-integration-tests/suites/tracing/genericPool/scenario.js new file mode 100644 index 000000000000..74d5f73693f5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/genericPool/scenario.js @@ -0,0 +1,71 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const mysql = require('mysql'); +const genericPool = require('generic-pool'); + +const factory = { + create: function () { + return mysql.createConnection({ + user: 'root', + password: 'docker', + }); + }, + destroy: function (client) { + client.end(err => { + if (err) { + // eslint-disable-next-line no-console + console.error('Error while disconnecting MySQL:', err); + } + }); + }, +}; + +const opts = { + max: 10, + min: 2, +}; + +const myPool = genericPool.createPool(factory, opts); + +async function run() { + await Sentry.startSpan( + { + op: 'transaction', + name: 'Test Transaction', + }, + async () => { + try { + const client1 = await myPool.acquire(); + const client2 = await myPool.acquire(); + + client1.query('SELECT NOW()', function () { + myPool.release(client1); + }); + + client2.query('SELECT 1 + 1 AS solution', function () { + myPool.release(client2); + }); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Error while pooling MySQL:', err); + } finally { + await myPool.drain(); + await myPool.clear(); + } + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts b/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts new file mode 100644 index 000000000000..129e142f2808 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts @@ -0,0 +1,34 @@ +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +describe('genericPool auto instrumentation', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('should auto-instrument `genericPool` package when calling pool.require()', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'generic-pool.aquire', + origin: 'auto.db.otel.generic-pool', + data: { + 'sentry.origin': 'auto.db.otel.generic-pool', + }, + status: 'ok', + }), + + expect.objectContaining({ + description: 'generic-pool.aquire', + origin: 'auto.db.otel.generic-pool', + data: { + 'sentry.origin': 'auto.db.otel.generic-pool', + }, + status: 'ok', + }), + ]), + }; + + createRunner(__dirname, 'scenario.js').expect({ transaction: EXPECTED_TRANSACTION }).start(done); + }); +}); diff --git a/dev-packages/overhead-metrics/package.json b/dev-packages/overhead-metrics/package.json index 724ffad496a8..4b4825b185df 100644 --- a/dev-packages/overhead-metrics/package.json +++ b/dev-packages/overhead-metrics/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "8.25.0", + "version": "8.26.0", "name": "@sentry-internal/overhead-metrics", "main": "index.js", "author": "Sentry", diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json index 5b591020aaeb..6ac6f0a1a729 100644 --- a/dev-packages/rollup-utils/package.json +++ b/dev-packages/rollup-utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/rollup-utils", - "version": "8.25.0", + "version": "8.26.0", "description": "Rollup utilities used at Sentry for the Sentry JavaScript SDK", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/rollup-utils", diff --git a/dev-packages/size-limit-gh-action/index.mjs b/dev-packages/size-limit-gh-action/index.mjs index c42e9c523e94..3dbb8aa22127 100644 --- a/dev-packages/size-limit-gh-action/index.mjs +++ b/dev-packages/size-limit-gh-action/index.mjs @@ -31,12 +31,27 @@ class SizeLimit { return bytes.format(size, { unitSeparator: ' ' }); } - formatTime(seconds) { - if (seconds >= 1) { - return `${Math.ceil(seconds * 10) / 10} s`; + formatPercentageChange(base = 0, current = 0) { + if (base === 0) { + return 'added'; + } + + if (current === 0) { + return 'removed'; + } + + const value = ((current - base) / base) * 100; + const formatted = (Math.sign(value) * Math.ceil(Math.abs(value) * 100)) / 100; + + if (value > 0) { + return `+${formatted}%`; + } + + if (value === 0) { + return '-'; } - return `${Math.ceil(seconds * 1000)} ms`; + return `${formatted}%`; } formatChange(base = 0, current = 0) { @@ -48,18 +63,18 @@ class SizeLimit { return 'removed'; } - const value = ((current - base) / base) * 100; - const formatted = (Math.sign(value) * Math.ceil(Math.abs(value) * 100)) / 100; + const value = current - base; + const formatted = this.formatBytes(value); if (value > 0) { - return `+${formatted}% 🔺`; + return `+${formatted} 🔺`; } if (value === 0) { - return `${formatted}%`; + return '-'; } - return `${formatted}% 🔽`; + return `${formatted} 🔽`; } formatLine(value, change) { @@ -67,16 +82,11 @@ class SizeLimit { } formatSizeResult(name, base, current) { - return [name, this.formatLine(this.formatBytes(current.size), this.formatChange(base.size, current.size))]; - } - - formatTimeResult(name, base, current) { return [ name, - this.formatLine(this.formatBytes(current.size), this.formatChange(base.size, current.size)), - this.formatLine(this.formatTime(current.loading), this.formatChange(base.loading, current.loading)), - this.formatLine(this.formatTime(current.running), this.formatChange(base.running, current.running)), - this.formatTime(current.total), + this.formatBytes(current.size), + this.formatPercentageChange(base.size, current.size), + this.formatChange(base.size, current.size), ]; } @@ -84,26 +94,12 @@ class SizeLimit { const results = JSON.parse(output); return results.reduce((current, result) => { - let time = {}; - - if (result.loading !== undefined && result.running !== undefined) { - const loading = +result.loading; - const running = +result.running; - - time = { - running, - loading, - total: loading + running, - }; - } - return { // biome-ignore lint/performance/noAccumulatingSpread: ...current, [result.name]: { name: result.name, size: +result.size, - ...time, }, }; }, {}); @@ -111,12 +107,6 @@ class SizeLimit { hasSizeChanges(base, current, threshold = 0) { const names = [...new Set([...(base ? Object.keys(base) : []), ...Object.keys(current)])]; - const isSize = names.some(name => current[name] && current[name].total === undefined); - - // Always return true if time results are present - if (!isSize) { - return true; - } return !!names.find(name => { const baseResult = base?.[name] || EmptyResult; @@ -132,16 +122,12 @@ class SizeLimit { formatResults(base, current) { const names = [...new Set([...(base ? Object.keys(base) : []), ...Object.keys(current)])]; - const isSize = names.some(name => current[name] && current[name].total === undefined); - const header = isSize ? SIZE_RESULTS_HEADER : TIME_RESULTS_HEADER; + const header = SIZE_RESULTS_HEADER; const fields = names.map(name => { const baseResult = base?.[name] || EmptyResult; const currentResult = current[name] || EmptyResult; - if (isSize) { - return this.formatSizeResult(name, baseResult, currentResult); - } - return this.formatTimeResult(name, baseResult, currentResult); + return this.formatSizeResult(name, baseResult, currentResult); }); return [header, ...fields]; @@ -165,15 +151,11 @@ async function execSizeLimit() { return { status, output }; } -const SIZE_RESULTS_HEADER = ['Path', 'Size']; -const TIME_RESULTS_HEADER = ['Path', 'Size', 'Loading time (3g)', 'Running time (snapdragon)', 'Total time']; +const SIZE_RESULTS_HEADER = ['Path', 'Size', '% Change', 'Change']; const EmptyResult = { name: '-', size: 0, - running: 0, - loading: 0, - total: 0, }; async function run() { @@ -227,6 +209,8 @@ async function run() { // Else, we run size limit for the current branch, AND fetch it for the comparison branch let base; let current; + let baseIsNotLatest = false; + let baseWorkflowRun; try { const artifacts = await getArtifactsForBranchAndWorkflow(octokit, { @@ -240,6 +224,8 @@ async function run() { throw new Error('No artifacts found'); } + baseWorkflowRun = artifacts.workflowRun; + await downloadOtherWorkflowArtifact(octokit, { ...repo, artifactName: ARTIFACT_NAME, @@ -248,6 +234,11 @@ async function run() { }); base = JSON.parse(await fs.readFile(resultsFilePath, { encoding: 'utf8' })); + + if (!artifacts.isLatest) { + baseIsNotLatest = true; + core.info('Base artifact is not the latest one. This may lead to incorrect results.'); + } } catch (error) { core.startGroup('Warning, unable to find base results'); core.error(error); @@ -271,7 +262,22 @@ async function run() { isNaN(thresholdNumber) || limit.hasSizeChanges(base, current, thresholdNumber) || sizeLimitComment; if (shouldComment) { - const body = [SIZE_LIMIT_HEADING, markdownTable(limit.formatResults(base, current))].join('\r\n'); + const bodyParts = [SIZE_LIMIT_HEADING]; + + if (baseIsNotLatest) { + bodyParts.push( + '⚠️ **Warning:** Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.', + ); + } + + bodyParts.push(markdownTable(limit.formatResults(base, current))); + + if (baseWorkflowRun) { + bodyParts.push(''); + bodyParts.push(`[View base workflow run](${baseWorkflowRun.html_url})`); + } + + const body = bodyParts.join('\r\n'); try { if (!sizeLimitComment) { @@ -320,7 +326,7 @@ const DEFAULT_PAGE_LIMIT = 10; * This is a bit hacky since GitHub Actions currently does not directly * support downloading artifacts from other workflows */ -export async function getArtifactsForBranchAndWorkflow(octokit, { owner, repo, workflowName, branch, artifactName }) { +async function getArtifactsForBranchAndWorkflow(octokit, { owner, repo, workflowName, branch, artifactName }) { core.startGroup(`getArtifactsForBranchAndWorkflow - workflow:"${workflowName}", branch:"${branch}"`); let repositoryWorkflow = null; @@ -361,14 +367,13 @@ export async function getArtifactsForBranchAndWorkflow(octokit, { owner, repo, w const workflow_id = repositoryWorkflow.id; let currentPage = 0; - const completedWorkflowRuns = []; + let latestWorkflowRun = null; for await (const response of octokit.paginate.iterator(octokit.rest.actions.listWorkflowRuns, { owner, repo, workflow_id, branch, - status: 'completed', per_page: DEFAULT_PAGE_LIMIT, event: 'push', })) { @@ -379,12 +384,47 @@ export async function getArtifactsForBranchAndWorkflow(octokit, { owner, repo, w } // Do not allow downloading artifacts from a fork. - completedWorkflowRuns.push( - ...response.data.filter(workflowRun => workflowRun.head_repository.full_name === `${owner}/${repo}`), - ); + const filtered = response.data.filter(workflowRun => workflowRun.head_repository.full_name === `${owner}/${repo}`); + + // Sort to ensure the latest workflow run is the first + filtered.sort((a, b) => { + return new Date(b.created_at).getTime() - new Date(a.created_at).getTime(); + }); + + // Store the first workflow run, to determine if this is the latest one... + if (!latestWorkflowRun) { + latestWorkflowRun = filtered[0]; + } + + // Search through workflow artifacts until we find a workflow run w/ artifact name that we are looking for + for (const workflowRun of filtered) { + core.info(`Checking artifacts for workflow run: ${workflowRun.html_url}`); - if (completedWorkflowRuns.length) { - break; + const { + data: { artifacts }, + } = await octokit.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: workflowRun.id, + }); + + if (!artifacts) { + core.warning( + `Unable to fetch artifacts for branch: ${branch}, workflow: ${workflow_id}, workflowRunId: ${workflowRun.id}`, + ); + } else { + const foundArtifact = artifacts.find(({ name }) => name === artifactName); + if (foundArtifact) { + core.info(`Found suitable artifact: ${foundArtifact.url}`); + return { + artifact: foundArtifact, + workflowRun, + isLatest: latestWorkflowRun.id === workflowRun.id, + }; + } else { + core.info(`No artifact found for ${artifactName}, trying next workflow run...`); + } + } } if (currentPage > DEFAULT_MAX_PAGES) { @@ -396,34 +436,6 @@ export async function getArtifactsForBranchAndWorkflow(octokit, { owner, repo, w currentPage++; } - // Search through workflow artifacts until we find a workflow run w/ artifact name that we are looking for - for (const workflowRun of completedWorkflowRuns) { - core.info(`Checking artifacts for workflow run: ${workflowRun.html_url}`); - - const { - data: { artifacts }, - } = await octokit.rest.actions.listWorkflowRunArtifacts({ - owner, - repo, - run_id: workflowRun.id, - }); - - if (!artifacts) { - core.warning( - `Unable to fetch artifacts for branch: ${branch}, workflow: ${workflow_id}, workflowRunId: ${workflowRun.id}`, - ); - } else { - const foundArtifact = artifacts.find(({ name }) => name === artifactName); - if (foundArtifact) { - core.info(`Found suitable artifact: ${foundArtifact.url}`); - return { - artifact: foundArtifact, - workflowRun, - }; - } - } - } - core.warning(`Artifact not found: ${artifactName}`); core.endGroup(); return null; diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json index 7179980f6f32..7d76088b54b6 100644 --- a/dev-packages/size-limit-gh-action/package.json +++ b/dev-packages/size-limit-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/size-limit-gh-action", "description": "An internal Github Action to compare the current size of a PR against the one on develop.", - "version": "8.25.0", + "version": "8.26.0", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json index 7fd73360538c..a0b1688f4142 100644 --- a/dev-packages/test-utils/package.json +++ b/dev-packages/test-utils/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "8.25.0", + "version": "8.26.0", "name": "@sentry-internal/test-utils", "author": "Sentry", "license": "MIT", @@ -45,8 +45,8 @@ }, "devDependencies": { "@playwright/test": "^1.44.1", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0" + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0" }, "volta": { "extends": "../../package.json" diff --git a/docs/changelog/v7.md b/docs/changelog/v7.md index 16072a8f8121..e784702015e0 100644 --- a/docs/changelog/v7.md +++ b/docs/changelog/v7.md @@ -1,7 +1,7 @@ # Changelog for Sentry SDK 7.x Support for Sentry SDK v7 will be dropped soon. We recommend migrating to the latest version of the SDK. You can migrate -from `v7` to `v8 of the SDK by following the [migration guide](../../MIGRATION.md). +from `v7` of the SDK to `v8` by following the [migration guide](../../MIGRATION.md#upgrading-from-7x-to-8x). ## 7.118.0 diff --git a/docs/new-sdk-release-checklist.md b/docs/new-sdk-release-checklist.md index 1292c5363fb0..7f1811e53d8f 100644 --- a/docs/new-sdk-release-checklist.md +++ b/docs/new-sdk-release-checklist.md @@ -45,8 +45,8 @@ differ slightly for other SDKs depending on how they are structured and how they - [ ] Make sure `build.yml` CI script is correctly set up to cover tests for the new package - - [ ] Ensure dependent packages are correctly set for “Determine changed packages” - [ ] Ensure unit tests run correctly + - [ ] If it is a browser SDK, add it to `BROWSER_TEST_PACKAGES` in `scripts/ci-unit-tests.ts` - [ ] Make sure the file paths in the ["Upload Artifacts" job](https://github.com/getsentry/sentry-javascript/blob/e5c1486eed236b878f2c49d6a04be86093816ac9/.github/workflows/build.yml#L314-L349) diff --git a/lerna.json b/lerna.json index ed95619c70a2..e3c3f83f3096 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "8.25.0", + "version": "8.26.0", "npmClient": "yarn" } diff --git a/package.json b/package.json index ebf4021a7a6a..beba7d79d284 100644 --- a/package.json +++ b/package.json @@ -35,15 +35,15 @@ "test:unit": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test:unit", "test:update-snapshots": "lerna run test:update-snapshots", "test:pr": "nx affected -t test --exclude \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"", - "test:pr:browser": "yarn test:pr --exclude \"@sentry/{core,utils,opentelemetry,bun,deno,node,profiling-node,aws-serverless,google-cloud-serverless,nextjs,nestjs,astro,cloudflare,solidstart,nuxt,remix,gatsby,sveltekit,vercel-edge}\"", - "test:pr:node": "ts-node ./scripts/node-unit-tests.ts --affected", - "test:ci:browser": "lerna run test --ignore \"@sentry/{core,utils,opentelemetry,bun,deno,node,profiling-node,aws-serverless,google-cloud-serverless,nextjs,nestjs,astro,cloudflare,solidstart,nuxt,remix,gatsby,sveltekit,vercel-edge}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"", - "test:ci:node": "ts-node ./scripts/node-unit-tests.ts", + "test:pr:browser": "UNIT_TEST_ENV=browser ts-node ./scripts/ci-unit-tests.ts --affected", + "test:pr:node": "UNIT_TEST_ENV=node ts-node ./scripts/ci-unit-tests.ts --affected", + "test:ci:browser": "UNIT_TEST_ENV=browser ts-node ./scripts/ci-unit-tests.ts", + "test:ci:node": "UNIT_TEST_ENV=node ts-node ./scripts/ci-unit-tests.ts", "test:ci:bun": "lerna run test --scope @sentry/bun", "yalc:publish": "lerna run yalc:publish" }, "volta": { - "node": "22.5.1", + "node": "18.20.3", "yarn": "1.22.22" }, "workspaces": [ diff --git a/packages/angular/package.json b/packages/angular/package.json index 5f22dc37ac12..a6c6bb06ef94 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/angular", - "version": "8.25.0", + "version": "8.26.0", "description": "Official Sentry SDK for Angular", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular", @@ -21,10 +21,10 @@ "rxjs": "^6.5.5 || ^7.x" }, "dependencies": { - "@sentry/browser": "8.25.0", - "@sentry/core": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0", + "@sentry/browser": "8.26.0", + "@sentry/core": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0", "tslib": "^2.4.1" }, "devDependencies": { diff --git a/packages/astro/package.json b/packages/astro/package.json index d857fd048877..b5fd94e25d3a 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/astro", - "version": "8.25.0", + "version": "8.26.0", "description": "Official Sentry SDK for Astro", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/astro", @@ -56,11 +56,11 @@ "astro": ">=3.x || >=4.0.0-beta" }, "dependencies": { - "@sentry/browser": "8.25.0", - "@sentry/core": "8.25.0", - "@sentry/node": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0", + "@sentry/browser": "8.26.0", + "@sentry/core": "8.26.0", + "@sentry/node": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0", "@sentry/vite-plugin": "^2.20.1" }, "devDependencies": { diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 3752bd30d448..95d099ff0526 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -147,7 +147,7 @@ async function instrumentRequest( async span => { const originalResponse = await next(); - if (span && originalResponse.status) { + if (originalResponse.status) { setHttpStatus(span, originalResponse.status); } diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index be6fe9f16c4d..881976a8db34 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/aws-serverless", - "version": "8.25.0", + "version": "8.26.0", "description": "Official Sentry SDK for AWS Lambda and AWS Serverless Environments", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/serverless", @@ -66,10 +66,10 @@ "dependencies": { "@opentelemetry/instrumentation-aws-lambda": "0.43.0", "@opentelemetry/instrumentation-aws-sdk": "0.43.1", - "@sentry/core": "8.25.0", - "@sentry/node": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0", + "@sentry/core": "8.26.0", + "@sentry/node": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0", "@types/aws-lambda": "^8.10.62" }, "devDependencies": { diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json index 8fc2497527a2..235032606e5d 100644 --- a/packages/browser-utils/package.json +++ b/packages/browser-utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/browser-utils", - "version": "8.25.0", + "version": "8.26.0", "description": "Browser Utilities for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser-utils", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0" + "@sentry/core": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/browser/package.json b/packages/browser/package.json index 2131cefd692c..07322e621598 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/browser", - "version": "8.25.0", + "version": "8.26.0", "description": "Official Sentry SDK for browsers", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser", @@ -39,16 +39,16 @@ "access": "public" }, "dependencies": { - "@sentry-internal/browser-utils": "8.25.0", - "@sentry-internal/feedback": "8.25.0", - "@sentry-internal/replay": "8.25.0", - "@sentry-internal/replay-canvas": "8.25.0", - "@sentry/core": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0" + "@sentry-internal/browser-utils": "8.26.0", + "@sentry-internal/feedback": "8.26.0", + "@sentry-internal/replay": "8.26.0", + "@sentry-internal/replay-canvas": "8.26.0", + "@sentry/core": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0" }, "devDependencies": { - "@sentry-internal/integration-shims": "8.25.0", + "@sentry-internal/integration-shims": "8.26.0", "fake-indexeddb": "^4.0.1" }, "scripts": { diff --git a/packages/browser/src/utils/lazyLoadIntegration.ts b/packages/browser/src/utils/lazyLoadIntegration.ts index b023bc18aa95..168d1fd1013b 100644 --- a/packages/browser/src/utils/lazyLoadIntegration.ts +++ b/packages/browser/src/utils/lazyLoadIntegration.ts @@ -31,7 +31,10 @@ const WindowWithMaybeIntegration = WINDOW as { * Lazy load an integration from the CDN. * Rejects if the integration cannot be loaded. */ -export async function lazyLoadIntegration(name: keyof typeof LazyLoadableIntegrations): Promise { +export async function lazyLoadIntegration( + name: keyof typeof LazyLoadableIntegrations, + scriptNonce?: string, +): Promise { const bundle = LazyLoadableIntegrations[name]; // `window.Sentry` is only set when using a CDN bundle, but this method can also be used via the NPM package @@ -56,6 +59,10 @@ export async function lazyLoadIntegration(name: keyof typeof LazyLoadableIntegra script.crossOrigin = 'anonymous'; script.referrerPolicy = 'origin'; + if (scriptNonce) { + script.setAttribute('nonce', scriptNonce); + } + const waitForLoad = new Promise((resolve, reject) => { script.addEventListener('load', () => resolve()); script.addEventListener('error', reject); diff --git a/packages/bun/package.json b/packages/bun/package.json index 71fcf3c36310..fe0c181bf70a 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/bun", - "version": "8.25.0", + "version": "8.26.0", "description": "Official Sentry SDK for bun", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bun", @@ -39,11 +39,11 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.25.0", - "@sentry/node": "8.25.0", - "@sentry/opentelemetry": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0" + "@sentry/core": "8.26.0", + "@sentry/node": "8.26.0", + "@sentry/opentelemetry": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0" }, "devDependencies": { "bun-types": "latest" diff --git a/packages/cloudflare/README.md b/packages/cloudflare/README.md index f7de52a56e88..398153563f1c 100644 --- a/packages/cloudflare/README.md +++ b/packages/cloudflare/README.md @@ -114,16 +114,16 @@ Currently only ESM handlers are supported. import * as Sentry from '@sentry/cloudflare'; export default withSentry( - (env) => ({ - dsn: env.SENTRY_DSN, + env => ({ + dsn: env.SENTRY_DSN, // Set tracesSampleRate to 1.0 to capture 100% of spans for tracing. - tracesSampleRate: 1.0, - }), - { - async fetch(request, env, ctx) { - return new Response('Hello World!'); - }, - } satisfies ExportedHandler + tracesSampleRate: 1.0, + }), + { + async fetch(request, env, ctx) { + return new Response('Hello World!'); + }, + } satisfies ExportedHandler, ); ``` diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 43803985c7bb..fd9d9bcffe36 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cloudflare", - "version": "8.25.0", + "version": "8.26.0", "description": "Offical Sentry SDK for Cloudflare Workers and Pages", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/cloudflare", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0" + "@sentry/core": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0" }, "optionalDependencies": { "@cloudflare/workers-types": "^4.x" diff --git a/packages/core/package.json b/packages/core/package.json index e37131d2c479..8f140bfbb09c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/core", - "version": "8.25.0", + "version": "8.26.0", "description": "Base implementation for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/core", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0" + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/deno/package.json b/packages/deno/package.json index 2c70255bff45..4178c41cff65 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/deno", - "version": "8.25.0", + "version": "8.26.0", "description": "Official Sentry SDK for Deno", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/deno", @@ -24,9 +24,9 @@ "/build" ], "dependencies": { - "@sentry/core": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0" + "@sentry/core": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0" }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.5", diff --git a/packages/deno/src/client.ts b/packages/deno/src/client.ts index 1db45a5cb960..6f4e37f1ed74 100644 --- a/packages/deno/src/client.ts +++ b/packages/deno/src/client.ts @@ -4,6 +4,11 @@ import { SDK_VERSION, ServerRuntimeClient } from '@sentry/core'; import type { DenoClientOptions } from './types'; function getHostName(): string | undefined { + // Deno.permissions.querySync is not available on Deno Deploy + if (!Deno.permissions.querySync) { + return undefined; + } + const result = Deno.permissions.querySync({ name: 'sys', kind: 'hostname' }); return result.state === 'granted' ? Deno.hostname() : undefined; } diff --git a/packages/deno/src/integrations/context.ts b/packages/deno/src/integrations/context.ts index 69ef164bb32d..926fcd065f1b 100644 --- a/packages/deno/src/integrations/context.ts +++ b/packages/deno/src/integrations/context.ts @@ -16,8 +16,8 @@ function getOSName(): string { } } -function getOSRelease(): string | undefined { - return Deno.permissions.querySync({ name: 'sys', kind: 'osRelease' }).state === 'granted' +async function getOSRelease(): Promise { + return (await Deno.permissions.query({ name: 'sys', kind: 'osRelease' })).state === 'granted' ? Deno.osRelease() : undefined; } @@ -35,7 +35,7 @@ async function addDenoRuntimeContext(event: Event): Promise { }, os: { name: getOSName(), - version: getOSRelease(), + version: await getOSRelease(), }, v8: { name: 'v8', diff --git a/packages/deno/src/integrations/normalizepaths.ts b/packages/deno/src/integrations/normalizepaths.ts index f984d5bebc33..07e0f872338b 100644 --- a/packages/deno/src/integrations/normalizepaths.ts +++ b/packages/deno/src/integrations/normalizepaths.ts @@ -42,6 +42,11 @@ function appRootFromErrorStack(error: Error): string | undefined { } function getCwd(): string | undefined { + // Deno.permissions.querySync is not available on Deno Deploy + if (!Deno.permissions.querySync) { + return undefined; + } + // We don't want to prompt for permissions so we only get the cwd if // permissions are already granted const permission = Deno.permissions.querySync({ name: 'read', path: './' }); diff --git a/packages/deno/src/transports/index.ts b/packages/deno/src/transports/index.ts index 964c1a9347af..c678688c2462 100644 --- a/packages/deno/src/transports/index.ts +++ b/packages/deno/src/transports/index.ts @@ -1,6 +1,6 @@ import { createTransport } from '@sentry/core'; import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; -import { consoleSandbox, rejectedSyncPromise } from '@sentry/utils'; +import { consoleSandbox, logger, rejectedSyncPromise } from '@sentry/utils'; export interface DenoTransportOptions extends BaseTransportOptions { /** Custom headers for the transport. Used by the XHRTransport and FetchTransport */ @@ -13,13 +13,20 @@ export interface DenoTransportOptions extends BaseTransportOptions { export function makeFetchTransport(options: DenoTransportOptions): Transport { const url = new URL(options.url); - if (Deno.permissions.querySync({ name: 'net', host: url.host }).state !== 'granted') { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn(`Sentry SDK requires 'net' permission to send events. - Run with '--allow-net=${url.host}' to grant the requires permissions.`); + Deno.permissions + .query({ name: 'net', host: url.host }) + .then(({ state }) => { + if (state !== 'granted') { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn(`Sentry SDK requires 'net' permission to send events. + Run with '--allow-net=${url.host}' to grant the requires permissions.`); + }); + } + }) + .catch(() => { + logger.warn('Failed to read the "net" permission.'); }); - } function makeRequest(request: TransportRequest): PromiseLike { const requestOptions: RequestInit = { diff --git a/packages/ember/package.json b/packages/ember/package.json index 2682d65f9244..046d187d8da3 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/ember", - "version": "8.25.0", + "version": "8.26.0", "description": "Official Sentry SDK for Ember.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember", @@ -33,10 +33,10 @@ "dependencies": { "@babel/core": "^7.24.4", "@embroider/macros": "^1.16.0", - "@sentry/browser": "8.25.0", - "@sentry/core": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0", + "@sentry/browser": "8.26.0", + "@sentry/core": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0", "ember-auto-import": "^2.7.2", "ember-cli-babel": "^8.2.0", "ember-cli-htmlbars": "^6.1.1", diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json index e2630e1038c3..f7d26ab13625 100644 --- a/packages/eslint-config-sdk/package.json +++ b/packages/eslint-config-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-config-sdk", - "version": "8.25.0", + "version": "8.26.0", "description": "Official Sentry SDK eslint config", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-config-sdk", @@ -22,8 +22,8 @@ "access": "public" }, "dependencies": { - "@sentry-internal/eslint-plugin-sdk": "8.25.0", - "@sentry-internal/typescript": "8.25.0", + "@sentry-internal/eslint-plugin-sdk": "8.26.0", + "@sentry-internal/typescript": "8.26.0", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", "eslint-config-prettier": "^6.11.0", diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json index b0f74252abf8..6f01f96eee3e 100644 --- a/packages/eslint-plugin-sdk/package.json +++ b/packages/eslint-plugin-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-plugin-sdk", - "version": "8.25.0", + "version": "8.26.0", "description": "Official Sentry SDK eslint plugin", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-plugin-sdk", diff --git a/packages/feedback/package.json b/packages/feedback/package.json index def2f283dc81..ff4c10fcad8a 100644 --- a/packages/feedback/package.json +++ b/packages/feedback/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/feedback", - "version": "8.25.0", + "version": "8.26.0", "description": "Sentry SDK integration for user feedback", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.25.0", - "@sentry/types": "8.25.0", - "@sentry/utils": "8.25.0" + "@sentry/core": "8.26.0", + "@sentry/types": "8.26.0", + "@sentry/utils": "8.26.0" }, "devDependencies": { "preact": "^10.19.4" diff --git a/packages/feedback/src/core/components/Actor.css.ts b/packages/feedback/src/core/components/Actor.css.ts index 60ae7cebd08e..8a8b2c4efa29 100644 --- a/packages/feedback/src/core/components/Actor.css.ts +++ b/packages/feedback/src/core/components/Actor.css.ts @@ -3,7 +3,7 @@ import { DOCUMENT } from '../../constants'; /** * Creates