diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml index 27971a484f63..2a4b331fd3f6 100644 --- a/.github/actions/install-dependencies/action.yml +++ b/.github/actions/install-dependencies/action.yml @@ -11,6 +11,7 @@ runs: echo 'paths<> "$GITHUB_OUTPUT" shell: bash diff --git a/.github/actions/install-playwright/action.yml b/.github/actions/install-playwright/action.yml index bd84a13e9107..378e77666093 100644 --- a/.github/actions/install-playwright/action.yml +++ b/.github/actions/install-playwright/action.yml @@ -34,6 +34,13 @@ runs: playwright apt + # `--with-deps` names packages that the runner image already has (glib, mesa, nss, ...), and `apt-get install` would + # upgrade them to the latest Ubuntu update. That is half of the download on a cold cache, and the image versions work. + - name: Keep installed apt packages + if: steps.playwright-version.outputs.version != '' + run: echo 'APT::Get::Upgrade "false";' | sudo tee /etc/apt/apt.conf.d/99-no-upgrade > /dev/null + shell: bash + # Downloads only the browsers and apt packages that are not on the cache volume yet. `npx` runs outside the repo, # so it can't pick up a `playwright` that a parallel `pnpm install` is still linking. - name: Install Playwright diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 23377c9c06cc..e202edc68667 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -343,6 +343,14 @@ jobs: run: pnpm run lint:es-compatibility - name: Lint types run: pnpm run lint:types + - name: Check for circular dependencies + run: pnpm run circularDepCheck + - name: Check for dts files that reference stuff in the temporary build folder + run: | + if grep -r --include "*.d.ts" --exclude-dir ".nx" 'import("@sentry(-internal)?/[^/]*/build' .; then + echo "Found illegal TypeScript import statement." + exit 1 + fi - name: Check Playwright versions run: | version=$(node -p "require('./dev-packages/test-utils/package.json').devDependencies['@playwright/test']") @@ -354,7 +362,8 @@ jobs: job_check_lockfile: name: Check lockfile needs: [job_get_metadata] - timeout-minutes: 10 + # `pnpm dedupe --check` fetches metadata for every package from npm, so this job can take 8+ min on PRs. + timeout-minutes: 20 runs-on: namespace-profile-sentry-javascript if: | needs.job_get_metadata.outputs.changed_deps == 'true' || @@ -406,27 +415,6 @@ jobs: - name: Check file formatting run: pnpm run format:check - job_circular_dep_check: - name: Circular Dependency Check - needs: [job_get_metadata, job_build] - timeout-minutes: 10 - runs-on: - namespace-profile-sentry-javascript;overrides.cache-tag=sentry-javascript${{ github.event_name == 'pull_request' - && '-pr' || '' }} - steps: - - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) - uses: actions/checkout@v7 - with: - ref: ${{ env.HEAD_COMMIT }} - - name: Set up Node - uses: actions/setup-node@v7 - with: - node-version-file: 'package.json' - - name: Restore caches - uses: ./.github/actions/restore-cache - - name: Run madge - run: pnpm run circularDepCheck - job_artifacts: name: Upload Artifacts needs: [job_get_metadata, job_build, job_build_layer, job_build_bundles, job_build_tarballs] @@ -772,35 +760,8 @@ jobs: overwrite: true retention-days: 7 - job_check_for_faulty_dts: - name: Check for faulty .d.ts files - needs: [job_get_metadata, job_build] - runs-on: - namespace-profile-sentry-javascript;overrides.cache-tag=sentry-javascript${{ github.event_name == 'pull_request' - && '-pr' || '' }} - timeout-minutes: 5 - steps: - - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) - uses: actions/checkout@v7 - with: - ref: ${{ env.HEAD_COMMIT }} - - name: Set up Node - uses: actions/setup-node@v7 - with: - node-version-file: 'package.json' - - name: Restore caches - uses: ./.github/actions/restore-cache - - name: Check for dts files that reference stuff in the temporary build folder - run: | - if grep -r --include "*.d.ts" --exclude-dir ".nx" 'import("@sentry(-internal)?/[^/]*/build' .; then - echo "Found illegal TypeScript import statement." - exit 1 - fi - job_node_integration_tests: - name: - Node (${{ matrix.node }})${{ (matrix.typescript && format(' (TS {0})', matrix.typescript)) || '' }} Integration - Tests (${{ matrix.shard }}/3) + name: Node (${{ matrix.node }}) Integration Tests (${{ matrix.shard }}/3) needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_node_integration == 'true' || github.event_name != 'pull_request' runs-on: @@ -812,19 +773,6 @@ jobs: matrix: node: [20.19, 22, 24, 26] shard: [1, 2, 3] - typescript: - - false - include: - # Only check typescript for latest version (to streamline CI) - - node: 24 - typescript: '5.0' - shard: 1 - - node: 24 - typescript: '5.0' - shard: 2 - - node: 24 - typescript: '5.0' - shard: 3 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v7 @@ -850,11 +798,6 @@ jobs: - name: Restore caches uses: ./.github/actions/restore-cache - - name: Overwrite typescript version - if: matrix.typescript == '5.0' - run: node ./scripts/use-ts-5_0.js - working-directory: dev-packages/node-integration-tests - - name: Run integration tests working-directory: dev-packages/node-integration-tests run: pnpm run test --shard=${{ matrix.shard }}/3 @@ -1083,9 +1026,10 @@ jobs: if: always() && needs.job_build_tarballs.result == 'success' && needs.job_build.outputs.e2e-matrix !='{"include":[]}' needs: [job_get_metadata, job_build, job_build_layer, job_build_tarballs] + # Most apps finish in under a minute on 4 vCPUs, so they run on 2. Apps that take longer set `sentryTest.runner`. runs-on: - namespace-profile-sentry-javascript;overrides.cache-tag=sentry-javascript-e2e${{ github.event_name == - 'pull_request' && '-pr' || '' }} + ${{ matrix.runner || 'namespace-profile-sentry-javascript-small' }};overrides.cache-tag=sentry-javascript-e2e${{ + github.event_name == 'pull_request' && '-pr' || '' }} timeout-minutes: 15 env: # We just use a dummy DSN here, only send to the tunnel anyhow @@ -1115,9 +1059,10 @@ jobs: runtime: ${{ matrix.runtime }} deno-version: ${{ matrix.deno-version }} - # Installs Playwright while the app builds. Keep it right before the build: the runner sets up a background - # composite action's steps from the job's shared `github` context, which fails with "Collection was modified" if - # a foreground step starts at that moment. Starting next to `download-artifact` also made it fail to read the event file. + # Installs Playwright while the app installs and builds. Keep it right before the install: the runner sets up a + # background composite action's steps from the job's shared `github` context, which fails with "Collection was + # modified" if a foreground step starts at that moment. Starting next to `download-artifact` also made it fail to + # read the event file. - name: Install Playwright id: install-playwright background: true @@ -1126,6 +1071,23 @@ jobs: browsers: chromium cwd: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + # Every build command runs `pnpm install`, so installing first makes that a no-op. Some downloads from the npm + # registry crawl at tens of kB/s for the whole 7 minutes of the build step, and pnpm does not give up on a slow + # download. Another attempt reuses what is already in the store and opens new connections. + # The hoisted variant installs with another node linker, so installing first would only redo the install. + - name: Install E2E app dependencies + if: ${{ !contains(matrix.build-command, 'hoisted') }} + working-directory: ${{ runner.temp }}/test-application + timeout-minutes: 7 + run: | + for attempt in 1 2; do + timeout 150 pnpm install && exit 0 + echo "::warning::pnpm install attempt $attempt failed or did not finish in 150 s, retrying" + done + pnpm install + env: + SENTRY_E2E_WORKSPACE_ROOT: ${{ github.workspace }} + - name: Build E2E app working-directory: ${{ runner.temp }}/test-application timeout-minutes: 7 @@ -1216,7 +1178,6 @@ jobs: job_lint, job_check_lockfile, job_check_format, - job_circular_dep_check, job_size_check, ] # Always run this, even if a dependent job failed diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml index e748668b92db..5d44ccb3497e 100644 --- a/.github/workflows/enforce-license-compliance.yml +++ b/.github/workflows/enforce-license-compliance.yml @@ -17,6 +17,12 @@ on: - v9 - v8 +# Cancel in progress workflows on pull_requests. +# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: enforce-license-compliance: runs-on: namespace-profile-sentry-javascript diff --git a/dev-packages/e2e-tests/README.md b/dev-packages/e2e-tests/README.md index 971ba3057c10..d71ba9b1b8a3 100644 --- a/dev-packages/e2e-tests/README.md +++ b/dev-packages/e2e-tests/README.md @@ -280,6 +280,10 @@ For example, if something is changed in the browser package, only E2E test apps You can add additional information about the test (e.g. canary versions, optional in CI) by adding `sentryTest` in the `package.json` of a test application. +The required E2E jobs run on 2 vCPUs (`namespace-profile-sentry-javascript-small`). If an app's job takes longer than +about 90 seconds, set `"sentryTest": { "runner": "namespace-profile-sentry-javascript" }` to run it and its variants on +4 vCPUs. + If you add Sentry dependencies to your test application, set the dependency versions to `latest || *` in order for it to work with both regular and prerelease versions: diff --git a/dev-packages/e2e-tests/lib/getTestMatrix.mjs b/dev-packages/e2e-tests/lib/getTestMatrix.mjs index bae7c3262787..cec2cb530828 100644 --- a/dev-packages/e2e-tests/lib/getTestMatrix.mjs +++ b/dev-packages/e2e-tests/lib/getTestMatrix.mjs @@ -72,6 +72,8 @@ function addIncludesForTestApp(testApp, includes, { optionalMode }) { const shouldSkip = packageJson.sentryTest?.skip || false; const isOptional = packageJson.sentryTest?.optional || false; const variants = (optionalMode ? packageJson.sentryTest?.optionalVariants : packageJson.sentryTest?.variants) || []; + const runner = packageJson.sentryTest?.runner; + const baseInclude = { 'test-application': testApp, ...(runner ? { runner } : {}) }; if (shouldSkip) { return; @@ -79,9 +81,7 @@ function addIncludesForTestApp(testApp, includes, { optionalMode }) { // Add the basic test-application itself, if it is in the current mode if (optionalMode === isOptional) { - includes.push({ - 'test-application': testApp, - }); + includes.push(baseInclude); } variants.forEach(variant => { @@ -94,7 +94,7 @@ function addIncludesForTestApp(testApp, includes, { optionalMode }) { // Don't leak the `skip` flag into the matrix include. const { skip: _skip, ...variantConfig } = variant; includes.push({ - 'test-application': testApp, + ...baseInclude, ...variantConfig, }); }); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-layer/package.json b/dev-packages/e2e-tests/test-applications/aws-serverless-layer/package.json index 66acf6b2afeb..f24cd95e12a0 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-layer/package.json +++ b/dev-packages/e2e-tests/test-applications/aws-serverless-layer/package.json @@ -24,6 +24,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "NODE_VERSION=22 pnpm test:build", diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-layer/tests/layer.test.ts b/dev-packages/e2e-tests/test-applications/aws-serverless-layer/tests/layer.test.ts index 538b533967e0..8e47ac4255a4 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-layer/tests/layer.test.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless-layer/tests/layer.test.ts @@ -337,7 +337,8 @@ test.describe('Lambda layer', () => { const transactionEvent = await transactionEventPromise; expect(transactionEvent.contexts?.trace?.status).toBe('ok'); - expect(transactionEvent.timestamp! - transactionEvent.start_timestamp!).toBeGreaterThanOrEqual(0.2); + // Node timers can fire up to a millisecond early, so the 200 ms callback delay can measure as 199.x ms + expect(transactionEvent.timestamp! - transactionEvent.start_timestamp!).toBeGreaterThanOrEqual(0.19); }); test('sets error status when a callback-style handler calls back with an error after it returned', async ({ @@ -360,7 +361,7 @@ test.describe('Lambda layer', () => { const [errorEvent, transactionEvent] = await Promise.all([errorEventPromise, transactionEventPromise]); expect(transactionEvent.contexts?.trace?.status).toBe('internal_error'); - expect(transactionEvent.timestamp! - transactionEvent.start_timestamp!).toBeGreaterThanOrEqual(0.2); + expect(transactionEvent.timestamp! - transactionEvent.start_timestamp!).toBeGreaterThanOrEqual(0.19); expect(errorEvent.contexts?.trace?.trace_id).toBe(transactionEvent.contexts?.trace?.trace_id); }); diff --git a/dev-packages/e2e-tests/test-applications/browser-bfcache/package.json b/dev-packages/e2e-tests/test-applications/browser-bfcache/package.json index 1db45bd6f42e..e1c004757888 100644 --- a/dev-packages/e2e-tests/test-applications/browser-bfcache/package.json +++ b/dev-packages/e2e-tests/test-applications/browser-bfcache/package.json @@ -23,5 +23,8 @@ }, "volta": { "extends": "../../package.json" + }, + "sentryTest": { + "runner": "namespace-profile-sentry-javascript" } } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15-basepath/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15-basepath/package.json index 318d05a6185f..379b4038a5e9 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15-basepath/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15-basepath/package.json @@ -26,5 +26,8 @@ }, "volta": { "extends": "../../package.json" + }, + "sentryTest": { + "runner": "namespace-profile-sentry-javascript" } } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15-intl/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15-intl/package.json index 998a4976ad3a..15294b246cbc 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15-intl/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15-intl/package.json @@ -27,5 +27,8 @@ }, "volta": { "extends": "../../package.json" + }, + "sentryTest": { + "runner": "namespace-profile-sentry-javascript" } } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15-t3/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15-t3/package.json index db01367495e8..2bbf3cdb8dcb 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15-t3/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15-t3/package.json @@ -46,5 +46,8 @@ }, "volta": { "extends": "../../package.json" + }, + "sentryTest": { + "runner": "namespace-profile-sentry-javascript" } } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index 30da1c0e8747..7f18b33c2879 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -34,6 +34,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "optionalVariants": [ { "build-command": "pnpm test:build-latest", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json index 855491173d4f..e90155de6c2c 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json @@ -46,6 +46,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "pnpm test:build-webpack", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-static/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-static/package.json index 50f57e8ca472..b2b626143a71 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-static/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-static/package.json @@ -42,5 +42,8 @@ }, "volta": { "extends": "../../package.json" + }, + "sentryTest": { + "runner": "namespace-profile-sentry-javascript" } } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/package.json index 3e6d5d60e233..231afe799973 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/package.json @@ -46,6 +46,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "pnpm test:build-webpack", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/package.json index 39da00835495..af35c23fcc8d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/package.json @@ -44,6 +44,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "pnpm test:build-webpack", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/package.json index adbc49ec455c..53c5adcc5761 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/package.json @@ -54,6 +54,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "pnpm test:build-webpack", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json index 25ec54f931d0..34b8d7f62dc9 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json @@ -67,6 +67,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "pnpm test:build-webpack", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json index 43cea1078cf2..1fd3800e0d2e 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json @@ -32,6 +32,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "pnpm test:build-15", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/package.json b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/package.json index fb3aeaf3399f..44591f3a2ec0 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/package.json @@ -33,6 +33,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "pnpm test:build-15", diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json index b5299208a860..06fe7b58519a 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json @@ -27,5 +27,8 @@ }, "volta": { "extends": "../../package.json" + }, + "sentryTest": { + "runner": "namespace-profile-sentry-javascript" } } diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4-static/package.json b/dev-packages/e2e-tests/test-applications/nuxt-4-static/package.json index ee355572b388..798eef35e989 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4-static/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-4-static/package.json @@ -33,6 +33,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "E2E_TEST_OTEL_SETUP=true pnpm test:build", diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json index 92fa2a41d87b..bf2941ed1992 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json @@ -35,6 +35,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "E2E_TEST_OTEL_SETUP=true pnpm test:build", diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/package.json b/dev-packages/e2e-tests/test-applications/react-router-6/package.json index 2ee798930dc4..dd022fa72381 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6/package.json @@ -50,5 +50,8 @@ }, "volta": { "extends": "../../package.json" + }, + "sentryTest": { + "runner": "namespace-profile-sentry-javascript" } } diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/package.json index 561d8a362f29..605ff3df45a9 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/package.json @@ -48,5 +48,8 @@ }, "volta": { "extends": "../../package.json" + }, + "sentryTest": { + "runner": "namespace-profile-sentry-javascript" } } diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-framework/package.json b/dev-packages/e2e-tests/test-applications/react-router-8-framework/package.json index de5503c7626a..bc4dc0092188 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-8-framework/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/package.json @@ -69,6 +69,7 @@ "extends": "../../package.json" }, "sentryTest": { + "runner": "namespace-profile-sentry-javascript", "variants": [ { "build-command": "pnpm test:build-latest", diff --git a/dev-packages/node-integration-tests/scripts/use-ts-5_0.js b/dev-packages/node-integration-tests/scripts/use-ts-5_0.js deleted file mode 100644 index 281bb4e9dcb7..000000000000 --- a/dev-packages/node-integration-tests/scripts/use-ts-5_0.js +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable no-console */ -const { execSync } = require('child_process'); -const { join } = require('path'); - -const cwd = join(__dirname, '../../..'); - -const tsVersion = '5.0.4'; - -console.log(`Installing typescript@${tsVersion}, and @types/node@18...`); - -execSync(`pnpm add --save-dev --workspace-root typescript@${tsVersion} @types/node@^18`, { - stdio: 'inherit', - cwd, -}); - -console.log('TypeScript version updated successfully.'); diff --git a/packages/replay-internal/src/coreHandlers/handleClick.ts b/packages/replay-internal/src/coreHandlers/handleClick.ts index f4576c3a7ca7..c61278494646 100644 --- a/packages/replay-internal/src/coreHandlers/handleClick.ts +++ b/packages/replay-internal/src/coreHandlers/handleClick.ts @@ -183,10 +183,10 @@ export class ClickDetector implements ReplayClickDetector { const now = nowInSeconds(); this._clicks.forEach(click => { - if (!click.mutationAfter && this._lastMutation) { + if (click.mutationAfter === undefined && this._lastMutation) { click.mutationAfter = click.timestamp <= this._lastMutation ? this._lastMutation - click.timestamp : undefined; } - if (!click.scrollAfter && this._lastScroll) { + if (click.scrollAfter === undefined && this._lastScroll) { click.scrollAfter = click.timestamp <= this._lastScroll ? this._lastScroll - click.timestamp : undefined; } @@ -215,8 +215,9 @@ export class ClickDetector implements ReplayClickDetector { /** Generate matching breadcrumb(s) for the click. */ private _generateBreadcrumbs(click: Click): void { const replay = this._replay; - const hadScroll = click.scrollAfter && click.scrollAfter <= this._scrollTimeout; - const hadMutation = click.mutationAfter && click.mutationAfter <= this._threshold; + // A mutation or scroll in the same millisecond as the click gives `0`, which still counts + const hadScroll = click.scrollAfter !== undefined && click.scrollAfter <= this._scrollTimeout; + const hadMutation = click.mutationAfter !== undefined && click.mutationAfter <= this._threshold; const isSlowClick = !hadScroll && !hadMutation; const { clickCount, clickBreadcrumb } = click; diff --git a/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts b/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts index 66beddf6ab19..7a51c5ca3e5b 100644 --- a/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts @@ -321,6 +321,22 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); }); + test('it does not consider clicks with a mutation in the same millisecond as slow click', async () => { + const breadcrumb: Breadcrumb = { + timestamp: BASE_TIMESTAMP / 1000, + data: { + nodeId: 1, + }, + }; + const node = document.createElement('button'); + detector.handleClick(breadcrumb, node); + detector.registerMutation(BASE_TIMESTAMP); + + vi.advanceTimersByTime(3_500); + + expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); + }); + test('it considers clicks with mutation after threshold as slow click', async () => { const breadcrumb: Breadcrumb = { timestamp: BASE_TIMESTAMP / 1000, @@ -429,6 +445,22 @@ describe('Unit | coreHandlers | handleClick', () => { ); }); + test('it does not consider clicks with a scroll in the same millisecond as slow click', async () => { + const breadcrumb: Breadcrumb = { + timestamp: BASE_TIMESTAMP / 1000, + data: { + nodeId: 1, + }, + }; + const node = document.createElement('button'); + detector.handleClick(breadcrumb, node); + detector.registerScroll(BASE_TIMESTAMP); + + vi.advanceTimersByTime(3_500); + + expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); + }); + test('it does not consider clicks with scroll before threshold as slow click', async () => { const breadcrumb: Breadcrumb = { timestamp: BASE_TIMESTAMP / 1000,