diff --git a/.prettierignore b/.prettierignore index 113a311c2c62..f2ddf012c98e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,2 @@ *.md .nxcache -packages/browser-integration-tests/fixtures diff --git a/packages/browser-integration-tests/.eslintrc.js b/packages/browser-integration-tests/.eslintrc.js index 16de146cce24..47e485f9068a 100644 --- a/packages/browser-integration-tests/.eslintrc.js +++ b/packages/browser-integration-tests/.eslintrc.js @@ -11,6 +11,7 @@ module.exports = { 'loader-suites/**/subject.js', 'scripts/**', 'fixtures/**', + 'tmp/**', ], parserOptions: { sourceType: 'module', diff --git a/packages/browser-integration-tests/.gitignore b/packages/browser-integration-tests/.gitignore index 87364a273f79..41569583fe3f 100644 --- a/packages/browser-integration-tests/.gitignore +++ b/packages/browser-integration-tests/.gitignore @@ -1 +1,2 @@ test-results +tmp diff --git a/packages/browser-integration-tests/.prettierignore b/packages/browser-integration-tests/.prettierignore new file mode 100644 index 000000000000..af2405d11b29 --- /dev/null +++ b/packages/browser-integration-tests/.prettierignore @@ -0,0 +1,2 @@ +tmp +fixtures diff --git a/packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts b/packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts index a45d0c42200d..722d2923fcff 100644 --- a/packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts +++ b/packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import path from 'path'; import { sentryTest, TEST_HOST } from '../../../../utils/fixtures'; -import { LOADER_CONFIGS } from '../../../../utils/generatePage'; +import { LOADER_CONFIGS } from '../../../../utils/generatePlugin'; import { envelopeRequestParser, waitForErrorRequest } from '../../../../utils/helpers'; const bundle = process.env.PW_BUNDLE || ''; diff --git a/packages/browser-integration-tests/package.json b/packages/browser-integration-tests/package.json index 9a972e78b075..1c8a31969bfe 100644 --- a/packages/browser-integration-tests/package.json +++ b/packages/browser-integration-tests/package.json @@ -8,7 +8,7 @@ }, "private": true, "scripts": { - "clean": "rimraf -g suites/**/dist loader-suites/**/dist", + "clean": "rimraf -g suites/**/dist loader-suites/**/dist tmp", "install-browsers": "playwright install --with-deps", "lint": "run-s lint:prettier lint:eslint", "lint:eslint": "eslint . --format stylish", @@ -58,6 +58,7 @@ }, "devDependencies": { "@types/glob": "8.0.0", + "@types/node": "^14.6.4", "glob": "8.0.3" }, "volta": { diff --git a/packages/browser-integration-tests/playwright.config.ts b/packages/browser-integration-tests/playwright.config.ts index 3baf3937c1f7..8b85d4a83f4f 100644 --- a/packages/browser-integration-tests/playwright.config.ts +++ b/packages/browser-integration-tests/playwright.config.ts @@ -7,5 +7,8 @@ const config: PlaywrightTestConfig = { // Use 3 workers on CI, else use defaults (based on available CPU cores) // Note that 3 is a random number selected to work well with our CI setup workers: process.env.CI ? 3 : undefined, + + globalSetup: require.resolve('./playwright.setup.ts'), }; + export default config; diff --git a/packages/browser-integration-tests/playwright.setup.ts b/packages/browser-integration-tests/playwright.setup.ts new file mode 100644 index 000000000000..fe97c814199e --- /dev/null +++ b/packages/browser-integration-tests/playwright.setup.ts @@ -0,0 +1,5 @@ +import setupStaticAssets from './utils/staticAssets'; + +export default function globalSetup(): Promise { + return setupStaticAssets(); +} diff --git a/packages/browser-integration-tests/utils/fixtures.ts b/packages/browser-integration-tests/utils/fixtures.ts index 1a848dfdd22c..274f42ab3fd4 100644 --- a/packages/browser-integration-tests/utils/fixtures.ts +++ b/packages/browser-integration-tests/utils/fixtures.ts @@ -3,7 +3,7 @@ import { test as base } from '@playwright/test'; import fs from 'fs'; import path from 'path'; -import { generateLoader, generatePage } from './generatePage'; +import { generatePage } from './generatePage'; export const TEST_HOST = 'http://sentry-test.io'; @@ -53,7 +53,6 @@ const sentryTest = base.extend({ const pagePath = `${TEST_HOST}/index.html`; await build(testDir); - generateLoader(testDir); // Serve all assets under if (!skipRouteHandler) { diff --git a/packages/browser-integration-tests/utils/generatePage.ts b/packages/browser-integration-tests/utils/generatePage.ts index 45b276f303a0..47be50c707e4 100644 --- a/packages/browser-integration-tests/utils/generatePage.ts +++ b/packages/browser-integration-tests/utils/generatePage.ts @@ -1,88 +1,10 @@ -import { existsSync, mkdirSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from 'fs'; +import { existsSync, mkdirSync } from 'fs'; import HtmlWebpackPlugin from 'html-webpack-plugin'; -import path from 'path'; import webpack from 'webpack'; import webpackConfig from '../webpack.config'; -import { TEST_HOST } from './fixtures'; import SentryScenarioGenerationPlugin from './generatePlugin'; -const LOADER_TEMPLATE = readFileSync(path.join(__dirname, '../fixtures/loader.js'), 'utf-8'); - -export const LOADER_CONFIGS: Record; lazy: boolean }> = { - loader_base: { - bundle: 'browser/build/bundles/bundle.es5.min.js', - options: {}, - lazy: true, - }, - loader_eager: { - bundle: 'browser/build/bundles/bundle.es5.min.js', - options: {}, - lazy: false, - }, - loader_debug: { - bundle: 'browser/build/bundles/bundle.es5.debug.min.js', - options: { debug: true }, - lazy: true, - }, - loader_tracing: { - bundle: 'browser/build/bundles/bundle.tracing.es5.min.js', - options: { tracesSampleRate: 1 }, - lazy: false, - }, - loader_replay: { - bundle: 'browser/build/bundles/bundle.replay.min.js', - options: { replaysSessionSampleRate: 1, replaysOnErrorSampleRate: 1 }, - lazy: false, - }, - loader_tracing_replay: { - bundle: 'browser/build/bundles/bundle.tracing.replay.debug.min.js', - options: { tracesSampleRate: 1, replaysSessionSampleRate: 1, replaysOnErrorSampleRate: 1, debug: true }, - lazy: false, - }, -}; - -const bundleKey = process.env.PW_BUNDLE || ''; - -export function generateLoader(outPath: string): void { - const localPath = `${outPath}/dist`; - - if (!existsSync(localPath)) { - return; - } - - // Generate loader files - const loaderConfig = LOADER_CONFIGS[bundleKey]; - - if (!loaderConfig) { - throw new Error(`Unknown loader bundle key: ${bundleKey}`); - } - - const localCdnBundlePath = path.join(localPath, 'cdn.bundle.js'); - - try { - unlinkSync(localCdnBundlePath); - } catch { - // ignore if this didn't exist - } - - const cdnSourcePath = path.resolve(__dirname, `../../${loaderConfig.bundle}`); - symlinkSync(cdnSourcePath, localCdnBundlePath); - - const loaderPath = path.join(localPath, 'loader.js'); - const loaderContent = LOADER_TEMPLATE.replace('__LOADER_BUNDLE__', `'${TEST_HOST}/cdn.bundle.js'`) - .replace( - '__LOADER_OPTIONS__', - JSON.stringify({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - ...loaderConfig.options, - }), - ) - .replace('__LOADER_LAZY__', loaderConfig.lazy ? 'true' : 'false'); - - writeFileSync(loaderPath, loaderContent, 'utf-8'); -} - export async function generatePage( initPath: string, subjectPath: string, @@ -110,7 +32,7 @@ export async function generatePage( filename: '[name].bundle.js', }, plugins: [ - new SentryScenarioGenerationPlugin(), + new SentryScenarioGenerationPlugin(localPath), new HtmlWebpackPlugin({ filename: outPageName, template: templatePath, diff --git a/packages/browser-integration-tests/utils/generatePlugin.ts b/packages/browser-integration-tests/utils/generatePlugin.ts index 202ea6f6126d..c25cf823c4a5 100644 --- a/packages/browser-integration-tests/utils/generatePlugin.ts +++ b/packages/browser-integration-tests/utils/generatePlugin.ts @@ -4,6 +4,9 @@ import HtmlWebpackPlugin, { createHtmlTagObject } from 'html-webpack-plugin'; import path from 'path'; import type { Compiler } from 'webpack'; +import { addStaticAsset, addStaticAssetSymlink } from './staticAssets'; + +const LOADER_TEMPLATE = fs.readFileSync(path.join(__dirname, '../fixtures/loader.js'), 'utf-8'); const PACKAGES_DIR = '../../packages'; /** @@ -34,6 +37,12 @@ const BUNDLE_PATHS: Record> = { bundle_tracing_es6_min: 'build/bundles/bundle.tracing.min.js', bundle_tracing_replay_es6: 'build/bundles/bundle.tracing.replay.js', bundle_tracing_replay_es6_min: 'build/bundles/bundle.tracing.replay.min.js', + loader_base: 'build/bundles/bundle.es5.min.js', + loader_eager: 'build/bundles/bundle.es5.min.js', + loader_debug: 'build/bundles/bundle.es5.debug.min.js', + loader_tracing: 'build/bundles/bundle.tracing.es5.min.js', + loader_replay: 'build/bundles/bundle.replay.min.js', + loader_tracing_replay: 'build/bundles/bundle.tracing.replay.debug.min.js', }, integrations: { cjs: 'build/npm/cjs/index.js', @@ -51,6 +60,33 @@ const BUNDLE_PATHS: Record> = { }, }; +export const LOADER_CONFIGS: Record; lazy: boolean }> = { + loader_base: { + options: {}, + lazy: true, + }, + loader_eager: { + options: {}, + lazy: false, + }, + loader_debug: { + options: { debug: true }, + lazy: true, + }, + loader_tracing: { + options: { tracesSampleRate: 1 }, + lazy: false, + }, + loader_replay: { + options: { replaysSessionSampleRate: 1, replaysOnErrorSampleRate: 1 }, + lazy: false, + }, + loader_tracing_replay: { + options: { tracesSampleRate: 1, replaysSessionSampleRate: 1, replaysOnErrorSampleRate: 1, debug: true }, + lazy: false, + }, +}; + /* * Generate webpack aliases based on packages in monorepo * @@ -97,9 +133,14 @@ function generateSentryAlias(): Record { class SentryScenarioGenerationPlugin { public requiredIntegrations: string[] = []; public requiresWASMIntegration: boolean = false; + public localOutPath: string; private _name: string = 'SentryScenarioGenerationPlugin'; + public constructor(localOutPath: string) { + this.localOutPath = localOutPath; + } + public apply(compiler: Compiler): void { compiler.options.resolve.alias = generateSentryAlias(); compiler.options.externals = useBundleOrLoader @@ -136,20 +177,34 @@ class SentryScenarioGenerationPlugin { const bundleName = 'browser'; const bundlePath = BUNDLE_PATHS[bundleName][bundleKey]; - let bundleObject = - bundlePath && - createHtmlTagObject('script', { - src: path.resolve(PACKAGES_DIR, bundleName, bundlePath), - }); - - if (!bundleObject && useLoader) { - bundleObject = createHtmlTagObject('script', { - src: 'loader.js', - }); + if (!bundlePath) { + throw new Error(`Could not find bundle or loader for key ${bundleKey}`); } - if (!bundleObject) { - throw new Error(`Could not find bundle or loader for key ${bundleKey}`); + const bundleObject = useLoader + ? createHtmlTagObject('script', { + src: 'loader.js', + }) + : createHtmlTagObject('script', { + src: 'cdn.bundle.js', + }); + + addStaticAssetSymlink(this.localOutPath, path.resolve(PACKAGES_DIR, bundleName, bundlePath), 'cdn.bundle.js'); + + if (useLoader) { + const loaderConfig = LOADER_CONFIGS[bundleKey]; + + addStaticAsset(this.localOutPath, 'loader.js', () => { + return LOADER_TEMPLATE.replace('__LOADER_BUNDLE__', "'/cdn.bundle.js'") + .replace( + '__LOADER_OPTIONS__', + JSON.stringify({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + ...loaderConfig.options, + }), + ) + .replace('__LOADER_LAZY__', loaderConfig.lazy ? 'true' : 'false'); + }); } // Convert e.g. bundle_tracing_es5_min to bundle_es5_min @@ -159,20 +214,33 @@ class SentryScenarioGenerationPlugin { .replace('_tracing', ''); this.requiredIntegrations.forEach(integration => { - const integrationObject = createHtmlTagObject('script', { - src: path.resolve( + const fileName = `${integration}.bundle.js`; + addStaticAssetSymlink( + this.localOutPath, + path.resolve( PACKAGES_DIR, 'integrations', BUNDLE_PATHS['integrations'][integrationBundleKey].replace('[INTEGRATION_NAME]', integration), ), + fileName, + ); + + const integrationObject = createHtmlTagObject('script', { + src: fileName, }); data.assetTags.scripts.unshift(integrationObject); }); if (this.requiresWASMIntegration && BUNDLE_PATHS['wasm'][integrationBundleKey]) { + addStaticAssetSymlink( + this.localOutPath, + path.resolve(PACKAGES_DIR, 'wasm', BUNDLE_PATHS['wasm'][integrationBundleKey]), + 'wasm.bundle.js', + ); + const wasmObject = createHtmlTagObject('script', { - src: path.resolve(PACKAGES_DIR, 'wasm', BUNDLE_PATHS['wasm'][integrationBundleKey]), + src: 'wasm.bundle.js', }); data.assetTags.scripts.unshift(wasmObject); diff --git a/packages/browser-integration-tests/utils/staticAssets.ts b/packages/browser-integration-tests/utils/staticAssets.ts new file mode 100644 index 000000000000..e293bd65237c --- /dev/null +++ b/packages/browser-integration-tests/utils/staticAssets.ts @@ -0,0 +1,51 @@ +import fs from 'fs'; +import path from 'path'; + +export const STATIC_DIR = path.join(__dirname, '../tmp/static'); + +export default async function setupStaticAssets(): Promise { + if (fs.existsSync(STATIC_DIR)) { + await fs.promises.rm(STATIC_DIR, { recursive: true }); + } + + await fs.promises.mkdir(STATIC_DIR, { recursive: true }); +} + +export function addStaticAsset(localOutPath: string, fileName: string, cb: () => string): void { + const newPath = path.join(STATIC_DIR, fileName); + + // Only copy files once + if (!fs.existsSync(newPath)) { + fs.writeFileSync(newPath, cb(), 'utf-8'); + } + + symlinkAsset(newPath, path.join(localOutPath, fileName)); +} + +export function addStaticAssetSymlink(localOutPath: string, originalPath: string, fileName: string): void { + const newPath = path.join(STATIC_DIR, fileName); + + // Only copy files once + if (!fs.existsSync(newPath)) { + fs.symlinkSync(originalPath, newPath); + } + + symlinkAsset(newPath, path.join(localOutPath, fileName)); +} + +function symlinkAsset(originalPath: string, targetPath: string): void { + try { + fs.unlinkSync(targetPath); + } catch { + // ignore errors here + } + + try { + fs.linkSync(originalPath, targetPath); + } catch (error) { + // only ignore these kind of errors + if (!`${error}`.includes('file already exists')) { + throw error; + } + } +}