diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/.gitignore b/packages/e2e-tests/test-applications/react-create-hash-router/.gitignore
new file mode 100644
index 000000000000..84634c973eeb
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/.gitignore
@@ -0,0 +1,29 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+/test-results/
+/playwright-report/
+/playwright/.cache/
+
+!*.d.ts
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/.npmrc b/packages/e2e-tests/test-applications/react-create-hash-router/.npmrc
new file mode 100644
index 000000000000..c6b3ef9b3eaa
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/.npmrc
@@ -0,0 +1,2 @@
+@sentry:registry=http://localhost:4873
+@sentry-internal:registry=http://localhost:4873
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/package.json b/packages/e2e-tests/test-applications/react-create-hash-router/package.json
new file mode 100644
index 000000000000..bac46c9562d0
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "react-create-hash-router-test",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@sentry/react": "*",
+ "@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.4.2",
+ "web-vitals": "2.1.0"
+ },
+ "scripts": {
+ "build": "react-scripts build",
+ "start": "serve -s build",
+ "test": "playwright test"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@playwright/test": "1.26.1",
+ "axios": "1.1.2",
+ "serve": "14.0.1"
+ },
+ "volta": {
+ "node": "16.19.0",
+ "yarn": "1.22.19"
+ }
+}
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/playwright.config.ts b/packages/e2e-tests/test-applications/react-create-hash-router/playwright.config.ts
new file mode 100644
index 000000000000..a24d7bc1c742
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/playwright.config.ts
@@ -0,0 +1,70 @@
+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: 60 * 1000,
+ 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: Number(process.env.BASE_PORT) + Number(process.env.PORT_MODULO),
+ env: {
+ PORT: String(Number(process.env.BASE_PORT) + Number(process.env.PORT_MODULO)),
+ },
+ },
+};
+
+export default config;
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/public/index.html b/packages/e2e-tests/test-applications/react-create-hash-router/public/index.html
new file mode 100644
index 000000000000..39da76522bea
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/public/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+ React App
+
+
+
+
+
+
+
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/src/globals.d.ts b/packages/e2e-tests/test-applications/react-create-hash-router/src/globals.d.ts
new file mode 100644
index 000000000000..ffa61ca49acc
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/src/globals.d.ts
@@ -0,0 +1,5 @@
+interface Window {
+ recordedTransactions?: string[];
+ capturedExceptionId?: string;
+ sentryReplayId?: string;
+}
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx b/packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx
new file mode 100644
index 000000000000..aef574bce3c4
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import * as Sentry from '@sentry/react';
+import {
+ useLocation,
+ useNavigationType,
+ createRoutesFromChildren,
+ matchRoutes,
+ RouterProvider,
+ createHashRouter,
+} from 'react-router-dom';
+import Index from './pages/Index';
+import User from './pages/User';
+
+const replay = new Sentry.Replay();
+
+Sentry.init({
+ // environment: 'qa', // dynamic sampling bias to keep transactions
+ dsn: process.env.REACT_APP_E2E_TEST_DSN,
+ integrations: [
+ new Sentry.BrowserTracing({
+ routingInstrumentation: Sentry.reactRouterV6Instrumentation(
+ React.useEffect,
+ useLocation,
+ useNavigationType,
+ createRoutesFromChildren,
+ matchRoutes,
+ ),
+ }),
+ replay,
+ ],
+ // We recommend adjusting this value in production, or using tracesSampler
+ // for finer control
+ tracesSampleRate: 1.0,
+ release: 'e2e-test',
+
+ // Always capture replays, so we can test this properly
+ replaysSessionSampleRate: 1.0,
+ replaysOnErrorSampleRate: 0.0,
+});
+
+Object.defineProperty(window, 'sentryReplayId', {
+ get() {
+ return replay['_replay'].session.id;
+ },
+});
+
+Sentry.addGlobalEventProcessor(event => {
+ if (
+ event.type === 'transaction' &&
+ (event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation')
+ ) {
+ const eventId = event.event_id;
+ if (eventId) {
+ window.recordedTransactions = window.recordedTransactions || [];
+ window.recordedTransactions.push(eventId);
+ }
+ }
+
+ return event;
+});
+
+const sentryCreateHashRouter = Sentry.wrapCreateBrowserRouter(createHashRouter);
+
+const router = sentryCreateHashRouter([
+ {
+ path: '/',
+ element: ,
+ },
+ {
+ path: '/user/:id',
+ element: ,
+ },
+]);
+
+const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
+
+root.render();
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/src/pages/Index.tsx b/packages/e2e-tests/test-applications/react-create-hash-router/src/pages/Index.tsx
new file mode 100644
index 000000000000..2f683c63ed84
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/src/pages/Index.tsx
@@ -0,0 +1,24 @@
+import * as React from 'react';
+import * as Sentry from '@sentry/react';
+import { Link } from 'react-router-dom';
+
+const Index = () => {
+ return (
+ <>
+ {
+ const eventId = Sentry.captureException(new Error('I am an error!'));
+ window.capturedExceptionId = eventId;
+ }}
+ />
+
+ navigate
+
+ >
+ );
+};
+
+export default Index;
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/src/pages/User.tsx b/packages/e2e-tests/test-applications/react-create-hash-router/src/pages/User.tsx
new file mode 100644
index 000000000000..671455a92fff
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/src/pages/User.tsx
@@ -0,0 +1,7 @@
+import * as React from 'react';
+
+const User = () => {
+ return I am a blank page :)
;
+};
+
+export default User;
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/src/react-app-env.d.ts b/packages/e2e-tests/test-applications/react-create-hash-router/src/react-app-env.d.ts
new file mode 100644
index 000000000000..6431bc5fc6b2
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/test-recipe.json b/packages/e2e-tests/test-applications/react-create-hash-router/test-recipe.json
new file mode 100644
index 000000000000..7955a96ea1d0
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/test-recipe.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "../../test-recipe-schema.json",
+ "testApplicationName": "react-create-hash-router",
+ "buildCommand": "pnpm install && npx playwright install && pnpm build",
+ "tests": [
+ {
+ "testName": "Playwright tests",
+ "testCommand": "pnpm test"
+ }
+ ],
+ "canaryVersions": [
+ {
+ "dependencyOverrides": {
+ "react": "latest",
+ "react-dom": "latest"
+ }
+ }
+ ]
+}
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts b/packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts
new file mode 100644
index 000000000000..fb2d291dd70d
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/tests/behaviour-test.spec.ts
@@ -0,0 +1,254 @@
+import { test, expect } from '@playwright/test';
+import axios, { AxiosError } from 'axios';
+import { ReplayRecordingData } from './fixtures/ReplayRecordingData';
+
+const EVENT_POLLING_TIMEOUT = 30_000;
+
+const authToken = process.env.E2E_TEST_AUTH_TOKEN;
+const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
+const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT;
+
+test('Sends an exception to Sentry', async ({ page }) => {
+ await page.goto('/');
+
+ const exceptionButton = page.locator('id=exception-button');
+ await exceptionButton.click();
+
+ const exceptionIdHandle = await page.waitForFunction(() => window.capturedExceptionId);
+ const exceptionEventId = await exceptionIdHandle.jsonValue();
+
+ console.log(`Polling for error eventId: ${exceptionEventId}`);
+
+ await expect
+ .poll(
+ async () => {
+ try {
+ const response = await axios.get(
+ `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`,
+ { headers: { Authorization: `Bearer ${authToken}` } },
+ );
+ return response.status;
+ } catch (e) {
+ if (e instanceof AxiosError && e.response) {
+ if (e.response.status !== 404) {
+ throw e;
+ } else {
+ return e.response.status;
+ }
+ } else {
+ throw e;
+ }
+ }
+ },
+ {
+ 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 () => {
+ try {
+ const response = await axios.get(
+ `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`,
+ { headers: { Authorization: `Bearer ${authToken}` } },
+ );
+
+ if (response.data.contexts.trace.op === 'pageload') {
+ hadPageLoadTransaction = true;
+ }
+
+ return response.status;
+ } catch (e) {
+ if (e instanceof AxiosError && e.response) {
+ if (e.response.status !== 404) {
+ throw e;
+ } else {
+ return e.response.status;
+ }
+ } else {
+ throw e;
+ }
+ }
+ },
+ {
+ timeout: EVENT_POLLING_TIMEOUT,
+ },
+ )
+ .toBe(200);
+ }),
+ );
+
+ expect(hadPageLoadTransaction).toBe(true);
+});
+
+test('Sends a navigation transaction to Sentry', async ({ page }) => {
+ await page.goto('/');
+
+ // Give pageload transaction time to finish
+ page.waitForTimeout(4000);
+
+ const linkElement = page.locator('id=navigation');
+ await linkElement.click();
+
+ const recordedTransactionsHandle = await page.waitForFunction(() => {
+ if (window.recordedTransactions && window.recordedTransactions?.length >= 2) {
+ return window.recordedTransactions;
+ } else {
+ return undefined;
+ }
+ });
+ const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue();
+
+ if (recordedTransactionEventIds === undefined) {
+ throw new Error("Application didn't record any transaction event IDs.");
+ }
+
+ let hadPageNavigationTransaction = false;
+
+ console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`);
+
+ await Promise.all(
+ recordedTransactionEventIds.map(async transactionEventId => {
+ await expect
+ .poll(
+ async () => {
+ try {
+ const response = await axios.get(
+ `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`,
+ { headers: { Authorization: `Bearer ${authToken}` } },
+ );
+
+ if (response.data.contexts.trace.op === 'navigation') {
+ hadPageNavigationTransaction = true;
+ }
+
+ return response.status;
+ } catch (e) {
+ if (e instanceof AxiosError && e.response) {
+ if (e.response.status !== 404) {
+ throw e;
+ } else {
+ return e.response.status;
+ }
+ } else {
+ throw e;
+ }
+ }
+ },
+ {
+ timeout: EVENT_POLLING_TIMEOUT,
+ },
+ )
+ .toBe(200);
+ }),
+ );
+
+ expect(hadPageNavigationTransaction).toBe(true);
+});
+
+test('Sends a Replay recording to Sentry', async ({ browser }) => {
+ const context = await browser.newContext();
+ const page = await context.newPage();
+
+ await page.goto('/');
+
+ const replayId = await page.waitForFunction(() => {
+ return window.sentryReplayId;
+ });
+
+ // Wait for replay to be sent
+
+ if (replayId === undefined) {
+ throw new Error("Application didn't set a replayId");
+ }
+
+ console.log(`Polling for replay with ID: ${replayId}`);
+
+ await expect
+ .poll(
+ async () => {
+ try {
+ const response = await axios.get(
+ `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/`,
+ { headers: { Authorization: `Bearer ${authToken}` } },
+ );
+
+ return response.status;
+ } catch (e) {
+ if (e instanceof AxiosError && e.response) {
+ if (e.response.status !== 404) {
+ throw e;
+ } else {
+ return e.response.status;
+ }
+ } else {
+ throw e;
+ }
+ }
+ },
+ {
+ timeout: EVENT_POLLING_TIMEOUT,
+ },
+ )
+ .toBe(200);
+
+ // now fetch the first recording segment
+ await expect
+ .poll(
+ async () => {
+ try {
+ const response = await axios.get(
+ `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/replays/${replayId}/recording-segments/?cursor=100%3A0%3A1`,
+ { headers: { Authorization: `Bearer ${authToken}` } },
+ );
+
+ return {
+ status: response.status,
+ data: response.data,
+ };
+ } catch (e) {
+ if (e instanceof AxiosError && e.response) {
+ if (e.response.status !== 404) {
+ throw e;
+ } else {
+ return e.response.status;
+ }
+ } else {
+ throw e;
+ }
+ }
+ },
+ {
+ timeout: EVENT_POLLING_TIMEOUT,
+ },
+ )
+ .toEqual({
+ status: 200,
+ data: ReplayRecordingData,
+ });
+});
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts b/packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts
new file mode 100644
index 000000000000..a22694a64304
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts
@@ -0,0 +1,265 @@
+import { expect } from '@playwright/test';
+
+export const ReplayRecordingData = [
+ [
+ {
+ type: 4,
+ data: { href: expect.stringMatching(/http:\/\/localhost:\d+\//), width: 1280, height: 720 },
+ timestamp: expect.any(Number),
+ },
+ {
+ data: {
+ payload: {
+ blockAllMedia: true,
+ errorSampleRate: 0,
+ maskAllInputs: true,
+ maskAllText: true,
+ networkCaptureBodies: true,
+ networkDetailHasUrls: false,
+ networkRequestHasHeaders: true,
+ networkResponseHasHeaders: true,
+ sessionSampleRate: 1,
+ useCompression: false,
+ useCompressionOption: true,
+ },
+ tag: 'options',
+ },
+ timestamp: expect.any(Number),
+ type: 5,
+ },
+ {
+ type: 2,
+ data: {
+ node: {
+ type: 0,
+ childNodes: [
+ { type: 1, name: 'html', publicId: '', systemId: '', id: 2 },
+ {
+ type: 2,
+ tagName: 'html',
+ attributes: { lang: 'en' },
+ childNodes: [
+ {
+ type: 2,
+ tagName: 'head',
+ attributes: {},
+ childNodes: [
+ { type: 2, tagName: 'meta', attributes: { charset: 'utf-8' }, childNodes: [], id: 5 },
+ {
+ type: 2,
+ tagName: 'meta',
+ attributes: { name: 'viewport', content: 'width=device-width,initial-scale=1' },
+ childNodes: [],
+ id: 6,
+ },
+ {
+ type: 2,
+ tagName: 'meta',
+ attributes: { name: 'theme-color', content: '#000000' },
+ childNodes: [],
+ id: 7,
+ },
+ {
+ type: 2,
+ tagName: 'title',
+ attributes: {},
+ childNodes: [{ type: 3, textContent: '***** ***', id: 9 }],
+ id: 8,
+ },
+ ],
+ id: 4,
+ },
+ {
+ type: 2,
+ tagName: 'body',
+ attributes: {},
+ childNodes: [
+ {
+ type: 2,
+ tagName: 'noscript',
+ attributes: {},
+ childNodes: [{ type: 3, textContent: '*** **** ** ****** ********** ** *** **** ****', id: 12 }],
+ id: 11,
+ },
+ { type: 2, tagName: 'div', attributes: { id: 'root' }, childNodes: [], id: 13 },
+ ],
+ id: 10,
+ },
+ ],
+ id: 3,
+ },
+ ],
+ id: 1,
+ },
+ initialOffset: { left: 0, top: 0 },
+ },
+ timestamp: expect.any(Number),
+ },
+ {
+ type: 5,
+ timestamp: expect.any(Number),
+ data: {
+ tag: 'performanceSpan',
+ payload: {
+ op: 'memory',
+ description: 'memory',
+ startTimestamp: expect.any(Number),
+ endTimestamp: expect.any(Number),
+ data: {
+ memory: {
+ jsHeapSizeLimit: expect.any(Number),
+ totalJSHeapSize: expect.any(Number),
+ usedJSHeapSize: expect.any(Number),
+ },
+ },
+ },
+ },
+ },
+ {
+ type: 3,
+ data: {
+ source: 0,
+ texts: [],
+ attributes: [],
+ removes: [],
+ adds: [
+ {
+ parentId: 13,
+ nextId: null,
+ node: {
+ type: 2,
+ tagName: 'a',
+ attributes: { id: 'navigation', href: expect.stringMatching(/http:\/\/localhost:\d+\/user\/5/) },
+ childNodes: [],
+ id: 14,
+ },
+ },
+ { parentId: 14, nextId: null, node: { type: 3, textContent: '********', id: 15 } },
+ {
+ parentId: 13,
+ nextId: 14,
+ node: {
+ type: 2,
+ tagName: 'input',
+ attributes: { type: 'button', id: 'exception-button', value: '******* *********' },
+ childNodes: [],
+ id: 16,
+ },
+ },
+ ],
+ },
+ timestamp: expect.any(Number),
+ },
+ {
+ type: 3,
+ data: { source: 5, text: 'Capture Exception', isChecked: false, id: 16 },
+ timestamp: expect.any(Number),
+ },
+ ],
+ [
+ {
+ type: 5,
+ timestamp: expect.any(Number),
+ data: {
+ tag: 'performanceSpan',
+ payload: {
+ op: 'navigation.navigate',
+ description: expect.stringMatching(/http:\/\/localhost:\d+\//),
+ startTimestamp: expect.any(Number),
+ endTimestamp: expect.any(Number),
+ data: {
+ decodedBodySize: expect.any(Number),
+ encodedBodySize: expect.any(Number),
+ duration: expect.any(Number),
+ domInteractive: expect.any(Number),
+ domContentLoadedEventEnd: expect.any(Number),
+ domContentLoadedEventStart: expect.any(Number),
+ loadEventStart: expect.any(Number),
+ loadEventEnd: expect.any(Number),
+ domComplete: expect.any(Number),
+ redirectCount: expect.any(Number),
+ size: expect.any(Number),
+ },
+ },
+ },
+ },
+ {
+ type: 5,
+ timestamp: expect.any(Number),
+ data: {
+ tag: 'performanceSpan',
+ payload: {
+ op: 'resource.script',
+ description: expect.stringMatching(/http:\/\/localhost:\d+\/static\/js\/main.(\w+).js/),
+ startTimestamp: expect.any(Number),
+ endTimestamp: expect.any(Number),
+ data: {
+ decodedBodySize: expect.any(Number),
+ encodedBodySize: expect.any(Number),
+ size: expect.any(Number),
+ },
+ },
+ },
+ },
+ {
+ type: 5,
+ timestamp: expect.any(Number),
+ data: {
+ tag: 'performanceSpan',
+ payload: {
+ op: 'largest-contentful-paint',
+ description: 'largest-contentful-paint',
+ startTimestamp: expect.any(Number),
+ endTimestamp: expect.any(Number),
+ data: { value: expect.any(Number), size: expect.any(Number), nodeId: 16 },
+ },
+ },
+ },
+ {
+ type: 5,
+ timestamp: expect.any(Number),
+ data: {
+ tag: 'performanceSpan',
+ payload: {
+ op: 'paint',
+ description: 'first-paint',
+ startTimestamp: expect.any(Number),
+ endTimestamp: expect.any(Number),
+ },
+ },
+ },
+ {
+ type: 5,
+ timestamp: expect.any(Number),
+ data: {
+ tag: 'performanceSpan',
+ payload: {
+ op: 'paint',
+ description: 'first-contentful-paint',
+ startTimestamp: expect.any(Number),
+ endTimestamp: expect.any(Number),
+ },
+ },
+ },
+ {
+ type: 5,
+ timestamp: expect.any(Number),
+ data: {
+ tag: 'performanceSpan',
+ payload: {
+ op: 'memory',
+ description: 'memory',
+ startTimestamp: expect.any(Number),
+ endTimestamp: expect.any(Number),
+ data: {
+ memory: {
+ jsHeapSizeLimit: expect.any(Number),
+ totalJSHeapSize: expect.any(Number),
+ usedJSHeapSize: expect.any(Number),
+ },
+ },
+ },
+ },
+ },
+ ],
+];
diff --git a/packages/e2e-tests/test-applications/react-create-hash-router/tsconfig.json b/packages/e2e-tests/test-applications/react-create-hash-router/tsconfig.json
new file mode 100644
index 000000000000..c8df41dcf4b5
--- /dev/null
+++ b/packages/e2e-tests/test-applications/react-create-hash-router/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react"
+ },
+ "include": ["src", "tests"]
+}