From fb0bc072265ac771df048bcbf954b91b7037f0be Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 29 Apr 2024 10:40:07 +0200 Subject: [PATCH] WIP --- .github/workflows/build.yml | 2 + .github/workflows/canary.yml | 47 +-- .../ember-classic/.editorconfig | 19 ++ .../ember-classic/.ember-cli | 15 + .../ember-classic/.gitignore | 32 ++ .../test-applications/ember-classic/.npmrc | 2 + .../ember-classic/.watchmanconfig | 3 + .../test-applications/ember-classic/README.md | 56 ++++ .../ember-classic/app/app.ts | 20 ++ .../ember-classic/app/components/link.hbs | 3 + .../ember-classic/app/components/link.ts | 33 +++ .../app/components/slow-loading-gc-list.ts | 4 + .../app/components/slow-loading-list.ts | 25 ++ .../app/components/test-section.ts | 6 + .../ember-classic/app/config/environment.d.ts | 17 ++ .../ember-classic/app/controllers/index.ts | 64 ++++ .../app/controllers/slow-loading-route.ts | 13 + .../controllers/slow-loading-route/index.ts | 5 + .../ember-classic/app/controllers/tracing.ts | 13 + .../ember-classic/app/helpers/utils.ts | 3 + .../ember-classic/app/index.html | 24 ++ .../app/initializers/deprecation.ts | 13 + .../ember-classic/app/router.ts | 18 ++ .../ember-classic/app/routes/replay.ts | 13 + .../app/routes/slow-loading-route.ts | 26 ++ .../app/routes/slow-loading-route/index.ts | 26 ++ .../ember-classic/app/styles/app.css | 197 +++++++++++++ .../app/templates/application.hbs | 22 ++ .../components/slow-loading-gc-list.hbs | 10 + .../components/slow-loading-list.hbs | 10 + .../app/templates/components/test-section.hbs | 6 + .../ember-classic/app/templates/index.hbs | 11 + .../ember-classic/app/templates/replay.hbs | 1 + .../app/templates/slow-loading-route.hbs | 11 + .../templates/slow-loading-route/index.hbs | 5 + .../ember-classic/app/templates/tracing.hbs | 2 + .../config/ember-cli-update.json | 21 ++ .../ember-classic/config/environment.js | 64 ++++ .../config/optional-features.json | 6 + .../ember-classic/config/targets.js | 11 + .../ember-classic/ember-cli-build.js | 24 ++ .../ember-classic/package.json | 85 ++++++ .../ember-classic/playwright.config.ts | 73 +++++ .../public/assets/images/sentry-logo.svg | 1 + .../images/sentry-pattern-transparent.png | Bin 0 -> 28158 bytes .../ember-classic/public/robots.txt | 3 + .../ember-classic/start-event-proxy.ts | 6 + .../ember-classic/tests/errors.test.ts | 66 +++++ .../ember-classic/tests/performance.test.ts | 279 ++++++++++++++++++ .../ember-classic/tsconfig.app.json | 32 ++ .../ember-classic/tsconfig.json | 11 + .../ember-classic/tsconfig.node.json | 11 + .../types/ember-classic/index.d.ts | 11 + .../ember-data/types/registries/model.d.ts | 6 + .../ember-classic/types/global.d.ts | 7 + .../ember-classic/vendor/.gitkeep | 0 .../ember-embroider/.editorconfig | 19 ++ .../ember-embroider/.ember-cli | 15 + .../ember-embroider/.gitignore | 32 ++ .../test-applications/ember-embroider/.npmrc | 2 + .../ember-embroider/.watchmanconfig | 3 + .../ember-embroider/README.md | 56 ++++ .../ember-embroider/app/app.ts | 18 ++ .../app/components/error-button.hbs | 6 + .../app/components/error-button.ts | 10 + .../app/config/environment.d.ts | 17 ++ .../ember-embroider/app/index.html | 24 ++ .../ember-embroider/app/router.ts | 18 ++ .../ember-embroider/app/routes/index.ts | 3 + .../app/routes/slow-loading-route.ts | 30 ++ .../app/routes/slow-loading-route/index.ts | 30 ++ .../ember-embroider/app/routes/tracing.ts | 3 + .../ember-embroider/app/styles/app.css | 197 +++++++++++++ .../app/templates/application.hbs | 21 ++ .../ember-embroider/app/templates/index.hbs | 4 + .../app/templates/slow-loading-route.hbs | 1 + .../templates/slow-loading-route/index.hbs | 5 + .../templates/slow-loading-route/loading.hbs | 1 + .../ember-embroider/app/templates/tracing.hbs | 7 + .../ember-embroider/config/environment.js | 64 ++++ .../config/optional-features.json | 7 + .../ember-embroider/config/targets.js | 11 + .../ember-embroider/ember-cli-build.js | 24 ++ .../ember-embroider/package.json | 105 +++++++ .../ember-embroider/playwright.config.ts | 73 +++++ .../public/assets/images/sentry-logo.svg | 1 + .../images/sentry-pattern-transparent.png | Bin 0 -> 28158 bytes .../ember-embroider/public/robots.txt | 3 + .../ember-embroider/start-event-proxy.ts | 6 + .../ember-embroider/tests/errors.test.ts | 66 +++++ .../ember-embroider/tests/performance.test.ts | 279 ++++++++++++++++++ .../ember-embroider/tsconfig.app.json | 32 ++ .../ember-embroider/tsconfig.json | 11 + .../ember-embroider/tsconfig.node.json | 11 + .../types/ember-embroider/index.d.ts | 11 + .../ember-embroider/types/global.d.ts | 7 + .../ember-embroider/vendor/.gitkeep | 0 packages/ember/addon/index.ts | 4 +- .../sentry-performance.ts | 25 +- 99 files changed, 2712 insertions(+), 44 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/.editorconfig create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/.ember-cli create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/.watchmanconfig create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/README.md create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/app.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/components/link.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/components/link.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/components/slow-loading-gc-list.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/components/slow-loading-list.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/components/test-section.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/config/environment.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/index.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/slow-loading-route.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/slow-loading-route/index.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/tracing.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/helpers/utils.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/index.html create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/initializers/deprecation.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/router.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/routes/replay.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/routes/slow-loading-route.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/routes/slow-loading-route/index.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/styles/app.css create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/templates/application.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/slow-loading-gc-list.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/slow-loading-list.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/templates/components/test-section.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/templates/index.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/templates/replay.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/templates/slow-loading-route.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/templates/slow-loading-route/index.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/app/templates/tracing.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/config/ember-cli-update.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/config/environment.js create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/config/optional-features.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/config/targets.js create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/ember-cli-build.js create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/package.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/playwright.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/public/assets/images/sentry-logo.svg create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/public/assets/images/sentry-pattern-transparent.png create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/public/robots.txt create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/start-event-proxy.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/tests/performance.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.app.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/tsconfig.node.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/types/ember-classic/index.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/types/ember-data/types/registries/model.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/types/global.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/vendor/.gitkeep create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/.editorconfig create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/.ember-cli create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/.watchmanconfig create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/README.md create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/app.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/components/error-button.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/components/error-button.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/config/environment.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/index.html create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/router.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/index.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/slow-loading-route.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/slow-loading-route/index.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/routes/tracing.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/styles/app.css create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/application.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/index.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route/index.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/slow-loading-route/loading.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/app/templates/tracing.hbs create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/config/environment.js create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/config/optional-features.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/config/targets.js create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/ember-cli-build.js create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/package.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/playwright.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/public/assets/images/sentry-logo.svg create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/public/assets/images/sentry-pattern-transparent.png create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/public/robots.txt create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/start-event-proxy.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/tests/performance.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.app.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/tsconfig.node.json create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/types/ember-embroider/index.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/types/global.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/vendor/.gitkeep diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3e000957653d..c2ac12bdecb6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1000,6 +1000,8 @@ jobs: 'create-remix-app-v2', 'create-remix-app-express-vite-dev', 'debug-id-sourcemaps', + 'ember-classic', + 'ember-embroider', # 'esm-loader-node-express-app', # This is currently broken for upstream reasons. See https://github.com/getsentry/sentry-javascript/pull/11338#issuecomment-2025450675 'nextjs-app-dir', 'nextjs-14', diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 2771e84ece6b..1df929e75bff 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -89,6 +89,12 @@ jobs: - test-application: 'standard-frontend-react' build-command: 'test:build-canary' label: 'standard-frontend-react (canary)' + - test-application: 'ember-classic' + build-command: 'test:build-latest' + label: 'ember-classic (latest)' + - test-application: 'ember-embroider' + build-command: 'test:build-latest' + label: 'ember-embroider (latest)' steps: - name: Check out current commit @@ -145,44 +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] - 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/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..9c7d1590d14c --- /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..bd28b635e1a1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/app/controllers/index.ts @@ -0,0 +1,64 @@ +import Controller from '@ember/controller'; +import EmberError from '@ember/error'; +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 EmberError('Whoops, looks like you have an EmberError'); + } + + @action + public createCaughtEmberError(): void { + try { + throw new EmberError('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..ccb918d19c7b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/config/ember-cli-update.json @@ -0,0 +1,21 @@ +{ + "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..1e48e0599f9a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/config/targets.js @@ -0,0 +1,11 @@ +'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..45d36bf2f3c0 --- /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, { + // Add options here + }); + + // 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..0e667bcb826b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/package.json @@ -0,0 +1,85 @@ +{ + "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.41.1", + "@sentry/ember": "latest || *", + "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server", + "@tsconfig/ember": "^3.0.6", + "@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", + "@tsconfig/node18": "18.2.4", + "@types/rsvp": "^4.0.9", + "babel-eslint": "^10.1.0", + "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-data": "~4.7.3", + "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", + "webpack": "^5.74.0" + }, + "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 0000000000000000000000000000000000000000..1f7312b5f6af002e52a3f46c175df7d9efcce9ad GIT binary patch literal 28158 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 { + 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..edb31fc8ade0 --- /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/event-proxy-server'; + +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/ember-data/types/registries/model.d.ts b/dev-packages/e2e-tests/test-applications/ember-classic/types/ember-data/types/registries/model.d.ts new file mode 100644 index 000000000000..e7a68fcd05a2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/types/ember-data/types/registries/model.d.ts @@ -0,0 +1,6 @@ +/** + * Catch-all for ember-data. + */ +export default interface ModelRegistry { + [key: string]: any; +} 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..35a12aa4feaa --- /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..1e48e0599f9a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/config/targets.js @@ -0,0 +1,11 @@ +'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..b8b3e37fe118 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/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, { + // Add options here + }); + + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack, { + staticAddonTestSupportTrees: true, + staticAddonTrees: true, + staticHelpers: true, + staticModifiers: true, + staticComponents: true, + staticEmberSource: true, + skipBabel: [ + { + package: 'qunit', + }, + ], + }); +}; 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..94303c153ade --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json @@ -0,0 +1,105 @@ +{ + "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/eslint-parser": "^7.24.1", + "@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.7", + "@embroider/core": "^3.4.7", + "@embroider/webpack": "^3.2.3", + "@glimmer/component": "^1.1.2", + "@glimmer/tracking": "^1.1.2", + "broccoli-asset-rev": "^3.0.0", + "concurrently": "^8.2.2", + "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-qunit": "^8.0.2", + "ember-resolver": "^11.0.1", + "ember-source": "~5.8.0", + "ember-template-lint": "^5.13.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-ember": "^11.12.0", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-qunit": "^8.1.1", + "loader.js": "^4.7.0", + "prettier": "^3.2.5", + "qunit": "^2.20.1", + "qunit-dom": "^2.0.0", + "stylelint": "^15.11.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-prettier": "^4.1.0", + "tracked-built-ins": "^3.3.0", + "webpack": "^5.91.0", + "@playwright/test": "^1.41.1", + "@sentry/ember": "latest || *", + "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server", + "@tsconfig/ember": "^3.0.6", + "@types/ember": "^4.0.11", + "@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", + "@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 0000000000000000000000000000000000000000..1f7312b5f6af002e52a3f46c175df7d9efcce9ad GIT binary patch literal 28158 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
;{OfCCj$QVtO<#C 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 { + 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..2c66cb668f4f --- /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/event-proxy-server'; + +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 diff --git a/packages/ember/addon/index.ts b/packages/ember/addon/index.ts index f17e4749ceb7..33ba495e5acc 100644 --- a/packages/ember/addon/index.ts +++ b/packages/ember/addon/index.ts @@ -5,7 +5,7 @@ import { getOwnConfig, isDevelopingApp, macroCondition } from '@embroider/macros import { startSpan } from '@sentry/browser'; import type { BrowserOptions } from '@sentry/browser'; import * as Sentry from '@sentry/browser'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, applySdkMetadata } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, applySdkMetadata } from '@sentry/core'; import { GLOBAL_OBJ } from '@sentry/utils'; import Ember from 'ember'; @@ -75,9 +75,11 @@ export const instrumentRoutePerformance = (BaseRoute { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember', }, op, name, + onlyIfParent: true, }, () => { return fn(...args); diff --git a/packages/ember/addon/instance-initializers/sentry-performance.ts b/packages/ember/addon/instance-initializers/sentry-performance.ts index 7db6e386c192..f8d03a025217 100644 --- a/packages/ember/addon/instance-initializers/sentry-performance.ts +++ b/packages/ember/addon/instance-initializers/sentry-performance.ts @@ -145,6 +145,12 @@ export function _instrumentEmberRouter( routerService.on('routeWillChange', (transition: Transition) => { const { fromRoute, toRoute } = getTransitionInformation(transition, routerService); + + // We want to ignore loading && error routes + if (transitionIsIntermediate(transition)) { + return; + } + activeRootSpan?.end(); activeRootSpan = startBrowserTracingNavigationSpan(client, { @@ -167,8 +173,8 @@ export function _instrumentEmberRouter( }); }); - routerService.on('routeDidChange', () => { - if (!transitionSpan || !activeRootSpan) { + routerService.on('routeDidChange', transition => { + if (!transitionSpan || !activeRootSpan || transitionIsIntermediate(transition)) { return; } transitionSpan.end(); @@ -492,3 +498,18 @@ function _instrumentNavigation( export default { initialize, }; + +function transitionIsIntermediate(transition: Transition): boolean { + // We want to use ignore, as this may actually be defined on new versions + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This actually exists on newer versions + const isIntermediate: boolean | undefined = transition.isIntermediate; + + if (typeof isIntermediate === 'boolean') { + return isIntermediate; + } + + // For versions without this, we look if the route is a `.loading` or `.error` route + // This is not perfect and may false-positive in some cases, but it's the best we can do + return transition.to?.localName === 'loading' || transition.to?.localName === 'error'; +}