diff --git a/e2e-tests/tests/nextjs.test.ts b/e2e-tests/tests/nextjs.test.ts index 321e7b14..6949d32c 100644 --- a/e2e-tests/tests/nextjs.test.ts +++ b/e2e-tests/tests/nextjs.test.ts @@ -152,7 +152,7 @@ export const onRequestError = Sentry.captureRequestError;`, }); test('builds correctly', async () => { - await checkIfBuilds(projectDir, 'server-rendered on demand'); + await checkIfBuilds(projectDir); }); test('runs on prod mode correctly', async () => { diff --git a/e2e-tests/tests/nuxt-3.test.ts b/e2e-tests/tests/nuxt-3.test.ts index 87069a89..b13f71a9 100644 --- a/e2e-tests/tests/nuxt-3.test.ts +++ b/e2e-tests/tests/nuxt-3.test.ts @@ -180,7 +180,7 @@ function testNuxtProjectConfigs(projectDir: string) { function testNuxtProjectBuildsAndRuns(projectDir: string) { test('builds successfully', async () => { - await checkIfBuilds(projectDir, 'preview this build'); + await checkIfBuilds(projectDir); }); test('runs on prod mode correctly', async () => { diff --git a/e2e-tests/tests/nuxt-4.test.ts b/e2e-tests/tests/nuxt-4.test.ts index 0f6bf93f..b5a1ac6c 100644 --- a/e2e-tests/tests/nuxt-4.test.ts +++ b/e2e-tests/tests/nuxt-4.test.ts @@ -179,7 +179,7 @@ function testNuxtProjectConfigs(projectDir: string) { function testNuxtProjectBuildsAndRuns(projectDir: string) { test('builds successfully', async () => { - await checkIfBuilds(projectDir, 'preview this build'); + await checkIfBuilds(projectDir); }); test('runs on prod mode correctly', async () => { diff --git a/e2e-tests/tests/remix.test.ts b/e2e-tests/tests/remix.test.ts index 8a282123..bab0b5b6 100644 --- a/e2e-tests/tests/remix.test.ts +++ b/e2e-tests/tests/remix.test.ts @@ -61,24 +61,27 @@ app.all( app.listen(0, () => console.log('Express server listening')); `; - -async function runWizardOnRemixProject(projectDir: string, integration: Integration, fileModificationFn?: (projectDir: string, integration: Integration) => unknown) { +async function runWizardOnRemixProject( + projectDir: string, + integration: Integration, + fileModificationFn?: ( + projectDir: string, + integration: Integration, + ) => unknown, +) { const wizardInstance = startWizardInstance(integration, projectDir); let packageManagerPrompted = false; if (fileModificationFn) { fileModificationFn(projectDir, integration); - await wizardInstance.waitForOutput( - 'Do you want to continue anyway?', - ); + await wizardInstance.waitForOutput('Do you want to continue anyway?'); packageManagerPrompted = await wizardInstance.sendStdinAndWaitForOutput( [KEYS.ENTER], 'Please select your package manager.', ); } else { - packageManagerPrompted = await wizardInstance.waitForOutput( 'Please select your package manager.', ); @@ -119,12 +122,16 @@ async function runWizardOnRemixProject(projectDir: string, integration: Integrat ); wizardInstance.kill(); -}; +} -function checkRemixProject(projectDir: string, integration: Integration, options?: { - devModeExpectedOutput?: string; - prodModeExpectedOutput?: string; -}) { +function checkRemixProject( + projectDir: string, + integration: Integration, + options?: { + devModeExpectedOutput?: string; + prodModeExpectedOutput?: string; + }, +) { test('package.json is updated correctly', () => { checkPackageJson(projectDir, integration); }); @@ -195,15 +202,21 @@ function checkRemixProject(projectDir: string, integration: Integration, options }); test('builds successfully', async () => { - await checkIfBuilds(projectDir, 'built'); + await checkIfBuilds(projectDir); }); test('runs on dev mode correctly', async () => { - await checkIfRunsOnDevMode(projectDir, options?.devModeExpectedOutput || 'to expose'); + await checkIfRunsOnDevMode( + projectDir, + options?.devModeExpectedOutput || 'to expose', + ); }); test('runs on prod mode correctly', async () => { - await checkIfRunsOnProdMode(projectDir, options?.prodModeExpectedOutput || '[remix-serve]'); + await checkIfRunsOnProdMode( + projectDir, + options?.prodModeExpectedOutput || '[remix-serve]', + ); }); } @@ -236,13 +249,11 @@ describe('Remix', () => { beforeAll(async () => { await runWizardOnRemixProject(projectDir, integration, (projectDir) => { - createFile( - `${projectDir}/server.mjs`, - SERVER_TEMPLATE, - ); + createFile(`${projectDir}/server.mjs`, SERVER_TEMPLATE); modifyFile(`${projectDir}/package.json`, { - '"start": "remix-serve ./build/server/index.js"': '"start": "node ./server.mjs"', + '"start": "remix-serve ./build/server/index.js"': + '"start": "node ./server.mjs"', '"dev": "remix vite:dev"': '"dev": "node ./server.mjs"', }); }); diff --git a/e2e-tests/tests/sveltekit.test.ts b/e2e-tests/tests/sveltekit.test.ts index 56849669..074cd423 100644 --- a/e2e-tests/tests/sveltekit.test.ts +++ b/e2e-tests/tests/sveltekit.test.ts @@ -41,7 +41,14 @@ export async function handleError({ error, event }) { } `; -async function runWizardOnSvelteKitProject(projectDir: string, integration: Integration, fileModificationFn?: (projectDir: string, integration: Integration) => unknown) { +async function runWizardOnSvelteKitProject( + projectDir: string, + integration: Integration, + fileModificationFn?: ( + projectDir: string, + integration: Integration, + ) => unknown, +) { const wizardInstance = startWizardInstance(integration, projectDir); let packageManagerPrompted = false; @@ -49,9 +56,7 @@ async function runWizardOnSvelteKitProject(projectDir: string, integration: Inte fileModificationFn(projectDir, integration); // As we modified project, we have a warning prompt before we get the package manager prompt - await wizardInstance.waitForOutput( - 'Do you want to continue anyway?', - ); + await wizardInstance.waitForOutput('Do you want to continue anyway?'); packageManagerPrompted = await wizardInstance.sendStdinAndWaitForOutput( [KEYS.ENTER], @@ -59,7 +64,7 @@ async function runWizardOnSvelteKitProject(projectDir: string, integration: Inte ); } else { packageManagerPrompted = await wizardInstance.waitForOutput( - 'Please select your package manager' + 'Please select your package manager', ); } @@ -72,7 +77,7 @@ async function runWizardOnSvelteKitProject(projectDir: string, integration: Inte 'to track the performance of your application?', { timeout: 240_000, - } + }, )); const replayOptionPrompted = @@ -100,10 +105,14 @@ async function runWizardOnSvelteKitProject(projectDir: string, integration: Inte wizardInstance.kill(); } -function checkSvelteKitProject(projectDir: string, integration: Integration, options?: { - devModeExpectedOutput: string; - prodModeExpectedOutput: string; -}) { +function checkSvelteKitProject( + projectDir: string, + integration: Integration, + options?: { + devModeExpectedOutput: string; + prodModeExpectedOutput: string; + }, +) { test('should have the correct package.json', () => { checkPackageJson(projectDir, integration); }); @@ -113,14 +122,21 @@ function checkSvelteKitProject(projectDir: string, integration: Integration, opt }); test('example page exists', () => { - checkFileExists(path.resolve(projectDir, 'src/routes/sentry-example/+page.svelte')); - checkFileExists(path.resolve(projectDir, 'src/routes/sentry-example/+server.js')); + checkFileExists( + path.resolve(projectDir, 'src/routes/sentry-example/+page.svelte'), + ); + checkFileExists( + path.resolve(projectDir, 'src/routes/sentry-example/+server.js'), + ); }); test('vite.config contains sentry plugin', () => { - checkFileContents(path.resolve(projectDir, 'vite.config.ts'), `plugins: [sentrySvelteKit({ + checkFileContents( + path.resolve(projectDir, 'vite.config.ts'), + `plugins: [sentrySvelteKit({ sourceMapsUploadOptions: { -`); +`, + ); }); test('hook files created', () => { @@ -129,15 +145,22 @@ function checkSvelteKitProject(projectDir: string, integration: Integration, opt }); test('builds successfully', async () => { - await checkIfBuilds(projectDir, 'Successfully uploaded source maps to Sentry'); + await checkIfBuilds(projectDir); }); test('runs on dev mode correctly', async () => { - await checkIfRunsOnDevMode(projectDir, options?.devModeExpectedOutput || 'ready in'); + await checkIfRunsOnDevMode( + projectDir, + options?.devModeExpectedOutput || 'ready in', + ); }); test('runs on prod mode correctly', async () => { - await checkIfRunsOnProdMode(projectDir, options?.prodModeExpectedOutput || 'to expose', 'preview'); + await checkIfRunsOnProdMode( + projectDir, + options?.prodModeExpectedOutput || 'to expose', + 'preview', + ); }); } @@ -161,10 +184,9 @@ describe('Sveltekit', () => { checkSvelteKitProject(projectDir, integration); test('hooks.client.ts contains sentry', () => { - checkFileContents( - path.resolve(projectDir, 'src/hooks.client.ts'), - [`import * as Sentry from '@sentry/sveltekit';`, - `Sentry.init({ + checkFileContents(path.resolve(projectDir, 'src/hooks.client.ts'), [ + `import * as Sentry from '@sentry/sveltekit';`, + `Sentry.init({ dsn: '${TEST_ARGS.PROJECT_DSN}', tracesSampleRate: 1.0, @@ -179,22 +201,24 @@ describe('Sveltekit', () => { // If you don't want to use Session Replay, just remove the line below: integrations: [replayIntegration()], -});`, 'export const handleError = handleErrorWithSentry(']); +});`, + 'export const handleError = handleErrorWithSentry(', + ]); }); test('hooks.server.ts contains sentry', () => { - checkFileContents( - path.resolve(projectDir, 'src/hooks.server.ts'), - [ - `import * as Sentry from '@sentry/sveltekit';`, - `Sentry.init({ + checkFileContents(path.resolve(projectDir, 'src/hooks.server.ts'), [ + `import * as Sentry from '@sentry/sveltekit';`, + `Sentry.init({ dsn: '${TEST_ARGS.PROJECT_DSN}', tracesSampleRate: 1.0, // uncomment the line below to enable Spotlight (https://spotlightjs.com) // spotlight: import.meta.env.DEV, -});`, 'export const handleError = handleErrorWithSentry();']); +});`, + 'export const handleError = handleErrorWithSentry();', + ]); }); }); @@ -206,17 +230,21 @@ describe('Sveltekit', () => { ); beforeAll(async () => { - await runWizardOnSvelteKitProject(projectDir, integration, (projectDir) => { - createFile( - path.resolve(projectDir, 'src/hooks.server.ts'), - SERVER_HOOK_TEMPLATE, - ) - - createFile( - path.resolve(projectDir, 'src/hooks.client.ts'), - CLIENT_HOOK_TEMPLATE, - ) - }); + await runWizardOnSvelteKitProject( + projectDir, + integration, + (projectDir) => { + createFile( + path.resolve(projectDir, 'src/hooks.server.ts'), + SERVER_HOOK_TEMPLATE, + ); + + createFile( + path.resolve(projectDir, 'src/hooks.client.ts'), + CLIENT_HOOK_TEMPLATE, + ); + }, + ); }); afterAll(() => { @@ -229,27 +257,28 @@ describe('Sveltekit', () => { // These are removed from the common tests as the content is different // when the hooks are merged instead of created from the template test('hooks.client.ts contains sentry instrumentation', () => { - checkFileContents( - path.resolve(projectDir, 'src/hooks.client.ts'), - [`import * as Sentry from '@sentry/sveltekit';`, - `Sentry.init({ + checkFileContents(path.resolve(projectDir, 'src/hooks.client.ts'), [ + `import * as Sentry from '@sentry/sveltekit';`, + `Sentry.init({ dsn: "${TEST_ARGS.PROJECT_DSN}", tracesSampleRate: 1, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1, integrations: [Sentry.replayIntegration()] -})`, 'export const handleError = Sentry.handleErrorWithSentry(']); +})`, + 'export const handleError = Sentry.handleErrorWithSentry(', + ]); }); test('hooks.server.ts contains sentry init', () => { - checkFileContents( - path.resolve(projectDir, 'src/hooks.server.ts'), - [`import * as Sentry from '@sentry/sveltekit';`, - `Sentry.init({ + checkFileContents(path.resolve(projectDir, 'src/hooks.server.ts'), [ + `import * as Sentry from '@sentry/sveltekit';`, + `Sentry.init({ dsn: "${TEST_ARGS.PROJECT_DSN}", tracesSampleRate: 1 -})`, 'export const handleError = Sentry.handleErrorWithSentry();']); +})`, + 'export const handleError = Sentry.handleErrorWithSentry();', + ]); }); }); }); - diff --git a/e2e-tests/utils/index.ts b/e2e-tests/utils/index.ts index fda8155e..cb9b2671 100644 --- a/e2e-tests/utils/index.ts +++ b/e2e-tests/utils/index.ts @@ -80,6 +80,36 @@ export class WizardTestEnv { return outputPromise; } + /** + * Waits for the task to exit with a given `statusCode`. + * + * @returns a promise that resolves to `true` if the run ends with the status + * code, or it rejects when the `timeout` was reached. + */ + waitForStatusCode( + statusCode: number | null, + options: { + /** Timeout in ms */ + timeout?: number; + } = {}, + ) { + const { timeout } = { + timeout: 60_000, + ...options, + }; + + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + reject(new Error(`Timeout waiting for status code: ${statusCode}`)); + }, timeout); + + this.taskHandle.on('exit', (code: number | null) => { + clearTimeout(timeoutId); + resolve(code === statusCode); + }); + }); + } + /** * Waits for the provided output with `.includes()` logic. * @@ -318,19 +348,16 @@ export function checkEnvBuildPlugin(projectDir: string) { } /** - * Check if the project builds + * Check if the project builds and ends with status code 0. * @param projectDir */ -export async function checkIfBuilds( - projectDir: string, - expectedOutput: string, -) { +export async function checkIfBuilds(projectDir: string) { const testEnv = new WizardTestEnv('npm', ['run', 'build'], { cwd: projectDir, }); await expect( - testEnv.waitForOutput(expectedOutput, { + testEnv.waitForStatusCode(0, { timeout: 120_000, }), ).resolves.toBe(true);