diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bcbef8e06a8e..a846c6f90e77 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -994,6 +994,7 @@ jobs:
         test-application:
           [
             'angular-17',
+            'angular-18',
             'cloudflare-astro',
             'node-express',
             'create-react-app',
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/.editorconfig b/dev-packages/e2e-tests/test-applications/angular-18/.editorconfig
new file mode 100644
index 000000000000..59d9a3a3e73f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/.editorconfig
@@ -0,0 +1,16 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.ts]
+quote_type = single
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/.gitignore b/dev-packages/e2e-tests/test-applications/angular-18/.gitignore
new file mode 100644
index 000000000000..0711527ef9d5
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/.gitignore
@@ -0,0 +1,42 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# Compiled output
+/dist
+/tmp
+/out-tsc
+/bazel-out
+
+# Node
+/node_modules
+npm-debug.log
+yarn-error.log
+
+# IDEs and editors
+.idea/
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# Visual Studio Code
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# Miscellaneous
+/.angular/cache
+.sass-cache/
+/connect.lock
+/coverage
+/libpeerconnection.log
+testem.log
+/typings
+
+# System files
+.DS_Store
+Thumbs.db
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/.npmrc b/dev-packages/e2e-tests/test-applications/angular-18/.npmrc
new file mode 100644
index 000000000000..070f80f05092
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/.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/angular-18/README.md b/dev-packages/e2e-tests/test-applications/angular-18/README.md
new file mode 100644
index 000000000000..5a2f2e67c868
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/README.md
@@ -0,0 +1,3 @@
+# Angular 18
+
+E2E test app for Angular 18 and `@sentry/angular`.
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/angular.json b/dev-packages/e2e-tests/test-applications/angular-18/angular.json
new file mode 100644
index 000000000000..cb3c0b70cec6
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/angular.json
@@ -0,0 +1,95 @@
+{
+  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+  "version": 1,
+  "newProjectRoot": "projects",
+  "projects": {
+    "angular-18": {
+      "projectType": "application",
+      "schematics": {},
+      "root": "",
+      "sourceRoot": "src",
+      "prefix": "app",
+      "architect": {
+        "build": {
+          "builder": "@angular-devkit/build-angular:application",
+          "options": {
+            "outputPath": "dist/angular-18",
+            "index": "src/index.html",
+            "browser": "src/main.ts",
+            "polyfills": [
+              "zone.js"
+            ],
+            "tsConfig": "tsconfig.app.json",
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ],
+            "styles": [
+              "src/styles.css"
+            ],
+            "scripts": []
+          },
+          "configurations": {
+            "production": {
+              "budgets": [
+                {
+                  "type": "initial",
+                  "maximumWarning": "500kb",
+                  "maximumError": "1mb"
+                },
+                {
+                  "type": "anyComponentStyle",
+                  "maximumWarning": "2kb",
+                  "maximumError": "4kb"
+                }
+              ],
+              "outputHashing": "all"
+            },
+            "development": {
+              "optimization": false,
+              "extractLicenses": false,
+              "sourceMap": true
+            }
+          },
+          "defaultConfiguration": "production"
+        },
+        "serve": {
+          "builder": "@angular-devkit/build-angular:dev-server",
+          "configurations": {
+            "production": {
+              "buildTarget": "angular-18:build:production"
+            },
+            "development": {
+              "buildTarget": "angular-18:build:development"
+            }
+          },
+          "defaultConfiguration": "development"
+        },
+        "extract-i18n": {
+          "builder": "@angular-devkit/build-angular:extract-i18n",
+          "options": {
+            "buildTarget": "angular-18:build"
+          }
+        },
+        "test": {
+          "builder": "@angular-devkit/build-angular:karma",
+          "options": {
+            "polyfills": [
+              "zone.js",
+              "zone.js/testing"
+            ],
+            "tsConfig": "tsconfig.spec.json",
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ],
+            "styles": [
+              "src/styles.css"
+            ],
+            "scripts": []
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/package.json b/dev-packages/e2e-tests/test-applications/angular-18/package.json
new file mode 100644
index 000000000000..1c2bf8b723cb
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/package.json
@@ -0,0 +1,51 @@
+{
+  "name": "angular-18",
+  "version": "0.0.0",
+  "scripts": {
+    "ng": "ng",
+    "dev": "ng serve",
+    "proxy": "node start-event-proxy.mjs",
+    "preview": "http-server dist/angular-18/browser --port 8080",
+    "build": "ng build",
+    "watch": "ng build --watch --configuration development",
+    "test": "playwright test",
+    "test:build": "pnpm install && npx playwright install && pnpm build",
+    "test:assert": "playwright test",
+    "clean": "npx rimraf .angular node_modules pnpm-lock.yaml dist"
+  },
+  "private": true,
+  "dependencies": {
+    "@angular/animations": "^18.0.0",
+    "@angular/common": "^18.0.0",
+    "@angular/compiler": "^18.0.0",
+    "@angular/core": "^18.0.0",
+    "@angular/forms": "^18.0.0",
+    "@angular/platform-browser": "^18.0.0",
+    "@angular/platform-browser-dynamic": "^18.0.0",
+    "@angular/router": "^18.0.0",
+    "@sentry/angular": "* || latest",
+    "rxjs": "~7.8.0",
+    "tslib": "^2.3.0",
+    "zone.js": "~0.14.3"
+  },
+  "devDependencies": {
+    "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server",
+    "@angular-devkit/build-angular": "^18.0.0",
+    "@angular/cli": "^18.0.0",
+    "@angular/compiler-cli": "^18.0.0",
+    "@playwright/test": "^1.41.1",
+    "@types/jasmine": "~5.1.0",
+    "http-server": "^14.1.1",
+    "jasmine-core": "~5.1.0",
+    "karma": "~6.4.0",
+    "karma-chrome-launcher": "~3.2.0",
+    "karma-coverage": "~2.2.0",
+    "karma-jasmine": "~5.1.0",
+    "karma-jasmine-html-reporter": "~2.1.0",
+    "typescript": "~5.4.5",
+    "wait-port": "1.0.4"
+  },
+  "volta": {
+    "extends": "../../package.json"
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts b/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts
new file mode 100644
index 000000000000..df7c2d9758e9
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts
@@ -0,0 +1,77 @@
+import type { PlaywrightTestConfig } from '@playwright/test';
+import { devices } from '@playwright/test';
+
+// Fix urls not resolving to localhost on Node v17+
+// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575
+import { setDefaultResultOrder } from 'dns';
+setDefaultResultOrder('ipv4first');
+
+const testEnv = process.env['TEST_ENV'] || 'production';
+
+if (!testEnv) {
+  throw new Error('No test env defined');
+}
+
+const angularPort = 8080;
+const eventProxyPort = 3031;
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+const config: PlaywrightTestConfig = {
+  testDir: './tests',
+  /* Maximum time one test can run for. */
+  timeout: 150_000,
+  expect: {
+    /**
+     * Maximum time expect() should wait for the condition to be met.
+     * For example in `await expect(locator).toHaveText();`
+     */
+    timeout: 10000,
+  },
+  fullyParallel: false,
+  workers: 1,
+  /* Fail the build on CI if you accidentally left test.only in the source code. */
+  forbidOnly: !!process.env.CI,
+  /* `next dev` is incredibly buggy with the app dir */
+  retries: testEnv === 'development' ? 3 : 0,
+  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+  reporter: 'list',
+  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+  use: {
+    /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
+    actionTimeout: 0,
+    /* Base URL to use in actions like `await page.goto('/')`. */
+    baseURL: `http://localhost:${angularPort}`,
+
+    /* 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'],
+      },
+    },
+  ],
+
+  /* Run your local dev server before starting the tests */
+  webServer: [
+    {
+      command: 'node start-event-proxy.mjs',
+      port: eventProxyPort,
+    },
+    {
+      command:
+        testEnv === 'development'
+          ? `pnpm wait-port ${eventProxyPort} && pnpm preview -p ${angularPort}`
+          : `pnpm wait-port ${eventProxyPort} && pnpm preview -p ${angularPort}`,
+      port: angularPort,
+    },
+  ],
+};
+
+export default config;
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.component.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.component.ts
new file mode 100644
index 000000000000..ab3efd7e16f3
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+import { RouterOutlet } from '@angular/router';
+
+@Component({
+  selector: 'app-root',
+  standalone: true,
+  imports: [RouterOutlet],
+  template: ``,
+})
+export class AppComponent {
+  title = 'angular-18';
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.config.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.config.ts
new file mode 100644
index 000000000000..8267759c8ba1
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.config.ts
@@ -0,0 +1,25 @@
+import { APP_INITIALIZER, ApplicationConfig, ErrorHandler } from '@angular/core';
+import { Router, provideRouter } from '@angular/router';
+
+import { TraceService, createErrorHandler } from '@sentry/angular';
+import { routes } from './app.routes';
+
+export const appConfig: ApplicationConfig = {
+  providers: [
+    provideRouter(routes),
+    {
+      provide: ErrorHandler,
+      useValue: createErrorHandler(),
+    },
+    {
+      provide: TraceService,
+      deps: [Router],
+    },
+    {
+      provide: APP_INITIALIZER,
+      useFactory: () => () => {},
+      deps: [TraceService],
+      multi: true,
+    },
+  ],
+};
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.routes.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.routes.ts
new file mode 100644
index 000000000000..24bf8b769051
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.routes.ts
@@ -0,0 +1,42 @@
+import { Routes } from '@angular/router';
+import { cancelGuard } from './cancel-guard.guard';
+import { CancelComponent } from './cancel/cancel.components';
+import { ComponentTrackingComponent } from './component-tracking/component-tracking.components';
+import { HomeComponent } from './home/home.component';
+import { UserComponent } from './user/user.component';
+
+export const routes: Routes = [
+  {
+    path: 'users/:id',
+    component: UserComponent,
+  },
+  {
+    path: 'home',
+    component: HomeComponent,
+  },
+  {
+    path: 'cancel',
+    component: CancelComponent,
+    canActivate: [cancelGuard],
+  },
+  {
+    path: 'component-tracking',
+    component: ComponentTrackingComponent,
+  },
+  {
+    path: 'redirect1',
+    redirectTo: '/redirect2',
+  },
+  {
+    path: 'redirect2',
+    redirectTo: '/redirect3',
+  },
+  {
+    path: 'redirect3',
+    redirectTo: '/users/456',
+  },
+  {
+    path: '**',
+    redirectTo: 'home',
+  },
+];
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel-guard.guard.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel-guard.guard.ts
new file mode 100644
index 000000000000..16ec4a2ab164
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel-guard.guard.ts
@@ -0,0 +1,5 @@
+import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';
+
+export const cancelGuard: CanActivateFn = (_next: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
+  return false;
+};
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel/cancel.components.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel/cancel.components.ts
new file mode 100644
index 000000000000..b6ee1876e035
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel/cancel.components.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-cancel',
+  standalone: true,
+  template: `
`,
+})
+export class CancelComponent {}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/component-tracking/component-tracking.components.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/component-tracking/component-tracking.components.ts
new file mode 100644
index 000000000000..d437a1d43fdd
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/component-tracking/component-tracking.components.ts
@@ -0,0 +1,18 @@
+import { AfterViewInit, Component, OnInit } from '@angular/core';
+import { TraceClass, TraceMethod, TraceModule } from '@sentry/angular';
+import { SampleComponent } from '../sample-component/sample-component.components';
+
+@Component({
+  selector: 'app-cancel',
+  standalone: true,
+  imports: [TraceModule, SampleComponent],
+  template: ``,
+})
+@TraceClass({ name: 'ComponentTrackingComponent' })
+export class ComponentTrackingComponent implements OnInit, AfterViewInit {
+  @TraceMethod({ name: 'ngOnInit' })
+  ngOnInit() {}
+
+  @TraceMethod()
+  ngAfterViewInit() {}
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/home/home.component.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/home/home.component.ts
new file mode 100644
index 000000000000..9f36814d6c03
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/home/home.component.ts
@@ -0,0 +1,26 @@
+import { Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
+
+@Component({
+  selector: 'app-home',
+  standalone: true,
+  imports: [RouterLink],
+  template: `
+  
+    Welcome to Sentry's Angular 18 E2E test app
+    
+    
+  
+`,
+})
+export class HomeComponent {
+  throwError() {
+    throw new Error('Error thrown from Angular 18 E2E test app');
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/sample-component/sample-component.components.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/sample-component/sample-component.components.ts
new file mode 100644
index 000000000000..da09425c7565
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/sample-component/sample-component.components.ts
@@ -0,0 +1,12 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-sample-component',
+  standalone: true,
+  template: `Component
`,
+})
+export class SampleComponent implements OnInit {
+  ngOnInit() {
+    console.log('SampleComponent');
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/user/user.component.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/user/user.component.ts
new file mode 100644
index 000000000000..db02568d395f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/user/user.component.ts
@@ -0,0 +1,25 @@
+import { AsyncPipe } from '@angular/common';
+import { Component } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Observable, map } from 'rxjs';
+
+@Component({
+  selector: 'app-user',
+  standalone: true,
+  imports: [AsyncPipe],
+  template: `
+    Hello User {{ userId$ | async }}
+    
+  `,
+})
+export class UserComponent {
+  public userId$: Observable;
+
+  constructor(private route: ActivatedRoute) {
+    this.userId$ = this.route.paramMap.pipe(map(params => params.get('id') || 'UNKNOWN USER'));
+  }
+
+  throwError() {
+    throw new Error('Error thrown from user page');
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/favicon.ico b/dev-packages/e2e-tests/test-applications/angular-18/src/favicon.ico
new file mode 100644
index 000000000000..57614f9c9675
Binary files /dev/null and b/dev-packages/e2e-tests/test-applications/angular-18/src/favicon.ico differ
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/index.html b/dev-packages/e2e-tests/test-applications/angular-18/src/index.html
new file mode 100644
index 000000000000..075475aa383e
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+  
+  Angular 18
+  
+  
+  
+
+
+  
+
+
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/main.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/main.ts
new file mode 100644
index 000000000000..947f40691b05
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/main.ts
@@ -0,0 +1,15 @@
+import { bootstrapApplication } from '@angular/platform-browser';
+import { AppComponent } from './app/app.component';
+import { appConfig } from './app/app.config';
+
+import * as Sentry from '@sentry/angular';
+
+Sentry.init({
+  dsn: 'https://3b6c388182fb435097f41d181be2b2ba@o4504321058471936.ingest.sentry.io/4504321066008576',
+  tracesSampleRate: 1.0,
+  integrations: [Sentry.browserTracingIntegration({})],
+  tunnel: `http://localhost:3031/`, // proxy server
+  debug: true,
+});
+
+bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/styles.css b/dev-packages/e2e-tests/test-applications/angular-18/src/styles.css
new file mode 100644
index 000000000000..90d4ee0072ce
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/styles.css
@@ -0,0 +1 @@
+/* You can add global styles to this file, and also import other style files */
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/angular-18/start-event-proxy.mjs
new file mode 100644
index 000000000000..696f1807d8f1
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/start-event-proxy.mjs
@@ -0,0 +1,6 @@
+import { startEventProxyServer } from '@sentry-internal/event-proxy-server';
+
+startEventProxyServer({
+  port: 3031,
+  proxyServerName: 'angular-18',
+});
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/angular-18/tests/errors.test.ts
new file mode 100644
index 000000000000..a255d2130dda
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/tests/errors.test.ts
@@ -0,0 +1,65 @@
+import { expect, test } from '@playwright/test';
+import { waitForError, waitForTransaction } from '@sentry-internal/event-proxy-server';
+
+test('sends an error', async ({ page }) => {
+  const errorPromise = waitForError('angular-18', async errorEvent => {
+    return !errorEvent.type;
+  });
+
+  await page.goto(`/`);
+
+  await page.locator('#errorBtn').click();
+
+  const error = await errorPromise;
+
+  expect(error).toMatchObject({
+    exception: {
+      values: [
+        {
+          type: 'Error',
+          value: 'Error thrown from Angular 18 E2E test app',
+          mechanism: {
+            type: 'angular',
+            handled: false,
+          },
+        },
+      ],
+    },
+    transaction: '/home/',
+  });
+});
+
+test('assigns the correct transaction value after a navigation', async ({ page }) => {
+  const pageloadTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+    return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+  });
+
+  const errorPromise = waitForError('angular-18', async errorEvent => {
+    return !errorEvent.type;
+  });
+
+  await page.goto(`/`);
+  await pageloadTxnPromise;
+
+  await page.waitForTimeout(5000);
+
+  await page.locator('#navLink').click();
+
+  const [_, error] = await Promise.all([page.locator('#userErrorBtn').click(), errorPromise]);
+
+  expect(error).toMatchObject({
+    exception: {
+      values: [
+        {
+          type: 'Error',
+          value: 'Error thrown from user page',
+          mechanism: {
+            type: 'angular',
+            handled: false,
+          },
+        },
+      ],
+    },
+    transaction: '/users/:id/',
+  });
+});
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/angular-18/tests/performance.test.ts
new file mode 100644
index 000000000000..12f0fbd41133
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/tests/performance.test.ts
@@ -0,0 +1,313 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/event-proxy-server';
+import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
+
+test('sends a pageload transaction with a parameterized URL', async ({ page }) => {
+  const transactionPromise = waitForTransaction('angular-18', 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.angular',
+      },
+    },
+    transaction: '/home/',
+    transaction_info: {
+      source: 'route',
+    },
+  });
+});
+
+test('sends a navigation transaction with a parameterized URL', async ({ page }) => {
+  const pageloadTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+    return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+  });
+
+  const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+    return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+  });
+
+  await page.goto(`/`);
+  await pageloadTxnPromise;
+
+  await page.waitForTimeout(5000);
+
+  const [_, navigationTxn] = await Promise.all([page.locator('#navLink').click(), navigationTxnPromise]);
+
+  expect(navigationTxn).toMatchObject({
+    contexts: {
+      trace: {
+        op: 'navigation',
+      },
+    },
+    transaction: '/users/:id/',
+    transaction_info: {
+      source: 'route',
+    },
+  });
+});
+
+test('sends a navigation transaction even if the pageload span is still active', async ({ page }) => {
+  const pageloadTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+    return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+  });
+
+  const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+    return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+  });
+
+  await page.goto(`/`);
+
+  // immediately navigate to a different route
+  const [_, pageloadTxn, navigationTxn] = await Promise.all([
+    page.locator('#navLink').click(),
+    pageloadTxnPromise,
+    navigationTxnPromise,
+  ]);
+
+  expect(pageloadTxn).toMatchObject({
+    contexts: {
+      trace: {
+        op: 'pageload',
+        origin: 'auto.pageload.angular',
+      },
+    },
+    transaction: '/home/',
+    transaction_info: {
+      source: 'route',
+    },
+  });
+
+  expect(navigationTxn).toMatchObject({
+    contexts: {
+      trace: {
+        op: 'navigation',
+        origin: 'auto.navigation.angular',
+      },
+    },
+    transaction: '/users/:id/',
+    transaction_info: {
+      source: 'route',
+    },
+  });
+});
+
+test('groups redirects within one navigation root span', async ({ page }) => {
+  const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+    return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+  });
+
+  await page.goto(`/`);
+
+  // immediately navigate to a different route
+  const [_, navigationTxn] = await Promise.all([page.locator('#redirectLink').click(), navigationTxnPromise]);
+
+  expect(navigationTxn).toMatchObject({
+    contexts: {
+      trace: {
+        op: 'navigation',
+        origin: 'auto.navigation.angular',
+      },
+    },
+    transaction: '/users/:id/',
+    transaction_info: {
+      source: 'route',
+    },
+  });
+
+  const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
+
+  expect(routingSpan).toBeDefined();
+  expect(routingSpan?.description).toBe('/redirect1');
+});
+
+test.describe('finish routing span', () => {
+  test('finishes routing span on navigation cancel', async ({ page }) => {
+    const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+      return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+    });
+
+    await page.goto(`/`);
+
+    // immediately navigate to a different route
+    const [_, navigationTxn] = await Promise.all([page.locator('#cancelLink').click(), navigationTxnPromise]);
+
+    expect(navigationTxn).toMatchObject({
+      contexts: {
+        trace: {
+          op: 'navigation',
+          origin: 'auto.navigation.angular',
+        },
+      },
+      transaction: '/cancel',
+      transaction_info: {
+        source: 'url',
+      },
+    });
+
+    const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
+
+    expect(routingSpan).toBeDefined();
+    expect(routingSpan?.description).toBe('/cancel');
+  });
+
+  test('finishes routing span on navigation error', async ({ page }) => {
+    const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+      return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+    });
+
+    await page.goto(`/`);
+
+    // immediately navigate to a different route
+    const [_, navigationTxn] = await Promise.all([page.locator('#nonExistentLink').click(), navigationTxnPromise]);
+
+    const nonExistentRoute = '/non-existent';
+
+    expect(navigationTxn).toMatchObject({
+      contexts: {
+        trace: {
+          op: 'navigation',
+          origin: 'auto.navigation.angular',
+        },
+      },
+      transaction: nonExistentRoute,
+      transaction_info: {
+        source: 'url',
+      },
+    });
+
+    const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
+
+    expect(routingSpan).toBeDefined();
+    expect(routingSpan?.description).toBe(nonExistentRoute);
+  });
+});
+
+test.describe('TraceDirective', () => {
+  test('creates a child tracingSpan with component name as span name on ngOnInit', async ({ page }) => {
+    const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+      return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+    });
+
+    await page.goto(`/`);
+
+    // immediately navigate to a different route
+    const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+    const traceDirectiveSpan = navigationTxn.spans?.find(
+      span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_directive',
+    );
+
+    expect(traceDirectiveSpan).toBeDefined();
+    expect(traceDirectiveSpan).toEqual(
+      expect.objectContaining({
+        data: {
+          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
+          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
+        },
+        description: '',
+        op: 'ui.angular.init',
+        origin: 'auto.ui.angular.trace_directive',
+        start_timestamp: expect.any(Number),
+        timestamp: expect.any(Number),
+      }),
+    );
+  });
+});
+
+test.describe('TraceClass Decorator', () => {
+  test('adds init span for decorated class', async ({ page }) => {
+    const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+      return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+    });
+
+    await page.goto(`/`);
+
+    // immediately navigate to a different route
+    const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+    const classDecoratorSpan = navigationTxn.spans?.find(
+      span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_class_decorator',
+    );
+
+    expect(classDecoratorSpan).toBeDefined();
+    expect(classDecoratorSpan).toEqual(
+      expect.objectContaining({
+        data: {
+          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
+          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_class_decorator',
+        },
+        description: '',
+        op: 'ui.angular.init',
+        origin: 'auto.ui.angular.trace_class_decorator',
+        start_timestamp: expect.any(Number),
+        timestamp: expect.any(Number),
+      }),
+    );
+  });
+});
+
+test.describe('TraceMethod Decorator', () => {
+  test('adds name to span description of decorated method `ngOnInit`', async ({ page }) => {
+    const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+      return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+    });
+
+    await page.goto(`/`);
+
+    // immediately navigate to a different route
+    const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+    const ngInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngOnInit');
+
+    expect(ngInitSpan).toBeDefined();
+    expect(ngInitSpan).toEqual(
+      expect.objectContaining({
+        data: {
+          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngOnInit',
+          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
+        },
+        description: '',
+        op: 'ui.angular.ngOnInit',
+        origin: 'auto.ui.angular.trace_method_decorator',
+        start_timestamp: expect.any(Number),
+        timestamp: expect.any(Number),
+      }),
+    );
+  });
+
+  test('adds fallback name to span description of decorated method `ngAfterViewInit`', async ({ page }) => {
+    const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+      return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+    });
+
+    await page.goto(`/`);
+
+    // immediately navigate to a different route
+    const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+    const ngAfterViewInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngAfterViewInit');
+
+    expect(ngAfterViewInitSpan).toBeDefined();
+    expect(ngAfterViewInitSpan).toEqual(
+      expect.objectContaining({
+        data: {
+          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngAfterViewInit',
+          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
+        },
+        description: '',
+        op: 'ui.angular.ngAfterViewInit',
+        origin: 'auto.ui.angular.trace_method_decorator',
+        start_timestamp: expect.any(Number),
+        timestamp: expect.any(Number),
+      }),
+    );
+  });
+});
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.app.json b/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.app.json
new file mode 100644
index 000000000000..374cc9d294aa
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.app.json
@@ -0,0 +1,14 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "./out-tsc/app",
+    "types": []
+  },
+  "files": [
+    "src/main.ts"
+  ],
+  "include": [
+    "src/**/*.d.ts"
+  ]
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.json b/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.json
new file mode 100644
index 000000000000..f37b67ff0277
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.json
@@ -0,0 +1,33 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+  "compileOnSave": false,
+  "compilerOptions": {
+    "outDir": "./dist/out-tsc",
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "noImplicitOverride": true,
+    "noPropertyAccessFromIndexSignature": true,
+    "noImplicitReturns": true,
+    "noFallthroughCasesInSwitch": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "sourceMap": true,
+    "declaration": false,
+    "experimentalDecorators": true,
+    "moduleResolution": "node",
+    "importHelpers": true,
+    "target": "ES2022",
+    "module": "ES2022",
+    "useDefineForClassFields": false,
+    "lib": [
+      "ES2022",
+      "dom"
+    ]
+  },
+  "angularCompilerOptions": {
+    "enableI18nLegacyMessageIdFormat": false,
+    "strictInjectionParameters": true,
+    "strictInputAccessModifiers": true,
+    "strictTemplates": true
+  }
+}
diff --git a/packages/angular/package.json b/packages/angular/package.json
index 5984a62c62f3..d0a363fc8d45 100644
--- a/packages/angular/package.json
+++ b/packages/angular/package.json
@@ -15,9 +15,9 @@
     "access": "public"
   },
   "peerDependencies": {
-    "@angular/common": ">= 14.x <= 17.x",
-    "@angular/core": ">= 14.x <= 17.x",
-    "@angular/router": ">= 14.x <= 17.x",
+    "@angular/common": ">= 14.x <= 18.x",
+    "@angular/core": ">= 14.x <= 18.x",
+    "@angular/router": ">= 14.x <= 18.x",
     "rxjs": "^6.5.5 || ^7.x"
   },
   "dependencies": {