diff --git a/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/validIntegrationNpm/init.js b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/validIntegrationNpm/init.js new file mode 100644 index 000000000000..d06b9b7b1dfd --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/validIntegrationNpm/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +// So we can use this in subject.js +// We specifically DO NOT set this on window.Sentry as we want to test a non-CDN bundle environment, +// where window.Sentry is usually not available +window._testSentry = { ...Sentry }; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [], +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/validIntegrationNpm/subject.js b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/validIntegrationNpm/subject.js new file mode 100644 index 000000000000..cad7d1083252 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/validIntegrationNpm/subject.js @@ -0,0 +1,7 @@ +window._testLazyLoadIntegration = async function run() { + const integration = await window._testSentry.lazyLoadIntegration('httpClientIntegration'); + + window._testSentry.getClient()?.addIntegration(integration()); + + window._integrationLoaded = true; +}; diff --git a/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/validIntegrationNpm/test.ts b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/validIntegrationNpm/test.ts new file mode 100644 index 000000000000..707205912545 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/validIntegrationNpm/test.ts @@ -0,0 +1,39 @@ +import { expect } from '@playwright/test'; +import { SDK_VERSION } from '@sentry/browser'; + +import { sentryTest } from '../../../../utils/fixtures'; + +sentryTest('it allows to lazy load an integration when using the NPM package', async ({ getLocalTestUrl, page }) => { + const bundle = process.env.PW_BUNDLE || ''; + // We only want to run this in non-CDN bundle mode + if (bundle.startsWith('bundle')) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route(`https://browser.sentry-cdn.com/${SDK_VERSION}/httpclient.min.js`, route => { + return route.fulfill({ + status: 200, + contentType: 'application/javascript;', + body: "window.Sentry = window.Sentry || {};window.Sentry.httpClientIntegration = () => ({ name: 'HttpClient' })", + }); + }); + + await page.goto(url); + + const hasIntegration = await page.evaluate('!!window._testSentry.getClient().getIntegrationByName("HttpClient")'); + expect(hasIntegration).toBe(false); + + const scriptTagsBefore = await page.evaluate('document.querySelectorAll("script").length'); + + await page.evaluate('window._testLazyLoadIntegration()'); + await page.waitForFunction('window._integrationLoaded'); + + const scriptTagsAfter = await page.evaluate('document.querySelectorAll("script").length'); + + const hasIntegration2 = await page.evaluate('!!window._testSentry.getClient().getIntegrationByName("HttpClient")'); + expect(hasIntegration2).toBe(true); + + expect(scriptTagsAfter).toBe(scriptTagsBefore + 1); +}); diff --git a/packages/browser/src/utils/lazyLoadIntegration.ts b/packages/browser/src/utils/lazyLoadIntegration.ts index aaa5987a183c..f8d12abd93c0 100644 --- a/packages/browser/src/utils/lazyLoadIntegration.ts +++ b/packages/browser/src/utils/lazyLoadIntegration.ts @@ -33,12 +33,15 @@ const WindowWithMaybeIntegration = WINDOW as { export async function lazyLoadIntegration(name: keyof typeof LazyLoadableIntegrations): Promise { const bundle = LazyLoadableIntegrations[name]; - if (!bundle || !WindowWithMaybeIntegration.Sentry) { + // `window.Sentry` is only set when using a CDN bundle, but this method can also be used via the NPM package + const sentryOnWindow = (WindowWithMaybeIntegration.Sentry = WindowWithMaybeIntegration.Sentry || {}); + + if (!bundle) { throw new Error(`Cannot lazy load integration: ${name}`); } // Bail if the integration already exists - const existing = WindowWithMaybeIntegration.Sentry[name]; + const existing = sentryOnWindow[name]; if (typeof existing === 'function') { return existing; } @@ -61,7 +64,7 @@ export async function lazyLoadIntegration(name: keyof typeof LazyLoadableIntegra throw new Error(`Error when loading integration: ${name}`); } - const integrationFn = WindowWithMaybeIntegration.Sentry[name]; + const integrationFn = sentryOnWindow[name]; if (typeof integrationFn !== 'function') { throw new Error(`Could not load integration: ${name}`); diff --git a/packages/browser/test/unit/utils/lazyLoadIntegration.test.ts b/packages/browser/test/unit/utils/lazyLoadIntegration.test.ts index f2afefcbe9a2..fa6bc62fb4fd 100644 --- a/packages/browser/test/unit/utils/lazyLoadIntegration.test.ts +++ b/packages/browser/test/unit/utils/lazyLoadIntegration.test.ts @@ -48,12 +48,6 @@ describe('lazyLoadIntegration', () => { await expect(() => lazyLoadIntegration('invalid!!!')).rejects.toThrow('Cannot lazy load integration: invalid!!!'); }); - test('it rejects without global Sentry variable', async () => { - await expect(() => lazyLoadIntegration('httpClientIntegration')).rejects.toThrow( - 'Cannot lazy load integration: httpClientIntegration', - ); - }); - test('it does not inject a script tag if integration already exists', async () => { // @ts-expect-error For testing sake global.Sentry = Sentry;