diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3df961a6c778..bee434e40dc2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1015,6 +1015,8 @@ jobs: 'node-express-esm-without-loader', 'node-express-cjs-preload', 'node-otel-sdk-node', + 'ember-classic', + 'ember-embroider', 'nextjs-app-dir', 'nextjs-14', 'nextjs-15', diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index b8cffb3698ea..f861f4b4ae3b 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -151,45 +151,3 @@ jobs: with: filename: .github/CANARY_FAILURE_TEMPLATE.md update_existing: true - - job_ember_canary_test: - name: Ember Canary Tests - runs-on: ubuntu-20.04 - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - # scenario: [ember-release, embroider-optimized, ember-4.0] - scenario: [ember-4.0] - steps: - - name: 'Check out current commit' - uses: actions/checkout@v4 - with: - ref: ${{ env.HEAD_COMMIT }} - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version-file: 'package.json' - - name: Install dependencies - run: yarn install --ignore-engines --frozen-lockfile - - - name: Build dependencies - run: | - yarn lerna run build:types --scope=@sentry/ember --include-dependencies - yarn lerna run build:transpile --scope=@sentry/ember --include-dependencies - - - name: Run Ember tests - run: | - cd packages/ember - yarn ember try:one ${{ matrix.scenario }} --skip-cleanup=true - - - name: Create Issue - if: failure() && github.event_name == 'schedule' - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RUN_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - TITLE: Ember Canary ${{ matrix.scenario }} Test Failed - with: - filename: .github/CANARY_FAILURE_TEMPLATE.md - update_existing: true diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json index 4d41ba051e4b..e16d49c799f4 100644 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json +++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", - "@playwright/test": "^1.41.1", + "@playwright/test": "^1.44.1", "wait-port": "1.0.4" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/.editorconfig b/dev-packages/e2e-tests/test-applications/ember-classic/.editorconfig new file mode 100644 index 000000000000..c35a002406b9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.hbs] +insert_final_newline = false + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/.ember-cli b/dev-packages/e2e-tests/test-applications/ember-classic/.ember-cli new file mode 100644 index 000000000000..4ccb4bf43700 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/.ember-cli @@ -0,0 +1,15 @@ +{ + /** + Ember CLI sends analytics information by default. The data is completely + anonymous, but there are times when you might want to disable this behavior. + + Setting `disableAnalytics` to true will prevent any data from being sent. + */ + "disableAnalytics": false, + + /** + Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript + rather than JavaScript by default, when a TypeScript version of a given blueprint is available. + */ + "isTypeScriptProject": false +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/.gitignore b/dev-packages/e2e-tests/test-applications/ember-classic/.gitignore new file mode 100644 index 000000000000..f1e859b291c4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/.gitignore @@ -0,0 +1,32 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist/ +/tmp/ + +# dependencies +/bower_components/ +/node_modules/ + +# misc +/.env* +/.pnp* +/.sass-cache +/.eslintcache +/connect.lock +/coverage/ +/libpeerconnection.log +/npm-debug.log* +/testem.log +/yarn-error.log + +# ember-try +/.node_modules.ember-try/ +/bower.json.ember-try +/npm-shrinkwrap.json.ember-try +/package.json.ember-try +/package-lock.json.ember-try +/yarn.lock.ember-try + +# broccoli-debug +/DEBUG/ diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/.npmrc b/dev-packages/e2e-tests/test-applications/ember-classic/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/.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/ember-classic/.watchmanconfig b/dev-packages/e2e-tests/test-applications/ember-classic/.watchmanconfig new file mode 100644 index 000000000000..e7834e3e4f39 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/.watchmanconfig @@ -0,0 +1,3 @@ +{ + "ignore_dirs": ["tmp", "dist"] +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/README.md b/dev-packages/e2e-tests/test-applications/ember-classic/README.md new file mode 100644 index 000000000000..e07f12913b1d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/README.md @@ -0,0 +1,56 @@ +# ember-classic + +This README outlines the details of collaborating on this Ember application. A short introduction of this app could +easily go here. + +## Prerequisites + +You will need the following things properly installed on your computer. + +- [Git](https://git-scm.com/) +- [Node.js](https://nodejs.org/) (with npm) +- [Ember CLI](https://cli.emberjs.com/release/) +- [Google Chrome](https://google.com/chrome/) + +## Installation + +- `git clone ` this repository +- `cd ember-classic` +- `npm install` + +## Running / Development + +- `ember serve` +- Visit your app at [http://localhost:4200](http://localhost:4200). +- Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). + +### Code Generators + +Make use of the many generators for code, try `ember help generate` for more details + +### Running Tests + +- `ember test` +- `ember test --server` + +### Linting + +- `npm run lint` +- `npm run lint:fix` + +### Building + +- `ember build` (development) +- `ember build --environment production` (production) + +### Deploying + +Specify what it takes to deploy your app. + +## Further Reading / Useful Links + +- [ember.js](https://emberjs.com/) +- [ember-cli](https://cli.emberjs.com/release/) +- Development Browser Extensions + - [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) + - [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/app.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/app.ts new file mode 100644 index 000000000000..a37eadb8fff6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/app.ts @@ -0,0 +1,20 @@ +import Application from '@ember/application'; +import * as Sentry from '@sentry/ember'; +import loadInitializers from 'ember-load-initializers'; +import Resolver from 'ember-resolver'; + +import config from './config/environment'; + +Sentry.init({ + replaysSessionSampleRate: 1, + replaysOnErrorSampleRate: 1, + tunnel: `http://localhost:3031/`, // proxy server +}); + +export default class App extends Application { + public modulePrefix = config.modulePrefix; + public podModulePrefix = config.podModulePrefix; + public Resolver = Resolver; +} + +loadInitializers(App, config.modulePrefix); diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/components/link.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/link.hbs new file mode 100644 index 000000000000..c6a18f9e37cc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/link.hbs @@ -0,0 +1,3 @@ + + {{yield}} + diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/components/link.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/link.ts new file mode 100644 index 000000000000..1ba66df216fc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/link.ts @@ -0,0 +1,33 @@ +import { action } from '@ember/object'; +import type RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; +import Component from '@glimmer/component'; + +interface Args { + route: string; +} + +/* + Note: We use this custom component instead of the built-in ``, + as that is an ember component in older versions, and a glimmer component in newer versions. + + Since glimmer components are, as of now, not instrumented, this leads to different test results. +*/ +export default class LinkComponent extends Component { + @service public declare router: RouterService; + + public get href(): string { + return this.router.urlFor(this.args.route); + } + + public get isActive(): boolean { + return this.router.currentRouteName === this.args.route; + } + + @action + public onClick(event: MouseEvent): void { + event.preventDefault(); + + void this.router.transitionTo(this.args.route); + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/components/slow-loading-gc-list.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/slow-loading-gc-list.ts new file mode 100644 index 000000000000..3ac89dc43ca7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/slow-loading-gc-list.ts @@ -0,0 +1,4 @@ +/* eslint-disable ember/no-empty-glimmer-component-classes */ +import Component from '@glimmer/component'; + +export default class SlowLoadingGCList extends Component {} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/components/slow-loading-list.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/slow-loading-list.ts new file mode 100644 index 000000000000..e766fe78609f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/slow-loading-list.ts @@ -0,0 +1,25 @@ +/* eslint-disable ember/no-classic-classes */ +/* eslint-disable ember/no-classic-components */ +import Component from '@ember/component'; +import { computed } from '@ember/object'; + +interface Args { + title?: string; + items: number; +} + +export default Component.extend({ + tagName: '', + + _title: computed('title', function () { + return (this as Args).title || 'Slow Loading List'; + }), + + rowItems: computed('items', function () { + return new Array((this as Args).items).fill(0).map((_, index) => { + return { + index: index + 1, + }; + }); + }), +}); diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/components/test-section.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/test-section.ts new file mode 100644 index 000000000000..d0ca7e8edabc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/components/test-section.ts @@ -0,0 +1,6 @@ +/* eslint-disable ember/no-classic-classes */ +/* eslint-disable ember/no-classic-components */ +/* eslint-disable ember/require-tagless-components */ +import Component from '@ember/component'; + +export default Component.extend({}); diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/config/environment.d.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/config/environment.d.ts new file mode 100644 index 000000000000..8a8a687909e4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/config/environment.d.ts @@ -0,0 +1,17 @@ +/** + * Type declarations for + * import config from './config/environment' + * + * For now these need to be managed by the developer + * since different ember addons can materialize new entries. + */ +declare const config: { + environment: string; + modulePrefix: string; + podModulePrefix: string; + locationType: 'history' | 'hash' | 'none' | 'auto'; + rootURL: string; + APP: Record; +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/index.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/index.ts new file mode 100644 index 000000000000..c49ff8d94147 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/index.ts @@ -0,0 +1,63 @@ +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import { scheduleOnce } from '@ember/runloop'; +import { tracked } from '@glimmer/tracking'; +import { Promise } from 'rsvp'; + +export default class IndexController extends Controller { + @tracked public showComponents = false; + + @action + public createError(): void { + // @ts-expect-error this is fine + this.nonExistentFunction(); + } + + @action + public createEmberError(): void { + throw new Error('Whoops, looks like you have an EmberError'); + } + + @action + public createCaughtEmberError(): void { + try { + throw new Error('Looks like you have a caught EmberError'); + } catch (e) { + // do nothing + } + } + + @action + public createFetchError(): void { + void fetch('http://doesntexist.example'); + } + + @action + public createAfterRenderError(): void { + function throwAfterRender(): void { + throw new Error('After Render Error'); + } + scheduleOnce('afterRender', null, throwAfterRender); + } + + @action + public createRSVPRejection(): Promise { + const promise = new Promise((resolve, reject) => { + reject('Promise rejected'); + }); + return promise; + } + + @action + public createRSVPError(): Promise { + const promise = new Promise(() => { + throw new Error('Error within RSVP Promise'); + }); + return promise; + } + + @action + public toggleShowComponents(): void { + this.showComponents = !this.showComponents; + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/slow-loading-route.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/slow-loading-route.ts new file mode 100644 index 000000000000..01a523ea0985 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/slow-loading-route.ts @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import type RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; + +export default class SlowLoadingRouteController extends Controller { + @service public declare router: RouterService; + + @action + public back(): void { + void this.router.transitionTo('tracing'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/slow-loading-route/index.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/slow-loading-route/index.ts new file mode 100644 index 000000000000..b66350b5c911 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/slow-loading-route/index.ts @@ -0,0 +1,5 @@ +import Controller from '@ember/controller'; + +export default class SlowLoadingRouteController extends Controller { + public slowLoadingTemplateOnlyItems = new Array(2000).fill(0).map((_, index) => index); +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/tracing.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/tracing.ts new file mode 100644 index 000000000000..72c0d635702e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/tracing.ts @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import type RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; + +export default class TracingController extends Controller { + @service public declare router: RouterService; + + @action + public navigateToSlowRoute(): void { + void this.router.transitionTo('slow-loading-route'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/helpers/utils.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/helpers/utils.ts new file mode 100644 index 000000000000..60a3f2956224 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/helpers/utils.ts @@ -0,0 +1,3 @@ +export default function timeout(time: number): Promise { + return new Promise(resolve => setTimeout(resolve, time)); +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/index.html b/dev-packages/e2e-tests/test-applications/ember-classic/app/index.html new file mode 100644 index 000000000000..4be4ec8973e5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/index.html @@ -0,0 +1,24 @@ + + + + + EmberClassic + + + + {{content-for "head"}} + + + + + {{content-for "head-footer"}} + + + {{content-for "body"}} + + + + + {{content-for "body-footer"}} + + diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/initializers/deprecation.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/initializers/deprecation.ts new file mode 100644 index 000000000000..fcc2d180532e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/initializers/deprecation.ts @@ -0,0 +1,13 @@ +import { registerDeprecationHandler } from '@ember/debug'; + +export function initialize(): void { + registerDeprecationHandler((message, options, next) => { + if (options && options.until && options.until !== '3.0.0') { + return; + } else { + next(message, options); + } + }); +} + +export default { initialize }; diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/router.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/router.ts new file mode 100644 index 000000000000..e13dec6d82c5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/router.ts @@ -0,0 +1,18 @@ +import EmberRouter from '@ember/routing/router'; + +import config from './config/environment'; + +export default class Router extends EmberRouter { + public location = config.locationType; + public rootURL = config.rootURL; +} + +// This is a false positive of the eslint rule +// eslint-disable-next-line array-callback-return +Router.map(function () { + this.route('tracing'); + this.route('replay'); + this.route('slow-loading-route', function () { + this.route('index', { path: '/' }); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/routes/replay.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/routes/replay.ts new file mode 100644 index 000000000000..20e5200760b3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/routes/replay.ts @@ -0,0 +1,13 @@ +import Route from '@ember/routing/route'; +import type { BrowserClient } from '@sentry/ember'; +import * as Sentry from '@sentry/ember'; + +export default class ReplayRoute extends Route { + public async beforeModel(): Promise { + const { replayIntegration } = Sentry; + const client = Sentry.getClient(); + if (client && !client.getIntegrationByName('Replay')) { + client.addIntegration(replayIntegration()); + } + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/routes/slow-loading-route.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/routes/slow-loading-route.ts new file mode 100644 index 000000000000..96f57bd9cf2d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/routes/slow-loading-route.ts @@ -0,0 +1,26 @@ +import Route from '@ember/routing/route'; +import { instrumentRoutePerformance } from '@sentry/ember'; + +import timeout from '../helpers/utils'; + +const SLOW_TRANSITION_WAIT = 1500; + +class SlowDefaultLoadingRoute extends Route { + public beforeModel(): Promise { + return timeout(SLOW_TRANSITION_WAIT / 3); + } + + public model(): Promise { + return timeout(SLOW_TRANSITION_WAIT / 3); + } + + public afterModel(): Promise { + return timeout(SLOW_TRANSITION_WAIT / 3); + } + + public setupController(...rest: Parameters): ReturnType { + super.setupController(...rest); + } +} + +export default instrumentRoutePerformance(SlowDefaultLoadingRoute); diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/routes/slow-loading-route/index.ts b/dev-packages/e2e-tests/test-applications/ember-classic/app/routes/slow-loading-route/index.ts new file mode 100644 index 000000000000..c810ca5e2505 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/routes/slow-loading-route/index.ts @@ -0,0 +1,26 @@ +import Route from '@ember/routing/route'; +import { instrumentRoutePerformance } from '@sentry/ember'; + +import timeout from '../../helpers/utils'; + +const SLOW_TRANSITION_WAIT = 1500; + +class SlowLoadingRoute extends Route { + public beforeModel(): Promise { + return timeout(SLOW_TRANSITION_WAIT / 3); + } + + public model(): Promise { + return timeout(SLOW_TRANSITION_WAIT / 3); + } + + public afterModel(): Promise { + return timeout(SLOW_TRANSITION_WAIT / 3); + } + + public setupController(...rest: Parameters): ReturnType { + super.setupController(...rest); + } +} + +export default instrumentRoutePerformance(SlowLoadingRoute); diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/styles/app.css b/dev-packages/e2e-tests/test-applications/ember-classic/app/styles/app.css new file mode 100644 index 000000000000..f926764e8b3d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/styles/app.css @@ -0,0 +1,197 @@ +:root { + --primary-fg-color: #6c5fc7; + --button-border-color: #413496; + --foreground-color: #2f2936; + --background-color: #f2f1f3; + --content-border-color: #e2dee6; + --button-background-hover-color: #5b4cc0; +} + +html { + height: 100vh; +} + +body { + background: var(--background-color) url('/assets/images/sentry-pattern-transparent.png'); + background-size: 340px; + background-repeat: repeat; + height: 100%; + margin: 0; + font-family: + Rubik, + Avenir Next, + Helvetica Neue, + sans-serif; + font-size: 16px; + line-height: 24px; + color: var(--foreground-color); +} + +.app { + display: flex; + flex-direction: column; + flex-grow: 1; + align-items: center; +} + +.container { + position: relative; + padding-left: 30px; + padding-right: 30px; + padding-top: 5vh; + width: 100%; + max-width: 740px; + flex: 1; +} + +.box { + background-color: #fff; + border: 0; + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.08), + 0 1px 4px rgba(0, 0, 0, 0.1); + border-radius: 4px; + display: flex; + width: 100%; + margin: 0 0 20px; +} + +.sidebar { + padding-top: 20px; + width: 60px; + background: #564f64; + background-image: linear-gradient(-180deg, rgba(52, 44, 62, 0), rgba(52, 44, 62, 0.5)); + box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1); + border-radius: 4px 0 0 4px; + margin-top: -1px; + margin-bottom: -1px; + text-align: center; + + display: flex; + justify-content: center; + padding-top: 20px; + padding-bottom: 20px; +} + +.logo { + width: 24px; + height: 24px; + background-image: url('/assets/images/sentry-logo.svg'); +} + +.nav { + display: flex; + justify-content: center; + padding: 10px; + padding-top: 20px; + padding-bottom: 0px; +} + +.nav a { + padding-left: 10px; + padding-right: 10px; + font-weight: 500; + text-decoration: none; + color: var(--foreground-color); +} + +.nav a.active { + border-bottom: 4px solid #6c5fc7; +} + +section.content { + flex: 1; + padding-bottom: 40px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 600; +} + +h3 { + font-size: 24px; + line-height: 1.2; +} + +div.section { + margin-top: 20px; +} + +.section h4 { + margin-bottom: 10px; +} + +.content-container { + padding-left: 40px; + padding-right: 40px; + padding-top: 20px; +} + +.content-container h3, +.content-container h4 { + margin-top: 0px; +} + +.border-bottom { + border-bottom: 1px solid var(--content-border-color); +} + +button { + border-radius: 3px; + font-weight: 600; + padding: 8px 16px; + transition: all 0.1s; + + border: 1px solid transparent; + border-radius: 3px; + font-weight: 600; + padding: 8px 16px; + + -webkit-appearance: button; + cursor: pointer; +} + +button:hover { + text-decoration: none; +} + +button:focus { + outline-offset: -2px; +} + +button.primary { + color: #fff; + background-color: var(--primary-fg-color); + border-color: var(--button-border-color); + + display: inline-block; + + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: 0 2px 0 rgba(0, 0, 0, 0.08); + + text-transform: none; + overflow: visible; +} + +button.primary:hover { + background-color: var(--button-background-hover-color); + border-color: #204d74; +} + +button.primary:focus { + background: #5b4cc0; + border-color: #3a2f87; + box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.12); + outline: none; +} + +.list-grid { + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 10px; +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/application.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/application.hbs new file mode 100644 index 000000000000..09e6d2fffbb3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/application.hbs @@ -0,0 +1,22 @@ +
+
+
+ +
+
+

