From d1978da2753ce1dae011f57305f01ec52479147e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 31 May 2024 12:53:21 +0200 Subject: [PATCH] test(e2e): Update `react-router-6` test to avoid sending to Sentry --- .github/workflows/build.yml | 6 +- .github/workflows/canary.yml | 4 +- .../.gitignore | 0 .../.npmrc | 0 .../package.json | 11 +- .../react-router-6/playwright.config.mjs | 7 + .../public/index.html | 0 .../src/globals.d.ts | 0 .../src/index.tsx | 15 +- .../src/pages/Index.tsx | 4 +- .../src/pages/User.tsx | 0 .../src/react-app-env.d.ts | 0 .../react-router-6/start-event-proxy.mjs | 6 + .../react-router-6/tests/errors.test.ts | 59 ++++++++ .../react-router-6/tests/transactions.test.ts | 56 +++++++ .../tsconfig.json | 0 .../playwright.config.ts | 70 --------- .../tests/behaviour-test.spec.ts | 140 ------------------ 18 files changed, 138 insertions(+), 240 deletions(-) rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/.gitignore (100%) rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/.npmrc (100%) rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/package.json (81%) create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6/playwright.config.mjs rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/public/index.html (100%) rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/src/globals.d.ts (100%) rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/src/index.tsx (77%) rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/src/pages/Index.tsx (72%) rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/src/pages/User.tsx (100%) rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/src/react-app-env.d.ts (100%) create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts rename dev-packages/e2e-tests/test-applications/{standard-frontend-react => react-router-6}/tsconfig.json (100%) delete mode 100644 dev-packages/e2e-tests/test-applications/standard-frontend-react/playwright.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/behaviour-test.spec.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c2974d4313e..bd63ed8d46b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1012,7 +1012,7 @@ jobs: 'react-create-hash-router', 'react-router-6-use-routes', 'react-router-5', - 'standard-frontend-react', + 'react-router-6', 'svelte-5', 'sveltekit', 'sveltekit-2', @@ -1039,9 +1039,9 @@ jobs: - test-application: 'create-react-app' build-command: 'test:build-ts3.8' label: 'create-react-app (TS 3.8)' - - test-application: 'standard-frontend-react' + - test-application: 'react-router-6' build-command: 'test:build-ts3.8' - label: 'standard-frontend-react (TS 3.8)' + label: 'react-router-6 (TS 3.8)' - test-application: 'create-next-app' build-command: 'test:build-13' label: 'create-next-app (next@13)' diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 152cb275ad13..5d2214a850ca 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -92,9 +92,9 @@ jobs: - test-application: 'react-create-hash-router' build-command: 'test:build-canary' label: 'react-create-hash-router (canary)' - - test-application: 'standard-frontend-react' + - test-application: 'react-router-6' build-command: 'test:build-canary' - label: 'standard-frontend-react (canary)' + label: 'react-router-6 (canary)' steps: - name: Check out current commit diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/.gitignore b/dev-packages/e2e-tests/test-applications/react-router-6/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/.gitignore rename to dev-packages/e2e-tests/test-applications/react-router-6/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/.npmrc b/dev-packages/e2e-tests/test-applications/react-router-6/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/.npmrc rename to dev-packages/e2e-tests/test-applications/react-router-6/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/package.json b/dev-packages/e2e-tests/test-applications/react-router-6/package.json similarity index 81% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/package.json rename to dev-packages/e2e-tests/test-applications/react-router-6/package.json index bf1cd59a0a77..3053b0a7c137 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6/package.json @@ -1,22 +1,16 @@ { - "name": "standard-frontend-react-test", + "name": "react-router-6-test", "version": "0.1.0", "private": true, "dependencies": { "@sentry/react": "latest || *", - "@testing-library/jest-dom": "5.14.1", - "@testing-library/react": "13.0.0", - "@testing-library/user-event": "13.2.1", - "@types/jest": "27.0.1", - "@types/node": "16.7.13", "@types/react": "18.0.0", "@types/react-dom": "18.0.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "^6.4.1", "react-scripts": "5.0.1", - "typescript": "4.9.5", - "web-vitals": "2.1.0" + "typescript": "4.9.5" }, "scripts": { "build": "react-scripts build", @@ -48,6 +42,7 @@ }, "devDependencies": { "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/react-router-6/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6/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/standard-frontend-react/public/index.html b/dev-packages/e2e-tests/test-applications/react-router-6/public/index.html similarity index 100% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/public/index.html rename to dev-packages/e2e-tests/test-applications/react-router-6/public/index.html diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/globals.d.ts b/dev-packages/e2e-tests/test-applications/react-router-6/src/globals.d.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/src/globals.d.ts rename to dev-packages/e2e-tests/test-applications/react-router-6/src/globals.d.ts diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-6/src/index.tsx similarity index 77% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx rename to dev-packages/e2e-tests/test-applications/react-router-6/src/index.tsx index 5980df0ff8dd..601ac10a084b 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-router-6/src/index.tsx @@ -36,21 +36,8 @@ Sentry.init({ // Always capture replays, so we can test this properly replaysSessionSampleRate: 1.0, replaysOnErrorSampleRate: 0.0, -}); - -Sentry.addEventProcessor(event => { - if ( - event.type === 'transaction' && - (event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation') - ) { - const eventId = event.event_id; - if (eventId) { - window.recordedTransactions = window.recordedTransactions || []; - window.recordedTransactions.push(eventId); - } - } - return event; + tunnel: 'http://localhost:3031', }); const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-router-6/src/pages/Index.tsx similarity index 72% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/src/pages/Index.tsx rename to dev-packages/e2e-tests/test-applications/react-router-6/src/pages/Index.tsx index 7789a2773224..d6b71a1d1279 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/pages/Index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-router-6/src/pages/Index.tsx @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/react'; // biome-ignore lint/nursery/noUnusedImports: Need React import for JSX import * as React from 'react'; import { Link } from 'react-router-dom'; @@ -11,8 +10,7 @@ const Index = () => { value="Capture Exception" id="exception-button" onClick={() => { - const eventId = Sentry.captureException(new Error('I am an error!')); - window.capturedExceptionId = eventId; + throw new Error('I am an error!'); }} /> diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/pages/User.tsx b/dev-packages/e2e-tests/test-applications/react-router-6/src/pages/User.tsx similarity index 100% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/src/pages/User.tsx rename to dev-packages/e2e-tests/test-applications/react-router-6/src/pages/User.tsx diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/react-app-env.d.ts b/dev-packages/e2e-tests/test-applications/react-router-6/src/react-app-env.d.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/src/react-app-env.d.ts rename to dev-packages/e2e-tests/test-applications/react-router-6/src/react-app-env.d.ts diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/react-router-6/start-event-proxy.mjs new file mode 100644 index 000000000000..5f4dfa2d0d9f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'react-router-6', +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6/tests/errors.test.ts new file mode 100644 index 000000000000..8ffbfcae282a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6/tests/errors.test.ts @@ -0,0 +1,59 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends correct error event', async ({ page }) => { + const errorEventPromise = waitForError('react-router-6', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.request).toEqual({ + headers: expect.any(Object), + url: 'http://localhost:3030/', + }); + + expect(errorEvent.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + +test('Sets correct transactionName', async ({ page }) => { + const transactionPromise = waitForTransaction('react-router-6', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const errorEventPromise = waitForError('react-router-6', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + const transactionEvent = await transactionPromise; + + // Only capture error once transaction was sent + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: transactionEvent.contexts?.trace?.trace_id, + span_id: expect.not.stringContaining(transactionEvent.contexts?.trace?.span_id || ''), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts new file mode 100644 index 000000000000..c554c47d8dde --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts @@ -0,0 +1,56 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('sends a pageload transaction with a parameterized URL', async ({ page }) => { + const transactionPromise = waitForTransaction('react-router-6', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + await page.goto(`/`); + + const rootSpan = await transactionPromise; + + expect(rootSpan).toMatchObject({ + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.react.reactrouter_v6', + }, + }, + transaction: '/', + transaction_info: { + source: 'route', + }, + }); +}); + +test('sends a navigation transaction with a parameterized URL', async ({ page }) => { + page.on('console', msg => console.log(msg.text())); + const pageloadTxnPromise = waitForTransaction('react-router-6', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('react-router-6', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await pageloadTxnPromise; + + const linkElement = page.locator('id=navigation'); + + const [_, navigationTxn] = await Promise.all([linkElement.click(), navigationTxnPromise]); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.react.reactrouter_v6', + }, + }, + transaction: '/user/:id', + transaction_info: { + source: 'route', + }, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-router-6/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/standard-frontend-react/tsconfig.json rename to dev-packages/e2e-tests/test-applications/react-router-6/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/playwright.config.ts b/dev-packages/e2e-tests/test-applications/standard-frontend-react/playwright.config.ts deleted file mode 100644 index 5f93f826ebf0..000000000000 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/playwright.config.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; -import { devices } from '@playwright/test'; - -/** - * See https://playwright.dev/docs/test-configuration. - */ -const config: PlaywrightTestConfig = { - testDir: './tests', - /* Maximum time one test can run for. */ - timeout: 150_000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, - }, - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: 0, - /* Opt out of parallel tests on CI. */ - workers: 1, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'list', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - // For now we only test Chrome! - // { - // name: 'firefox', - // use: { - // ...devices['Desktop Firefox'], - // }, - // }, - // { - // name: 'webkit', - // use: { - // ...devices['Desktop Safari'], - // }, - // }, - ], - - /* Run your local dev server before starting the tests */ - webServer: { - command: 'pnpm start', - port: 3030, - env: { - PORT: '3030', - }, - }, -}; - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/behaviour-test.spec.ts b/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/behaviour-test.spec.ts deleted file mode 100644 index 5e9b5ed0cdd6..000000000000 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/behaviour-test.spec.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { expect, test } from '@playwright/test'; - -const EVENT_POLLING_TIMEOUT = 90_000; - -const authToken = process.env.E2E_TEST_AUTH_TOKEN; -const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; -const sentryTestProject = process.env.E2E_TEST_SENTRY_PROJECT; - -test('Sends an exception to Sentry', async ({ page }) => { - await page.goto('/'); - - const exceptionButton = page.locator('id=exception-button'); - await exceptionButton.click(); - - const exceptionIdHandle = await page.waitForFunction(() => window.capturedExceptionId); - const exceptionEventId = await exceptionIdHandle.jsonValue(); - - console.log(`Polling for error eventId: ${exceptionEventId}`); - - await expect - .poll( - async () => { - const response = await fetch( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - return response.status; - }, - { - timeout: EVENT_POLLING_TIMEOUT, - }, - ) - .toBe(200); -}); - -test('Sends a pageload transaction to Sentry', async ({ page }) => { - await page.goto('/'); - - const recordedTransactionsHandle = await page.waitForFunction(() => { - if (window.recordedTransactions && window.recordedTransactions?.length >= 1) { - return window.recordedTransactions; - } else { - return undefined; - } - }); - const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue(); - - if (recordedTransactionEventIds === undefined) { - throw new Error("Application didn't record any transaction event IDs."); - } - - let hadPageLoadTransaction = false; - - console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`); - - await Promise.all( - recordedTransactionEventIds.map(async transactionEventId => { - await expect - .poll( - async () => { - const response = await fetch( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.ok) { - const data = await response.json(); - if (data.contexts.trace.op === 'pageload') { - hadPageLoadTransaction = true; - } - } - - return response.status; - }, - { - timeout: EVENT_POLLING_TIMEOUT, - }, - ) - .toBe(200); - }), - ); - - expect(hadPageLoadTransaction).toBe(true); -}); - -test('Sends a navigation transaction to Sentry', async ({ page }) => { - await page.goto('/'); - - // Give pageload transaction time to finish - await page.waitForTimeout(4000); - - const linkElement = page.locator('id=navigation'); - await linkElement.click(); - - const recordedTransactionsHandle = await page.waitForFunction(() => { - if (window.recordedTransactions && window.recordedTransactions?.length >= 2) { - return window.recordedTransactions; - } else { - return undefined; - } - }); - const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue(); - - if (recordedTransactionEventIds === undefined) { - throw new Error("Application didn't record any transaction event IDs."); - } - - let hadPageNavigationTransaction = false; - - console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`); - - await Promise.all( - recordedTransactionEventIds.map(async transactionEventId => { - await expect - .poll( - async () => { - const response = await fetch( - `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, - { headers: { Authorization: `Bearer ${authToken}` } }, - ); - - if (response.ok) { - const data = await response.json(); - if (data.contexts.trace.op === 'navigation') { - hadPageNavigationTransaction = true; - } - } - - return response.status; - }, - { - timeout: EVENT_POLLING_TIMEOUT, - }, - ) - .toBe(200); - }), - ); - - expect(hadPageNavigationTransaction).toBe(true); -});