Sentry Instrumented Ember Application

+
+ +
+ {{outlet}} +
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/slow-loading-gc-list.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/slow-loading-gc-list.hbs new file mode 100644 index 000000000000..800b0344a1c3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/slow-loading-gc-list.hbs @@ -0,0 +1,10 @@ +
+

{{@title}}

+
+ {{#each @rowItems as |rowItem|}} +
+ {{rowItem}} +
+ {{/each}} +
+
diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/slow-loading-list.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/slow-loading-list.hbs new file mode 100644 index 000000000000..88e1a0db9371 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/slow-loading-list.hbs @@ -0,0 +1,10 @@ +
+

{{this._title}}

+
+ {{#each this.rowItems as |rowItem|}} +
+ {{rowItem.index}} +
+ {{/each}} +
+
diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/test-section.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/test-section.hbs new file mode 100644 index 000000000000..9204d2d7571b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/test-section.hbs @@ -0,0 +1,6 @@ +
+

{{@title}}

+ +
diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/index.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/index.hbs new file mode 100644 index 000000000000..b39ffce5c0e8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/index.hbs @@ -0,0 +1,11 @@ + + + + + + + +{{outlet}} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/replay.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/replay.hbs new file mode 100644 index 000000000000..effb884d8f5f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/replay.hbs @@ -0,0 +1 @@ +

Visiting this page starts Replay!

diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/slow-loading-route.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/slow-loading-route.hbs new file mode 100644 index 000000000000..72981cae67d7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/slow-loading-route.hbs @@ -0,0 +1,11 @@ +

Intentionally Slow Route

+ + +
+ +
+ {{outlet}} +
+
diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/slow-loading-route/index.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/slow-loading-route/index.hbs new file mode 100644 index 000000000000..45de2bc44207 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/slow-loading-route/index.hbs @@ -0,0 +1,5 @@ +
+ + +
diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/tracing.hbs b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/tracing.hbs new file mode 100644 index 000000000000..b2e5087f9f3e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/templates/tracing.hbs @@ -0,0 +1,2 @@ + diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/config/ember-cli-update.json b/dev-packages/e2e-tests/test-applications/ember-classic/config/ember-cli-update.json new file mode 100644 index 000000000000..2ace44409e0b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/config/ember-cli-update.json @@ -0,0 +1,18 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "4.8.0", + "blueprints": [ + { + "name": "app", + "outputRepo": "https://github.com/ember-cli/ember-new-output", + "codemodsSource": "ember-app-codemods-manifest@1", + "isBaseBlueprint": true, + "options": ["--no-welcome", "--ci-provider=github"] + } + ] + } + ] +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/config/environment.js b/dev-packages/e2e-tests/test-applications/ember-classic/config/environment.js new file mode 100644 index 000000000000..54919f9d6c9d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/config/environment.js @@ -0,0 +1,64 @@ +'use strict'; + +module.exports = function (environment) { + const ENV = { + modulePrefix: 'ember-classic', + environment, + rootURL: '/', + locationType: 'history', + EmberENV: { + FEATURES: { + // Here you can enable experimental features on an ember canary build + // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true + }, + }, + + APP: { + // Here you can pass flags/options to your application instance + // when it is created + }, + }; + + ENV['@sentry/ember'] = { + sentry: { + tracesSampleRate: 1, + dsn: process.env.E2E_TEST_DSN, + tracePropagationTargets: ['localhost', 'doesntexist.example'], + browserTracingOptions: { + _experiments: { + // This lead to some flaky tests, as that is sometimes logged + enableLongTask: false, + }, + }, + }, + ignoreEmberOnErrorWarning: true, + minimumRunloopQueueDuration: 0, + minimumComponentRenderDuration: 0, + }; + + if (environment === 'development') { + // ENV.APP.LOG_RESOLVER = true; + // ENV.APP.LOG_ACTIVE_GENERATION = true; + // ENV.APP.LOG_TRANSITIONS = true; + // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; + // ENV.APP.LOG_VIEW_LOOKUPS = true; + } + + if (environment === 'test') { + // Testem prefers this... + ENV.locationType = 'none'; + + // keep test console output quieter + ENV.APP.LOG_ACTIVE_GENERATION = false; + ENV.APP.LOG_VIEW_LOOKUPS = false; + + ENV.APP.rootElement = '#ember-testing'; + ENV.APP.autoboot = false; + } + + if (environment === 'production') { + // here you can enable a production-specific feature + } + + return ENV; +}; diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/config/optional-features.json b/dev-packages/e2e-tests/test-applications/ember-classic/config/optional-features.json new file mode 100644 index 000000000000..b26286e2ecdf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/config/optional-features.json @@ -0,0 +1,6 @@ +{ + "application-template-wrapper": false, + "default-async-observers": true, + "jquery-integration": false, + "template-only-glimmer-components": true +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/config/targets.js b/dev-packages/e2e-tests/test-applications/ember-classic/config/targets.js new file mode 100644 index 000000000000..9f6cc639666e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/config/targets.js @@ -0,0 +1,7 @@ +'use strict'; + +const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; + +module.exports = { + browsers, +}; diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/ember-cli-build.js b/dev-packages/e2e-tests/test-applications/ember-classic/ember-cli-build.js new file mode 100644 index 000000000000..6d9689fa1bc0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/ember-cli-build.js @@ -0,0 +1,24 @@ +'use strict'; + +const EmberApp = require('ember-cli/lib/broccoli/ember-app'); + +module.exports = function (defaults) { + const app = new EmberApp(defaults, { + tests: false, + }); + + // Use `app.import` to add additional libraries to the generated + // output files. + // + // If you need to use different assets in different + // environments, specify an object as the first parameter. That + // object's keys should be the environment name and the values + // should be the asset to use in that environment. + // + // If the library that you are including contains AMD or ES6 + // modules that you would like to import into your application + // please specify an object with the list of modules as keys + // along with the exports of each module as its value. + + return app.toTree(); +}; diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/package.json b/dev-packages/e2e-tests/test-applications/ember-classic/package.json new file mode 100644 index 000000000000..2de3891d90af --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/package.json @@ -0,0 +1,83 @@ +{ + "name": "ember-classic", + "version": "0.0.0", + "private": true, + "description": "Small description for ember-classic goes here", + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "proxy": "ts-node-script start-event-proxy.ts", + "build": "ember build --environment=production", + "start": "ember serve --prod", + "test": "playwright test", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:build-latest": "pnpm install && pnpm add ember-source@latest && npx playwright install && pnpm build", + "test:assert": "playwright test", + "clean": "npx rimraf node_modules,pnpm-lock.yaml,dist" + }, + "devDependencies": { + "@ember/optional-features": "^2.0.0", + "@glimmer/component": "^1.1.2", + "@glimmer/tracking": "^1.1.2", + "@playwright/test": "^1.44.1", + "@ember/string": "^3.1.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@sentry/ember": "latest || *", + "@tsconfig/ember": "^3.0.6", + "@tsconfig/node18": "18.2.4", + "@types/ember": "^4.0.11", + "@types/ember-resolver": "^9.0.0", + "@types/ember__application": "^4.0.11", + "@types/ember__array": "^4.0.10", + "@types/ember__component": "^4.0.22", + "@types/ember__controller": "^4.0.12", + "@types/ember__debug": "^4.0.8", + "@types/ember__destroyable": "^4.0.5", + "@types/ember__engine": "^4.0.11", + "@types/ember__error": "^4.0.6", + "@types/ember__object": "^4.0.12", + "@types/ember__polyfills": "^4.0.6", + "@types/ember__routing": "^4.0.22", + "@types/ember__runloop": "^4.0.10", + "@types/ember__service": "^4.0.9", + "@types/ember__string": "^3.0.15", + "@types/ember__template": "^4.0.7", + "@types/ember__utils": "^4.0.7", + "@types/node": "18.18.0", + "@types/rsvp": "^4.0.9", + "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^2.4.3", + "ember-cli": "~4.8.0", + "ember-cli-app-version": "^5.0.0", + "ember-cli-babel": "^7.26.11", + "ember-cli-dependency-checker": "^3.3.1", + "ember-cli-htmlbars": "^6.1.1", + "ember-cli-inject-live-reload": "^2.1.0", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.2", + "ember-cli-typescript": "^5.3.0", + "ember-fetch": "^8.1.2", + "ember-load-initializers": "^2.1.2", + "ember-page-title": "^7.0.0", + "ember-qunit": "^6.0.0", + "ember-resolver": "^8.0.3", + "ember-source": "~4.8.0", + "loader.js": "^4.7.0", + "ts-node": "10.9.1", + "typescript": "^5.4.5" + }, + "engines": { + "node": "14.* || 16.* || >= 18" + }, + "ember": { + "edition": "octane" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/playwright.config.ts b/dev-packages/e2e-tests/test-applications/ember-classic/playwright.config.ts new file mode 100644 index 000000000000..6c2442587de4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/playwright.config.ts @@ -0,0 +1,73 @@ +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 emberPort = 4020; +const eventProxyPort = 3031; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 30_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, + retries: 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:${emberPort}`, + + /* 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: 'pnpm ts-node-script start-event-proxy.ts', + port: eventProxyPort, + }, + { + command: `pnpm ember serve --path=dist/ --port=${emberPort}`, + port: emberPort, + }, + ], +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/public/assets/images/sentry-logo.svg b/dev-packages/e2e-tests/test-applications/ember-classic/public/assets/images/sentry-logo.svg new file mode 100644 index 000000000000..bac4e57b7790 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/public/assets/images/sentry-logo.svg @@ -0,0 +1 @@ +logos diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/public/assets/images/sentry-pattern-transparent.png b/dev-packages/e2e-tests/test-applications/ember-classic/public/assets/images/sentry-pattern-transparent.png new file mode 100644 index 000000000000..1f7312b5f6af Binary files /dev/null and b/dev-packages/e2e-tests/test-applications/ember-classic/public/assets/images/sentry-pattern-transparent.png differ diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/public/robots.txt b/dev-packages/e2e-tests/test-applications/ember-classic/public/robots.txt new file mode 100644 index 000000000000..f5916452e5ff --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/public/robots.txt @@ -0,0 +1,3 @@ +# http://www.robotstxt.org +User-agent: * +Disallow: diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/ember-classic/start-event-proxy.ts new file mode 100644 index 000000000000..4d7a6a8c0320 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/start-event-proxy.ts @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'ember-classic', +}); diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/ember-classic/tests/errors.test.ts new file mode 100644 index 000000000000..2e836cf8b756 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/tests/errors.test.ts @@ -0,0 +1,66 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('sends an error', async ({ page }) => { + const errorPromise = waitForError('ember-classic', async errorEvent => { + return !errorEvent.type; + }); + + await page.goto(`/`); + + await page.locator('[data-test-button="Throw Generic Javascript Error"]').click(); + + const error = await errorPromise; + + expect(error).toMatchObject({ + exception: { + values: [ + { + type: 'TypeError', + value: 'this.nonExistentFunction is not a function', + mechanism: { + type: 'instrument', + handled: false, + }, + }, + ], + }, + transaction: 'route:index', + }); +}); + +test('assigns the correct transaction value after a navigation', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('ember-classic', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const errorPromise = waitForError('ember-classic', async errorEvent => { + return !errorEvent.type; + }); + + await page.goto(`/tracing`); + await pageloadTxnPromise; + + await page.getByText('Errors').click(); + + const [_, error] = await Promise.all([ + page.locator('[data-test-button="Throw Generic Javascript Error"]').click(), + errorPromise, + ]); + + expect(error).toMatchObject({ + exception: { + values: [ + { + type: 'TypeError', + value: 'this.nonExistentFunction is not a function', + mechanism: { + type: 'instrument', + handled: false, + }, + }, + ], + }, + transaction: 'route:index', + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/ember-classic/tests/performance.test.ts new file mode 100644 index 000000000000..47f3cdb0a6b4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/tests/performance.test.ts @@ -0,0 +1,279 @@ +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('ember-classic', 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.ember', + }, + }, + transaction: 'route:index', + transaction_info: { + source: 'route', + }, + }); +}); + +test('sends a navigation transaction with a parameterized URL', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('ember-classic', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('ember-classic', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await pageloadTxnPromise; + + const [_, navigationTxn] = await Promise.all([page.getByText('Tracing').click(), navigationTxnPromise]); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.ember', + }, + }, + transaction: 'route:tracing', + transaction_info: { + source: 'route', + }, + }); +}); + +test('sends a navigation transaction even if the pageload span is still active', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('ember-classic', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('ember-classic', 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.getByText('Tracing').click(), + pageloadTxnPromise, + navigationTxnPromise, + ]); + + expect(pageloadTxn).toMatchObject({ + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.ember', + }, + }, + transaction: 'route:index', + transaction_info: { + source: 'route', + }, + }); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.ember', + }, + }, + transaction: 'route:tracing', + transaction_info: { + source: 'route', + }, + }); +}); + +test('captures correct spans for navigation', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('ember-classic', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('ember-classic', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/tracing`); + await pageloadTxnPromise; + + const [_, navigationTxn] = await Promise.all([page.getByText('Measure Things!').click(), navigationTxnPromise]); + + const traceId = navigationTxn.contexts?.trace?.trace_id; + const spanId = navigationTxn.contexts?.trace?.span_id; + + expect(traceId).toBeDefined(); + expect(spanId).toBeDefined(); + + const spans = navigationTxn.spans || []; + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.ember', + }, + }, + transaction: 'route:slow-loading-route.index', + transaction_info: { + source: 'route', + }, + }); + + const transitionSpans = spans.filter(span => span.op === 'ui.ember.transition'); + const beforeModelSpans = spans.filter(span => span.op === 'ui.ember.route.before_model'); + const modelSpans = spans.filter(span => span.op === 'ui.ember.route.model'); + const afterModelSpans = spans.filter(span => span.op === 'ui.ember.route.after_model'); + const renderSpans = spans.filter(span => span.op === 'ui.ember.runloop.render'); + + expect(transitionSpans).toHaveLength(1); + + // We have two spans each there - one for `slow-loading-route` and one for `slow-load-route.index` + expect(beforeModelSpans).toHaveLength(2); + expect(modelSpans).toHaveLength(2); + expect(afterModelSpans).toHaveLength(2); + + // There may be many render spans... + expect(renderSpans.length).toBeGreaterThan(1); + + expect(transitionSpans[0]).toEqual({ + data: { + 'sentry.op': 'ui.ember.transition', + 'sentry.origin': 'auto.ui.ember', + }, + description: 'route:tracing -> route:slow-loading-route.index', + op: 'ui.ember.transition', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); + + expect(beforeModelSpans).toEqual([ + { + data: { + 'sentry.op': 'ui.ember.route.before_model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route', + op: 'ui.ember.route.before_model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + { + data: { + 'sentry.op': 'ui.ember.route.before_model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route.index', + op: 'ui.ember.route.before_model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + ]); + + expect(modelSpans).toEqual([ + { + data: { + 'sentry.op': 'ui.ember.route.model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route', + op: 'ui.ember.route.model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + { + data: { + 'sentry.op': 'ui.ember.route.model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route.index', + op: 'ui.ember.route.model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + ]); + + expect(afterModelSpans).toEqual([ + { + data: { + 'sentry.op': 'ui.ember.route.after_model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route', + op: 'ui.ember.route.after_model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + { + data: { + 'sentry.op': 'ui.ember.route.after_model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route.index', + op: 'ui.ember.route.after_model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + ]); + + expect(renderSpans).toContainEqual({ + data: { + 'sentry.op': 'ui.ember.runloop.render', + 'sentry.origin': 'auto.ui.ember', + }, + description: 'runloop', + op: 'ui.ember.runloop.render', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.app.json b/dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.app.json new file mode 100644 index 000000000000..877f7b3990f9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.app.json @@ -0,0 +1,32 @@ +{ + "extends": "@tsconfig/ember/tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Node", + "noEmit": true, + + // The combination of `baseUrl` with `paths` allows Ember's classic package + // layout, which is not resolvable with the Node resolution algorithm, to + // work with TypeScript. + "baseUrl": ".", + "paths": { + "ember-classic/*": [ + "app/*" + ], + "*": [ + "types/*" + ], + } + }, + "include": [ + "app/**/*", + "types/**/*" + ], + "exclude": ["tests/**/*"], + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } + +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.json b/dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.json new file mode 100644 index 000000000000..78f134a16dca --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ], +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.node.json b/dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.node.json new file mode 100644 index 000000000000..65950d5c2bf1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "extends": "@tsconfig/node18/tsconfig.json", + "include": ["playwright.config.*", "start-event-proxy.ts"], + "compilerOptions": { + "composite": true, + "noEmit": true, + "module": "ESNext", + "moduleResolution": "Node", + "types": ["node"] + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/types/ember-classic/index.d.ts b/dev-packages/e2e-tests/test-applications/ember-classic/types/ember-classic/index.d.ts new file mode 100644 index 000000000000..d2f5fc1b01a7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/types/ember-classic/index.d.ts @@ -0,0 +1,11 @@ +import Ember from 'ember'; + +declare global { + // Prevents ESLint from "fixing" this via its auto-fix to turn it into a type + // alias (e.g. after running any Ember CLI generator) + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface Array extends Ember.ArrayPrototypeExtensions {} + // interface Function extends Ember.FunctionPrototypeExtensions {} +} + +export {}; diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/types/global.d.ts b/dev-packages/e2e-tests/test-applications/ember-classic/types/global.d.ts new file mode 100644 index 000000000000..a8988b72222e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/types/global.d.ts @@ -0,0 +1,7 @@ +// Types for compiled templates +declare module 'ember-classic/templates/*' { + import { TemplateFactory } from 'ember-cli-htmlbars'; + + const tmpl: TemplateFactory; + export default tmpl; +} diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/vendor/.gitkeep b/dev-packages/e2e-tests/test-applications/ember-classic/vendor/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/.editorconfig b/dev-packages/e2e-tests/test-applications/ember-embroider/.editorconfig new file mode 100644 index 000000000000..c35a002406b9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.hbs] +insert_final_newline = false + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/.ember-cli b/dev-packages/e2e-tests/test-applications/ember-embroider/.ember-cli new file mode 100644 index 000000000000..4ccb4bf43700 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/.ember-cli @@ -0,0 +1,15 @@ +{ + /** + Ember CLI sends analytics information by default. The data is completely + anonymous, but there are times when you might want to disable this behavior. + + Setting `disableAnalytics` to true will prevent any data from being sent. + */ + "disableAnalytics": false, + + /** + Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript + rather than JavaScript by default, when a TypeScript version of a given blueprint is available. + */ + "isTypeScriptProject": false +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/.gitignore b/dev-packages/e2e-tests/test-applications/ember-embroider/.gitignore new file mode 100644 index 000000000000..f1e859b291c4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/.gitignore @@ -0,0 +1,32 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist/ +/tmp/ + +# dependencies +/bower_components/ +/node_modules/ + +# misc +/.env* +/.pnp* +/.sass-cache +/.eslintcache +/connect.lock +/coverage/ +/libpeerconnection.log +/npm-debug.log* +/testem.log +/yarn-error.log + +# ember-try +/.node_modules.ember-try/ +/bower.json.ember-try +/npm-shrinkwrap.json.ember-try +/package.json.ember-try +/package-lock.json.ember-try +/yarn.lock.ember-try + +# broccoli-debug +/DEBUG/ diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/.npmrc b/dev-packages/e2e-tests/test-applications/ember-embroider/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/.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/ember-embroider/.watchmanconfig b/dev-packages/e2e-tests/test-applications/ember-embroider/.watchmanconfig new file mode 100644 index 000000000000..e7834e3e4f39 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/.watchmanconfig @@ -0,0 +1,3 @@ +{ + "ignore_dirs": ["tmp", "dist"] +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/README.md b/dev-packages/e2e-tests/test-applications/ember-embroider/README.md new file mode 100644 index 000000000000..c2ddb5f5357a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/README.md @@ -0,0 +1,56 @@ +# ember-embroider + +This README outlines the details of collaborating on this Ember application. A short introduction of this app could +easily go here. + +## Prerequisites + +You will need the following things properly installed on your computer. + +- [Git](https://git-scm.com/) +- [Node.js](https://nodejs.org/) (with npm) +- [Ember CLI](https://cli.emberjs.com/release/) +- [Google Chrome](https://google.com/chrome/) + +## Installation + +- `git clone ` this repository +- `cd ember-embroider` +- `npm install` + +## Running / Development + +- `ember serve` +- Visit your app at [http://localhost:4200](http://localhost:4200). +- Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). + +### Code Generators + +Make use of the many generators for code, try `ember help generate` for more details + +### Running Tests + +- `ember test` +- `ember test --server` + +### Linting + +- `npm run lint` +- `npm run lint:fix` + +### Building + +- `ember build` (development) +- `ember build --environment production` (production) + +### Deploying + +Specify what it takes to deploy your app. + +## Further Reading / Useful Links + +- [ember.js](https://emberjs.com/) +- [ember-cli](https://cli.emberjs.com/release/) +- Development Browser Extensions + - [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) + - [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/app.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/app/app.ts new file mode 100644 index 000000000000..7241d14be133 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/app.ts @@ -0,0 +1,18 @@ +import Application from '@ember/application'; +import * as Sentry from '@sentry/ember'; +import config from 'ember-embroider/config/environment'; +import loadInitializers from 'ember-load-initializers'; +import Resolver from 'ember-resolver'; + +Sentry.init({ + replaysSessionSampleRate: 1, + replaysOnErrorSampleRate: 1, + tunnel: `http://localhost:3031/`, // proxy server +}); +export default class App extends Application { + modulePrefix = config.modulePrefix; + podModulePrefix = config.podModulePrefix; + Resolver = Resolver; +} + +loadInitializers(App, config.modulePrefix); diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/components/error-button.hbs b/dev-packages/e2e-tests/test-applications/ember-embroider/app/components/error-button.hbs new file mode 100644 index 000000000000..754607dd75e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/components/error-button.hbs @@ -0,0 +1,6 @@ + diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/components/error-button.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/app/components/error-button.ts new file mode 100644 index 000000000000..ed3fea334397 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/components/error-button.ts @@ -0,0 +1,10 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; + +export default class ErrorButtonComponent extends Component { + @action + throwGenericJavascriptError() { + // @ts-expect-error This is fine + this.nonExistentFunction(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/config/environment.d.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/app/config/environment.d.ts new file mode 100644 index 000000000000..8a8a687909e4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/config/environment.d.ts @@ -0,0 +1,17 @@ +/** + * Type declarations for + * import config from './config/environment' + * + * For now these need to be managed by the developer + * since different ember addons can materialize new entries. + */ +declare const config: { + environment: string; + modulePrefix: string; + podModulePrefix: string; + locationType: 'history' | 'hash' | 'none' | 'auto'; + rootURL: string; + APP: Record; +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/index.html b/dev-packages/e2e-tests/test-applications/ember-embroider/app/index.html new file mode 100644 index 000000000000..f22e112de254 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/index.html @@ -0,0 +1,24 @@ + + + + + EmberClassic + + + + {{content-for "head"}} + + + + + {{content-for "head-footer"}} + + + {{content-for "body"}} + + + + + {{content-for "body-footer"}} + + diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/router.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/app/router.ts new file mode 100644 index 000000000000..e13dec6d82c5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/router.ts @@ -0,0 +1,18 @@ +import EmberRouter from '@ember/routing/router'; + +import config from './config/environment'; + +export default class Router extends EmberRouter { + public location = config.locationType; + public rootURL = config.rootURL; +} + +// This is a false positive of the eslint rule +// eslint-disable-next-line array-callback-return +Router.map(function () { + this.route('tracing'); + this.route('replay'); + this.route('slow-loading-route', function () { + this.route('index', { path: '/' }); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/index.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/index.ts new file mode 100644 index 000000000000..accaec92e0d8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/index.ts @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class IndexRoute extends Route {} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/slow-loading-route.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/slow-loading-route.ts new file mode 100644 index 000000000000..738a2528ec21 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/slow-loading-route.ts @@ -0,0 +1,30 @@ +import Route from '@ember/routing/route'; +import { instrumentRoutePerformance } from '@sentry/ember'; + +class SlowLoadingRouteRoute extends Route { + beforeModel() { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 250); + }); + } + + model() { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 250); + }); + } + + afterModel() { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 250); + }); + } +} + +export default instrumentRoutePerformance(SlowLoadingRouteRoute); diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/slow-loading-route/index.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/slow-loading-route/index.ts new file mode 100644 index 000000000000..626c3e403d08 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/slow-loading-route/index.ts @@ -0,0 +1,30 @@ +import Route from '@ember/routing/route'; +import { instrumentRoutePerformance } from '@sentry/ember'; + +class SlowLoadingRouteIndexRoute extends Route { + beforeModel() { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 250); + }); + } + + model() { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 250); + }); + } + + afterModel() { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 250); + }); + } +} + +export default instrumentRoutePerformance(SlowLoadingRouteIndexRoute); diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/tracing.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/tracing.ts new file mode 100644 index 000000000000..f6cc69a0ae39 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/tracing.ts @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class TracingRoute extends Route {} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/styles/app.css b/dev-packages/e2e-tests/test-applications/ember-embroider/app/styles/app.css new file mode 100644 index 000000000000..f926764e8b3d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/styles/app.css @@ -0,0 +1,197 @@ +:root { + --primary-fg-color: #6c5fc7; + --button-border-color: #413496; + --foreground-color: #2f2936; + --background-color: #f2f1f3; + --content-border-color: #e2dee6; + --button-background-hover-color: #5b4cc0; +} + +html { + height: 100vh; +} + +body { + background: var(--background-color) url('/assets/images/sentry-pattern-transparent.png'); + background-size: 340px; + background-repeat: repeat; + height: 100%; + margin: 0; + font-family: + Rubik, + Avenir Next, + Helvetica Neue, + sans-serif; + font-size: 16px; + line-height: 24px; + color: var(--foreground-color); +} + +.app { + display: flex; + flex-direction: column; + flex-grow: 1; + align-items: center; +} + +.container { + position: relative; + padding-left: 30px; + padding-right: 30px; + padding-top: 5vh; + width: 100%; + max-width: 740px; + flex: 1; +} + +.box { + background-color: #fff; + border: 0; + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.08), + 0 1px 4px rgba(0, 0, 0, 0.1); + border-radius: 4px; + display: flex; + width: 100%; + margin: 0 0 20px; +} + +.sidebar { + padding-top: 20px; + width: 60px; + background: #564f64; + background-image: linear-gradient(-180deg, rgba(52, 44, 62, 0), rgba(52, 44, 62, 0.5)); + box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1); + border-radius: 4px 0 0 4px; + margin-top: -1px; + margin-bottom: -1px; + text-align: center; + + display: flex; + justify-content: center; + padding-top: 20px; + padding-bottom: 20px; +} + +.logo { + width: 24px; + height: 24px; + background-image: url('/assets/images/sentry-logo.svg'); +} + +.nav { + display: flex; + justify-content: center; + padding: 10px; + padding-top: 20px; + padding-bottom: 0px; +} + +.nav a { + padding-left: 10px; + padding-right: 10px; + font-weight: 500; + text-decoration: none; + color: var(--foreground-color); +} + +.nav a.active { + border-bottom: 4px solid #6c5fc7; +} + +section.content { + flex: 1; + padding-bottom: 40px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 600; +} + +h3 { + font-size: 24px; + line-height: 1.2; +} + +div.section { + margin-top: 20px; +} + +.section h4 { + margin-bottom: 10px; +} + +.content-container { + padding-left: 40px; + padding-right: 40px; + padding-top: 20px; +} + +.content-container h3, +.content-container h4 { + margin-top: 0px; +} + +.border-bottom { + border-bottom: 1px solid var(--content-border-color); +} + +button { + border-radius: 3px; + font-weight: 600; + padding: 8px 16px; + transition: all 0.1s; + + border: 1px solid transparent; + border-radius: 3px; + font-weight: 600; + padding: 8px 16px; + + -webkit-appearance: button; + cursor: pointer; +} + +button:hover { + text-decoration: none; +} + +button:focus { + outline-offset: -2px; +} + +button.primary { + color: #fff; + background-color: var(--primary-fg-color); + border-color: var(--button-border-color); + + display: inline-block; + + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: 0 2px 0 rgba(0, 0, 0, 0.08); + + text-transform: none; + overflow: visible; +} + +button.primary:hover { + background-color: var(--button-background-hover-color); + border-color: #204d74; +} + +button.primary:focus { + background: #5b4cc0; + border-color: #3a2f87; + box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.12); + outline: none; +} + +.list-grid { + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 10px; +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/application.hbs b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/application.hbs new file mode 100644 index 000000000000..4e41d992dc3c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/application.hbs @@ -0,0 +1,21 @@ +
+
+
+ +
+
+

Sentry Instrumented Ember Application

+
+ +
+ {{outlet}} +
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/index.hbs b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/index.hbs new file mode 100644 index 000000000000..2d439e9f39e7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/index.hbs @@ -0,0 +1,4 @@ +{{page-title "Index"}} +{{outlet}} + + diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route.hbs b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route.hbs new file mode 100644 index 000000000000..c24cd68950a9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route/index.hbs b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route/index.hbs new file mode 100644 index 000000000000..cfccec8c94c8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route/index.hbs @@ -0,0 +1,5 @@ +{{page-title "Index"}} + +

+ This is a slow loading route! +

diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route/loading.hbs b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route/loading.hbs new file mode 100644 index 000000000000..24638b56a00e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route/loading.hbs @@ -0,0 +1 @@ +Loading slow route... diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/tracing.hbs b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/tracing.hbs new file mode 100644 index 000000000000..ee694dc85d89 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/tracing.hbs @@ -0,0 +1,7 @@ +{{page-title "Tracing"}} + +

+ This is a fast loading route. +

+ +Measure Things! diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/config/environment.js b/dev-packages/e2e-tests/test-applications/ember-embroider/config/environment.js new file mode 100644 index 000000000000..37edb5c20697 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/config/environment.js @@ -0,0 +1,64 @@ +'use strict'; + +module.exports = function (environment) { + const ENV = { + modulePrefix: 'ember-embroider', + environment, + rootURL: '/', + locationType: 'history', + EmberENV: { + FEATURES: { + // Here you can enable experimental features on an ember canary build + // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true + }, + }, + + APP: { + // Here you can pass flags/options to your application instance + // when it is created + }, + }; + + ENV['@sentry/ember'] = { + sentry: { + tracesSampleRate: 1, + dsn: process.env.E2E_TEST_DSN, + tracePropagationTargets: ['localhost', 'doesntexist.example'], + browserTracingOptions: { + _experiments: { + // This lead to some flaky tests, as that is sometimes logged + enableLongTask: false, + }, + }, + }, + ignoreEmberOnErrorWarning: true, + minimumRunloopQueueDuration: 0, + minimumComponentRenderDuration: 0, + }; + + if (environment === 'development') { + // ENV.APP.LOG_RESOLVER = true; + // ENV.APP.LOG_ACTIVE_GENERATION = true; + // ENV.APP.LOG_TRANSITIONS = true; + // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; + // ENV.APP.LOG_VIEW_LOOKUPS = true; + } + + if (environment === 'test') { + // Testem prefers this... + ENV.locationType = 'none'; + + // keep test console output quieter + ENV.APP.LOG_ACTIVE_GENERATION = false; + ENV.APP.LOG_VIEW_LOOKUPS = false; + + ENV.APP.rootElement = '#ember-testing'; + ENV.APP.autoboot = false; + } + + if (environment === 'production') { + // here you can enable a production-specific feature + } + + return ENV; +}; diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/config/optional-features.json b/dev-packages/e2e-tests/test-applications/ember-embroider/config/optional-features.json new file mode 100644 index 000000000000..5329dd9913bb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/config/optional-features.json @@ -0,0 +1,7 @@ +{ + "application-template-wrapper": false, + "default-async-observers": true, + "jquery-integration": false, + "template-only-glimmer-components": true, + "no-implicit-route-model": true +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/config/targets.js b/dev-packages/e2e-tests/test-applications/ember-embroider/config/targets.js new file mode 100644 index 000000000000..9f6cc639666e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/config/targets.js @@ -0,0 +1,7 @@ +'use strict'; + +const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; + +module.exports = { + browsers, +}; diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/ember-cli-build.js b/dev-packages/e2e-tests/test-applications/ember-embroider/ember-cli-build.js new file mode 100644 index 000000000000..0338e9ce1608 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/ember-cli-build.js @@ -0,0 +1,19 @@ +'use strict'; + +const EmberApp = require('ember-cli/lib/broccoli/ember-app'); + +module.exports = function (defaults) { + const app = new EmberApp(defaults, { + tests: false, + hinting: false, + }); + + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack, { + staticAddonTrees: true, + staticHelpers: true, + staticModifiers: true, + staticComponents: true, + staticEmberSource: true, + }); +}; diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json new file mode 100644 index 000000000000..1512a07d122c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json @@ -0,0 +1,72 @@ +{ + "name": "ember-embroider", + "version": "0.0.0", + "private": true, + "description": "Small description for ember-embroider goes here", + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "proxy": "ts-node-script start-event-proxy.ts", + "build": "ember build --environment=production", + "start": "ember serve --prod", + "test": "playwright test", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:build-latest": "pnpm install && pnpm add ember-source@latest && npx playwright install && pnpm build", + "test:assert": "playwright test", + "clean": "npx rimraf node_modules,pnpm-lock.yaml,dist" + }, + "devDependencies": { + "@babel/core": "^7.24.4", + "@babel/plugin-proposal-decorators": "^7.24.1", + "@ember/optional-features": "^2.1.0", + "@ember/string": "^3.1.1", + "@ember/test-helpers": "^3.3.0", + "@embroider/compat": "^3.4.8", + "@embroider/core": "^3.4.8", + "@embroider/webpack": "^4.0.0", + "@glimmer/component": "^1.1.2", + "@glimmer/tracking": "^1.1.2", + "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^2.7.2", + "ember-cli": "~5.8.0", + "ember-cli-app-version": "^6.0.1", + "ember-cli-babel": "^8.2.0", + "ember-cli-clean-css": "^3.0.0", + "ember-cli-dependency-checker": "^3.3.2", + "ember-cli-htmlbars": "^6.3.0", + "ember-cli-inject-live-reload": "^2.1.0", + "ember-cli-typescript": "5.3.0", + "ember-fetch": "^8.1.2", + "ember-load-initializers": "^2.1.2", + "ember-modifier": "^4.1.0", + "ember-page-title": "^8.2.3", + "ember-resolver": "^11.0.1", + "ember-source": "~5.8.0", + "loader.js": "^4.7.0", + "tracked-built-ins": "^3.3.0", + "webpack": "^5.91.0", + "@playwright/test": "^1.44.1", + "@sentry/ember": "latest || *", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@tsconfig/ember": "^3.0.6", + "@types/node": "18.18.0", + "@tsconfig/node18": "18.2.4", + "@types/rsvp": "^4.0.9", + "ts-node": "10.9.1", + "typescript": "^5.4.5" + }, + "engines": { + "node": ">= 18" + }, + "ember": { + "edition": "octane" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/playwright.config.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/playwright.config.ts new file mode 100644 index 000000000000..6c2442587de4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/playwright.config.ts @@ -0,0 +1,73 @@ +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 emberPort = 4020; +const eventProxyPort = 3031; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 30_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, + retries: 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:${emberPort}`, + + /* 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: 'pnpm ts-node-script start-event-proxy.ts', + port: eventProxyPort, + }, + { + command: `pnpm ember serve --path=dist/ --port=${emberPort}`, + port: emberPort, + }, + ], +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/public/assets/images/sentry-logo.svg b/dev-packages/e2e-tests/test-applications/ember-embroider/public/assets/images/sentry-logo.svg new file mode 100644 index 000000000000..bac4e57b7790 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/public/assets/images/sentry-logo.svg @@ -0,0 +1 @@ +logos diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/public/assets/images/sentry-pattern-transparent.png b/dev-packages/e2e-tests/test-applications/ember-embroider/public/assets/images/sentry-pattern-transparent.png new file mode 100644 index 000000000000..1f7312b5f6af Binary files /dev/null and b/dev-packages/e2e-tests/test-applications/ember-embroider/public/assets/images/sentry-pattern-transparent.png differ diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/public/robots.txt b/dev-packages/e2e-tests/test-applications/ember-embroider/public/robots.txt new file mode 100644 index 000000000000..f5916452e5ff --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/public/robots.txt @@ -0,0 +1,3 @@ +# http://www.robotstxt.org +User-agent: * +Disallow: diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/start-event-proxy.ts new file mode 100644 index 000000000000..7d545da45ca2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/start-event-proxy.ts @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'ember-embroider', +}); diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/tests/errors.test.ts new file mode 100644 index 000000000000..9171611e42cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/tests/errors.test.ts @@ -0,0 +1,66 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('sends an error', async ({ page }) => { + const errorPromise = waitForError('ember-embroider', async errorEvent => { + return !errorEvent.type; + }); + + await page.goto(`/`); + + await page.locator('[data-test-button="Throw Generic Javascript Error"]').click(); + + const error = await errorPromise; + + expect(error).toMatchObject({ + exception: { + values: [ + { + type: 'TypeError', + value: 'this.nonExistentFunction is not a function', + mechanism: { + type: 'instrument', + handled: false, + }, + }, + ], + }, + transaction: 'route:index', + }); +}); + +test('assigns the correct transaction value after a navigation', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('ember-embroider', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const errorPromise = waitForError('ember-embroider', async errorEvent => { + return !errorEvent.type; + }); + + await page.goto(`/tracing`); + await pageloadTxnPromise; + + await page.getByText('Errors').click(); + + const [_, error] = await Promise.all([ + page.locator('[data-test-button="Throw Generic Javascript Error"]').click(), + errorPromise, + ]); + + expect(error).toMatchObject({ + exception: { + values: [ + { + type: 'TypeError', + value: 'this.nonExistentFunction is not a function', + mechanism: { + type: 'instrument', + handled: false, + }, + }, + ], + }, + transaction: 'route:index', + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/tests/performance.test.ts new file mode 100644 index 000000000000..a26bec292650 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/tests/performance.test.ts @@ -0,0 +1,279 @@ +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('ember-embroider', 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.ember', + }, + }, + transaction: 'route:index', + transaction_info: { + source: 'route', + }, + }); +}); + +test('sends a navigation transaction with a parameterized URL', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('ember-embroider', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('ember-embroider', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await pageloadTxnPromise; + + const [_, navigationTxn] = await Promise.all([page.getByText('Tracing').click(), navigationTxnPromise]); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.ember', + }, + }, + transaction: 'route:tracing', + transaction_info: { + source: 'route', + }, + }); +}); + +test('sends a navigation transaction even if the pageload span is still active', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('ember-embroider', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('ember-embroider', 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.getByText('Tracing').click(), + pageloadTxnPromise, + navigationTxnPromise, + ]); + + expect(pageloadTxn).toMatchObject({ + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.ember', + }, + }, + transaction: 'route:index', + transaction_info: { + source: 'route', + }, + }); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.ember', + }, + }, + transaction: 'route:tracing', + transaction_info: { + source: 'route', + }, + }); +}); + +test('captures correct spans for navigation', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('ember-embroider', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('ember-embroider', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/tracing`); + await pageloadTxnPromise; + + const [_, navigationTxn] = await Promise.all([page.getByText('Measure Things!').click(), navigationTxnPromise]); + + const traceId = navigationTxn.contexts?.trace?.trace_id; + const spanId = navigationTxn.contexts?.trace?.span_id; + + expect(traceId).toBeDefined(); + expect(spanId).toBeDefined(); + + const spans = navigationTxn.spans || []; + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.ember', + }, + }, + transaction: 'route:slow-loading-route.index', + transaction_info: { + source: 'route', + }, + }); + + const transitionSpans = spans.filter(span => span.op === 'ui.ember.transition'); + const beforeModelSpans = spans.filter(span => span.op === 'ui.ember.route.before_model'); + const modelSpans = spans.filter(span => span.op === 'ui.ember.route.model'); + const afterModelSpans = spans.filter(span => span.op === 'ui.ember.route.after_model'); + const renderSpans = spans.filter(span => span.op === 'ui.ember.runloop.render'); + + expect(transitionSpans).toHaveLength(1); + + // We have two spans each there - one for `slow-loading-route` and one for `slow-load-route.index` + expect(beforeModelSpans).toHaveLength(2); + expect(modelSpans).toHaveLength(2); + expect(afterModelSpans).toHaveLength(2); + + // There may be many render spans... + expect(renderSpans.length).toBeGreaterThan(1); + + expect(transitionSpans[0]).toEqual({ + data: { + 'sentry.op': 'ui.ember.transition', + 'sentry.origin': 'auto.ui.ember', + }, + description: 'route:tracing -> route:slow-loading-route.index', + op: 'ui.ember.transition', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); + + expect(beforeModelSpans).toEqual([ + { + data: { + 'sentry.op': 'ui.ember.route.before_model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route', + op: 'ui.ember.route.before_model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + { + data: { + 'sentry.op': 'ui.ember.route.before_model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route.index', + op: 'ui.ember.route.before_model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + ]); + + expect(modelSpans).toEqual([ + { + data: { + 'sentry.op': 'ui.ember.route.model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route', + op: 'ui.ember.route.model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + { + data: { + 'sentry.op': 'ui.ember.route.model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route.index', + op: 'ui.ember.route.model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + ]); + + expect(afterModelSpans).toEqual([ + { + data: { + 'sentry.op': 'ui.ember.route.after_model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route', + op: 'ui.ember.route.after_model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + { + data: { + 'sentry.op': 'ui.ember.route.after_model', + 'sentry.origin': 'auto.ui.ember', + 'sentry.source': 'custom', + }, + description: 'slow-loading-route.index', + op: 'ui.ember.route.after_model', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }, + ]); + + expect(renderSpans).toContainEqual({ + data: { + 'sentry.op': 'ui.ember.runloop.render', + 'sentry.origin': 'auto.ui.ember', + }, + description: 'runloop', + op: 'ui.ember.runloop.render', + origin: 'auto.ui.ember', + parent_span_id: spanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.app.json b/dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.app.json new file mode 100644 index 000000000000..919403ddcda8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.app.json @@ -0,0 +1,32 @@ +{ + "extends": "@tsconfig/ember/tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Node", + "noEmit": true, + + // The combination of `baseUrl` with `paths` allows Ember's classic package + // layout, which is not resolvable with the Node resolution algorithm, to + // work with TypeScript. + "baseUrl": ".", + "paths": { + "ember-embroider/*": [ + "app/*" + ], + "*": [ + "types/*" + ], + } + }, + "include": [ + "app/**/*", + "types/**/*" + ], + "exclude": ["tests/**/*"], + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } + +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.json b/dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.json new file mode 100644 index 000000000000..78f134a16dca --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ], +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.node.json b/dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.node.json new file mode 100644 index 000000000000..65950d5c2bf1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "extends": "@tsconfig/node18/tsconfig.json", + "include": ["playwright.config.*", "start-event-proxy.ts"], + "compilerOptions": { + "composite": true, + "noEmit": true, + "module": "ESNext", + "moduleResolution": "Node", + "types": ["node"] + } +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/types/ember-embroider/index.d.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/types/ember-embroider/index.d.ts new file mode 100644 index 000000000000..d2f5fc1b01a7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/types/ember-embroider/index.d.ts @@ -0,0 +1,11 @@ +import Ember from 'ember'; + +declare global { + // Prevents ESLint from "fixing" this via its auto-fix to turn it into a type + // alias (e.g. after running any Ember CLI generator) + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface Array extends Ember.ArrayPrototypeExtensions {} + // interface Function extends Ember.FunctionPrototypeExtensions {} +} + +export {}; diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/types/global.d.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/types/global.d.ts new file mode 100644 index 000000000000..55d63f8da3a2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/types/global.d.ts @@ -0,0 +1,7 @@ +// Types for compiled templates +declare module 'ember-embroider/templates/*' { + import { TemplateFactory } from 'ember-cli-htmlbars'; + + const tmpl: TemplateFactory; + export default tmpl; +} diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/vendor/.gitkeep b/dev-packages/e2e-tests/test-applications/ember-embroider/vendor/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1