diff --git a/CLAUDE.md b/CLAUDE.md index e180dd181..b5a7f358e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -181,9 +181,10 @@ Use `abortIfCancelled()` wrapper for all Clack prompts to handle Ctrl+C graceful - `test-applications/`: Complete framework apps for testing - `tests/`: Test files that run wizard against test apps -- `utils/`: Test helpers including `WizardTestEnv` class +- `utils/`: Test helpers including assertion functions for reusable checks (e.g. `checkIfBuilds`) - Requires `.env` file with Sentry credentials - Use `yarn test:e2e [framework]` to run specific framework tests +- Use `clifty` to define wizard run and interactions ## Special Considerations diff --git a/e2e-tests/README.md b/e2e-tests/README.md index b5f8e5926..184e77d16 100644 --- a/e2e-tests/README.md +++ b/e2e-tests/README.md @@ -32,12 +32,8 @@ modifiers that can be used in (`*.test.ts`). #### Helpers -- `startWizardInstance` - Starts a new instance of `WizardTestEnv`. - -- `initGit` - Initializes a temporary git repository in the test project. -- `cleanupGit` - Cleans up the temporary git repository in the test project. -- `revertLocalChanges` - Reverts local changes (git tracked or untracked) in the - test project. +- `createIsolatedTestEnv` - creates a new isolated test env by copying the test app to a temporary directory. + Also initializes git in the tmp dir - `createFile` - Creates a file (optionally with content) in the test project. - `modifyFile` - Modifies a file in the test project. @@ -61,12 +57,6 @@ modifiers that can be used in (`*.test.ts`). - `checkSentryProperties` - Checks if the Flutter `sentry.properties` file contains the auth token -#### `WizardTestEnv` - -`WizardTestEnv` is a class that can be used to run the Sentry Wizard in a test -environment. It provides methods to run the wizard with specific arguments and -stdio. - ## Running Tests Locally First, you need to create a `.env` file set the environment variables from the @@ -82,6 +72,65 @@ To run a specific test application ## Writing Tests -Each test file should contain a single test suite that tests the Sentry Wizard -for a specific framework. The test suite should contain a `beforeAll` and -`afterAll` function that starts and stops the test application respectively. +Each test file should test the Sentry Wizard for a specific framework and project. + +The test suite may contain multiple wizard runs but for consistency, each scenario must be +isolated via `createIsolatedTestEnv`. You can most easily do this by using a `describe` block +per wizard run. + +For every `describe` block, isolate the test, run the wizard in `beforeAll`, `test` what you +want to test and clean up the tmp dir in `afterAll`: + +```ts +describe('no sentry files present', () => { + const {projectDir, cleanup} = createIsolatedTestEnv(); + + beforeAll(() => { + await runWizard(projectDir); + }); + + afterAll(() => { + cleanup() + }) +}) + +describe('with sentry files present', () => { + const {projectDir, cleanup} = createIsolatedTestEnv(); + + beforeAll(() => { + addSentryFiles(projectDir); + await runWizard(projectDir); + }); + + afterAll(() => { + cleanup() + }) +}) +``` + +### Running the wizard + +To define how a wizard run should look like (i.e. which responses the "user" makes) on +wizard prompots, use `clifty`. Clifty's `run` method starts a new process to run the wizard +with the predefined interaction and returns the processe's exit code. +You can use this to check for a successful wizard run. + +```ts +import { KEYS, withEnv } from 'clifty'; + +const wizardExitCode = await withEnv({ + cwd: projectDir, +}) + .defineInteraction() + .whenAsked('Do you want to enable Tracing') + .respondWith(KEYS.ENTER) + .expectOutput('Added Sentry code to sentry.client.ts') + .run(getWizardCommand(Integrations.nextjs)); + +// ... + +test('wizard ran successfully', () => { + expect(wizardExitCode).toBe(0); +}) +``` + diff --git a/e2e-tests/tests/angular-17.test.ts b/e2e-tests/tests/angular-17.test.ts index 0da1681eb..7d8611042 100644 --- a/e2e-tests/tests/angular-17.test.ts +++ b/e2e-tests/tests/angular-17.test.ts @@ -171,7 +171,7 @@ function checkAngularProject( }, ) { test('package.json is updated correctly', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/angular'); const packageJsonFile = path.resolve(projectDir, 'package.json'); checkFileContents(packageJsonFile, [ diff --git a/e2e-tests/tests/angular-19.test.ts b/e2e-tests/tests/angular-19.test.ts index 7442260ce..bfdafe6f4 100644 --- a/e2e-tests/tests/angular-19.test.ts +++ b/e2e-tests/tests/angular-19.test.ts @@ -170,7 +170,7 @@ function checkAngularProject( }, ) { test('package.json is updated correctly', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/angular'); const packageJsonFile = path.resolve(projectDir, 'package.json'); checkFileContents(packageJsonFile, [ diff --git a/e2e-tests/tests/cloudflare-worker.test.ts b/e2e-tests/tests/cloudflare-worker.test.ts index dfbb4d916..e24db4501 100644 --- a/e2e-tests/tests/cloudflare-worker.test.ts +++ b/e2e-tests/tests/cloudflare-worker.test.ts @@ -19,7 +19,6 @@ describe('cloudflare-worker', () => { const { projectDir, cleanup } = createIsolatedTestEnv('cloudflare-test-app'); beforeAll(async () => { - // Capture the date before running the wizard (wizard runs in subprocess) expectedCompatibilityDate = new Date().toISOString().slice(0, 10); @@ -61,7 +60,7 @@ describe('cloudflare-worker', () => { }); it('adds the SDK dependency to package.json', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/cloudflare'); }); it('builds correctly', async () => { diff --git a/e2e-tests/tests/nextjs-14.test.ts b/e2e-tests/tests/nextjs-14.test.ts index 7f2338ce1..414fff812 100644 --- a/e2e-tests/tests/nextjs-14.test.ts +++ b/e2e-tests/tests/nextjs-14.test.ts @@ -65,7 +65,7 @@ describe('NextJS-14', () => { }); test('package.json is updated correctly', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/nextjs'); }); test('.env-sentry-build-plugin is created and contains the auth token', () => { diff --git a/e2e-tests/tests/nextjs-15.test.ts b/e2e-tests/tests/nextjs-15.test.ts index 2a1387318..398bfd1e6 100644 --- a/e2e-tests/tests/nextjs-15.test.ts +++ b/e2e-tests/tests/nextjs-15.test.ts @@ -14,7 +14,6 @@ import { checkIfRunsOnProdMode, checkPackageJson, getWizardCommand, - initGit, } from '../utils'; import { describe, beforeAll, afterAll, test, expect } from 'vitest'; @@ -72,7 +71,7 @@ describe('NextJS-15', () => { }); test('package.json is updated correctly', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/nextjs'); }); test('.env-sentry-build-plugin is created and contains the auth token', () => { @@ -146,13 +145,12 @@ export const onRequestError = Sentry.captureRequestError;`, describe('NextJS-15 Spotlight', () => { const integration = Integration.nextjs; + let wizardExitCode: number; const { projectDir, cleanup } = createIsolatedTestEnv('nextjs-15-test-app'); beforeAll(async () => { - initGit(projectDir); - - await withEnv({ + wizardExitCode = await withEnv({ cwd: projectDir, }) .defineInteraction() @@ -189,8 +187,12 @@ describe('NextJS-15 Spotlight', () => { cleanup(); }); + test('exits with exit code 0', () => { + expect(wizardExitCode).toBe(0); + }); + test('package.json is updated correctly', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/nextjs'); }); test('.env-sentry-build-plugin should NOT exist in spotlight mode', () => { diff --git a/e2e-tests/tests/nextjs-16.test.ts b/e2e-tests/tests/nextjs-16.test.ts index 32403e972..0dbf1b9e7 100644 --- a/e2e-tests/tests/nextjs-16.test.ts +++ b/e2e-tests/tests/nextjs-16.test.ts @@ -8,8 +8,6 @@ import { checkPackageJson, createIsolatedTestEnv, getWizardCommand, - initGit, - revertLocalChanges, } from '../utils'; import { describe, beforeAll, afterAll, test, expect } from 'vitest'; @@ -23,9 +21,6 @@ describe('NextJS-16 with Prettier, Biome, and ESLint', () => { const { projectDir, cleanup } = createIsolatedTestEnv('nextjs-16-test-app'); beforeAll(async () => { - initGit(projectDir); - revertLocalChanges(projectDir); - wizardExitCode = await withEnv({ cwd: projectDir, }) @@ -75,7 +70,7 @@ describe('NextJS-16 with Prettier, Biome, and ESLint', () => { }); test('package.json is updated correctly', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/nextjs'); }); test('config files created', () => { diff --git a/e2e-tests/tests/nuxt-3.test.ts b/e2e-tests/tests/nuxt-3.test.ts index 7dd45506b..3e828ce00 100644 --- a/e2e-tests/tests/nuxt-3.test.ts +++ b/e2e-tests/tests/nuxt-3.test.ts @@ -73,7 +73,7 @@ describe('Nuxt-3', () => { }); test('package.json is updated correctly', () => { - checkPackageJson(projectDir, Integration.nuxt); + checkPackageJson(projectDir, '@sentry/nuxt'); }); test('.env-sentry-build-plugin is created and contains the auth token', () => { diff --git a/e2e-tests/tests/nuxt-4.test.ts b/e2e-tests/tests/nuxt-4.test.ts index 521398ee3..8b832872e 100644 --- a/e2e-tests/tests/nuxt-4.test.ts +++ b/e2e-tests/tests/nuxt-4.test.ts @@ -73,7 +73,7 @@ describe('Nuxt-4', () => { }); test('package.json is updated correctly', () => { - checkPackageJson(projectDir, Integration.nuxt); + checkPackageJson(projectDir, '@sentry/nuxt'); }); test('.env-sentry-build-plugin is created and contains the auth token', () => { diff --git a/e2e-tests/tests/pnpm-workspace.test.ts b/e2e-tests/tests/pnpm-workspace.test.ts index 7b2dd0d8c..fd1b473b1 100644 --- a/e2e-tests/tests/pnpm-workspace.test.ts +++ b/e2e-tests/tests/pnpm-workspace.test.ts @@ -21,11 +21,12 @@ describe('pnpm workspace', () => { const integration = Integration.sveltekit; let wizardExitCode: number; - const { projectDir: workspaceDir, cleanup } = createIsolatedTestEnv('pnpm-workspace-test-app'); + const { projectDir: workspaceDir, cleanup } = createIsolatedTestEnv( + 'pnpm-workspace-test-app', + ); const projectDir = path.resolve(workspaceDir, 'packages/sveltekit'); beforeAll(async () => { - wizardExitCode = await withEnv({ cwd: projectDir, }) @@ -70,7 +71,7 @@ describe('pnpm workspace', () => { }); it('adds the SDK dependency to package.json', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/sveltekit'); }); it('adds the .env.sentry-build-plugin', () => { diff --git a/e2e-tests/tests/react-router.test.ts b/e2e-tests/tests/react-router.test.ts index 28ab169fc..7c1c5d7e4 100644 --- a/e2e-tests/tests/react-router.test.ts +++ b/e2e-tests/tests/react-router.test.ts @@ -96,7 +96,7 @@ describe('React Router', () => { }); test('package.json is updated correctly', () => { - checkPackageJson(projectDir, Integration.reactRouter); + checkPackageJson(projectDir, '@sentry/react-router'); }); test('.env.sentry-build-plugin is created and contains the auth token', () => { @@ -267,7 +267,7 @@ startTransition(() => { // Only test the essential checks for this edge case test('package.json is updated correctly', () => { - checkPackageJson(projectDir, Integration.reactRouter); + checkPackageJson(projectDir, '@sentry/react-router'); }); test('essential files exist or wizard completes gracefully', () => { @@ -336,7 +336,7 @@ startTransition(() => { }); test('basic configuration still works', () => { - checkPackageJson(projectDir, Integration.reactRouter); + checkPackageJson(projectDir, '@sentry/react-router'); checkFileExists(`${projectDir}/instrument.server.mjs`); }); }); diff --git a/e2e-tests/tests/remix.test.ts b/e2e-tests/tests/remix.test.ts index d2252838d..dc11b014b 100644 --- a/e2e-tests/tests/remix.test.ts +++ b/e2e-tests/tests/remix.test.ts @@ -126,7 +126,7 @@ describe('Remix', () => { }); test('package.json is updated correctly', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/remix'); }); test('.env-sentry-build-plugin is created and contains the auth token', () => { @@ -241,7 +241,7 @@ describe('Remix', () => { }); test('package.json is updated correctly', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/remix'); }); test('.env-sentry-build-plugin is created and contains the auth token', () => { diff --git a/e2e-tests/tests/sveltekit-hooks.test.ts b/e2e-tests/tests/sveltekit-hooks.test.ts index f6a49af98..de9b5a252 100644 --- a/e2e-tests/tests/sveltekit-hooks.test.ts +++ b/e2e-tests/tests/sveltekit-hooks.test.ts @@ -45,10 +45,11 @@ describe.sequential('Sveltekit', () => { describe('without existing hooks', () => { const integration = Integration.sveltekit; - const { projectDir, cleanup } = createIsolatedTestEnv('sveltekit-hooks-test-app'); + const { projectDir, cleanup } = createIsolatedTestEnv( + 'sveltekit-hooks-test-app', + ); beforeAll(async () => { - await runWizardOnSvelteKitProject(projectDir, integration); }); @@ -57,7 +58,7 @@ describe.sequential('Sveltekit', () => { }); test('has the correct package.json', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/sveltekit'); }); test('has the correct .env.sentry-build-plugin', () => { @@ -159,10 +160,11 @@ describe.sequential('Sveltekit', () => { describe('with existing hooks', () => { const integration = Integration.sveltekit; - const { projectDir, cleanup } = createIsolatedTestEnv('sveltekit-hooks-test-app'); + const { projectDir, cleanup } = createIsolatedTestEnv( + 'sveltekit-hooks-test-app', + ); beforeAll(async () => { - await runWizardOnSvelteKitProject( projectDir, integration, @@ -185,7 +187,7 @@ describe.sequential('Sveltekit', () => { }); test('has the correct package.json', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/sveltekit'); }); test('has the correct .env.sentry-build-plugin', () => { diff --git a/e2e-tests/tests/sveltekit-tracing.test.ts b/e2e-tests/tests/sveltekit-tracing.test.ts index af13b3b73..afcabb16e 100644 --- a/e2e-tests/tests/sveltekit-tracing.test.ts +++ b/e2e-tests/tests/sveltekit-tracing.test.ts @@ -23,10 +23,11 @@ describe('Sveltekit with instrumentation and tracing', () => { const integration = Integration.sveltekit; let wizardExitCode: number; - const { projectDir, cleanup } = createIsolatedTestEnv('sveltekit-tracing-test-app'); + const { projectDir, cleanup } = createIsolatedTestEnv( + 'sveltekit-tracing-test-app', + ); beforeAll(async () => { - wizardExitCode = await withEnv({ cwd: projectDir, }) @@ -69,7 +70,7 @@ describe('Sveltekit with instrumentation and tracing', () => { }); it('adds the SDK dependency to package.json', () => { - checkPackageJson(projectDir, integration); + checkPackageJson(projectDir, '@sentry/sveltekit'); }); it('adds the .env.sentry-build-plugin', () => { diff --git a/e2e-tests/utils/index.ts b/e2e-tests/utils/index.ts index 535b6ffc7..7bcd3462a 100644 --- a/e2e-tests/utils/index.ts +++ b/e2e-tests/utils/index.ts @@ -7,6 +7,7 @@ import { spawn, execSync } from 'node:child_process'; import type { ChildProcess } from 'node:child_process'; import { dim, green, red } from '../../lib/Helper/Logging'; import { expect } from 'vitest'; +import { PackageDotJson } from '../../src/utils/package-json'; export const KEYS = { UP: '\u001b[A', @@ -116,7 +117,7 @@ export function createIsolatedTestEnv(testAppName: string): { return { projectDir, cleanup }; } -export class WizardTestEnv { +export class ProcessRunner { taskHandle: ChildProcess; constructor( @@ -135,32 +136,6 @@ export class WizardTestEnv { } } - sendStdin(input: string) { - this.taskHandle.stdin?.write(input); - } - - /** - * Sends the input and waits for the output. - * @returns a promise that resolves when the output was found - * @throws an error when the output was not found within the timeout - */ - sendStdinAndWaitForOutput( - input: string | string[], - output: string, - options?: { timeout?: number; optional?: boolean }, - ) { - const outputPromise = this.waitForOutput(output, options); - - if (Array.isArray(input)) { - for (const i of input) { - this.sendStdin(i); - } - } else { - this.sendStdin(input); - } - return outputPromise; - } - /** * Waits for the task to exit with a given `statusCode`. * @@ -271,71 +246,6 @@ export class WizardTestEnv { } } -/** - * Initialize a git repository in the given directory - * @param projectDir - */ -export function initGit(projectDir: string): void { - try { - execSync('git init', { cwd: projectDir }); - // Add all files to the git repo - execSync('git add -A', { cwd: projectDir }); - // Add author info to avoid git commit error - execSync('git config user.email test@test.sentry.io', { cwd: projectDir }); - execSync('git config user.name Test', { cwd: projectDir }); - execSync('git commit -m init', { cwd: projectDir }); - } catch (e) { - log.error('Error initializing git'); - log.error(e); - } -} - -/** - * Cleanup the git repository in the given directory - * - * Caution! Make sure `projectDir` is a test project directory, - * if in doubt, please commit your local non-test changes first! - * @param projectDir - */ -export function cleanupGit(projectDir: string): void { - try { - // Remove the .git directory - execSync(`rm -rf ${projectDir}/.git`); - } catch (e) { - log.error('Error cleaning up git'); - log.error(e); - } -} - -/** - * Revert local changes in the given directory - * - * Caution! Make sure `projectDir` is a test project directory, - * if in doubt, please commit your local non-test changes first! - * - * @param projectDir - */ -export function revertLocalChanges(projectDir: string): void { - try { - // Check if this is a git repository first - const isGitRepo = fs.existsSync(path.join(projectDir, '.git')); - - if (isGitRepo) { - // Revert tracked files - execSync('git restore .', { cwd: projectDir }); - // Revert untracked files - execSync('git clean -fd .', { cwd: projectDir }); - } - - // Remove node_modules and dist (.gitignore'd and therefore not removed via git clean) - execSync('rm -rf node_modules', { cwd: projectDir }); - execSync('rm -rf dist', { cwd: projectDir }); - } catch (e) { - log.error('Error reverting local changes'); - log.error(e); - } -} - export function getWizardCommand(integration: Integration): string { const binName = process.env.SENTRY_WIZARD_E2E_TEST_BIN ? ['dist-bin', `sentry-wizard-${process.platform}-${process.arch}`] @@ -360,47 +270,6 @@ export function getWizardCommand(integration: Integration): string { return `${binPath} ${args.join(' ')}`; } -/** - * Start the wizard instance with the given integration and project directory - * @param integration - * @param projectDir - * - * @returns WizardTestEnv - */ -export function startWizardInstance( - integration: Integration, - projectDir: string, - debug = false, - spotlight = false, -): WizardTestEnv { - const binName = process.env.SENTRY_WIZARD_E2E_TEST_BIN - ? ['dist-bin', `sentry-wizard-${process.platform}-${process.arch}`] - : ['dist', 'bin.js']; - const binPath = path.join(__dirname, '..', '..', ...binName); - - const args = ['--debug', '-i', integration]; - - if (spotlight) { - // Spotlight mode: skip authentication - args.push('--spotlight'); - } else { - args.push( - '--preSelectedProject.authToken', - TEST_ARGS.AUTH_TOKEN, - '--preSelectedProject.dsn', - TEST_ARGS.PROJECT_DSN, - '--preSelectedProject.orgSlug', - TEST_ARGS.ORG_SLUG, - '--preSelectedProject.projectSlug', - TEST_ARGS.PROJECT_SLUG, - ); - } - - args.push('--disable-telemetry'); - - return new WizardTestEnv(binPath, args, { cwd: projectDir, debug }); -} - /** * Create a file with the given content * @@ -486,57 +355,24 @@ export function checkFileDoesNotExist(filePath: string) { } /** - * Map integration to its corresponding Sentry package name - * @param type Integration type - * @returns Package name or undefined if no package exists - */ -function mapIntegrationToPackageName(type: string): string | undefined { - switch (type) { - case Integration.android: - return undefined; // Android doesn't have a JavaScript package - case Integration.reactNative: - return '@sentry/react-native'; - case Integration.flutter: - return undefined; // Flutter doesn't have a JavaScript package - case Integration.cordova: - return '@sentry/cordova'; - case Integration.angular: - return '@sentry/angular'; - case Integration.electron: - return '@sentry/electron'; - case Integration.nextjs: - return '@sentry/nextjs'; - case Integration.nuxt: - return '@sentry/nuxt'; - case Integration.remix: - return '@sentry/remix'; - case Integration.reactRouter: - return '@sentry/react-router'; - case Integration.sveltekit: - return '@sentry/sveltekit'; - case Integration.cloudflare: - return '@sentry/cloudflare'; - case Integration.sourcemaps: - return undefined; // Sourcemaps doesn't install a package - case Integration.ios: - return undefined; // iOS doesn't have a JavaScript package - default: - return undefined; - } -} - -/** - * Check if the package.json contains the given integration + * Check if the package.json lists the given package as a dependency or dev dependency * * @param projectDir * @param integration */ -export function checkPackageJson(projectDir: string, integration: Integration) { - const packageName = mapIntegrationToPackageName(integration); - if (!packageName) { - throw new Error(`No package name found for integration: ${integration}`); - } - checkFileContents(`${projectDir}/package.json`, packageName); +export function checkPackageJson( + projectDir: string, + packageName: string, + devDependency = false, +) { + const packageJson = fs.readFileSync(`${projectDir}/package.json`, 'utf-8'); + const packageJsonObject = JSON.parse(packageJson) as PackageDotJson; + + const packageVersion = + packageJsonObject.dependencies?.[packageName] || + (devDependency && packageJsonObject.devDependencies?.[packageName]); + + expect(packageVersion).toBeTruthy(); } /** @@ -579,11 +415,11 @@ export function checkSentryProperties(projectDir: string) { * @param projectDir */ export async function checkIfBuilds(projectDir: string) { - const testEnv = new WizardTestEnv('npm', ['run', 'build'], { + const npmRunner = new ProcessRunner('npm', ['run', 'build'], { cwd: projectDir, }); - const builtSuccessfully = await testEnv.waitForStatusCode(0, { + const builtSuccessfully = await npmRunner.waitForStatusCode(0, { timeout: 120_000, }); @@ -596,11 +432,11 @@ export async function checkIfBuilds(projectDir: string) { * @param projectDir */ export async function checkIfLints(projectDir: string) { - const testEnv = new WizardTestEnv('npm', ['run', 'lint'], { + const npmRunner = new ProcessRunner('npm', ['run', 'lint'], { cwd: projectDir, }); - const lintedSuccessfully = await testEnv.waitForStatusCode(0, { + const lintedSuccessfully = await npmRunner.waitForStatusCode(0, { timeout: 120_000, }); @@ -616,12 +452,12 @@ export async function checkIfFlutterBuilds( expectedOutput: string, debug = false, ) { - const testEnv = new WizardTestEnv('flutter', ['build', 'web'], { + const flutterRunner = new ProcessRunner('flutter', ['build', 'web'], { cwd: projectDir, debug: debug, }); - const outputReceived = await testEnv.waitForOutput(expectedOutput, { + const outputReceived = await flutterRunner.waitForOutput(expectedOutput, { timeout: 120_000, }); @@ -669,16 +505,16 @@ export async function checkIfReactNativeBundles( assetsDest, ]; - const testEnv = new WizardTestEnv('npx', bundleCommandArgs, { + const npxRunner = new ProcessRunner('npx', bundleCommandArgs, { cwd: projectDir, debug: debug, }); - const builtSuccessfully = await testEnv.waitForStatusCode(0, { + const builtSuccessfully = await npxRunner.waitForStatusCode(0, { timeout: 300_000, }); - testEnv.kill(); + npxRunner.kill(); return builtSuccessfully; } @@ -697,16 +533,16 @@ export async function checkIfExpoBundles( ): Promise { const exportCommandArgs = ['expo', 'export', '--platform', platform]; - const testEnv = new WizardTestEnv('npx', exportCommandArgs, { + const npxRunner = new ProcessRunner('npx', exportCommandArgs, { cwd: projectDir, debug: debug, }); - const builtSuccessfully = await testEnv.waitForStatusCode(0, { + const builtSuccessfully = await npxRunner.waitForStatusCode(0, { timeout: 300_000, }); - testEnv.kill(); + npxRunner.kill(); return builtSuccessfully; } @@ -719,15 +555,17 @@ export async function checkIfRunsOnDevMode( projectDir: string, expectedOutput: string, ) { - const testEnv = new WizardTestEnv('npm', ['run', 'dev'], { cwd: projectDir }); + const npmRunner = new ProcessRunner('npm', ['run', 'dev'], { + cwd: projectDir, + }); expect( - await testEnv.waitForOutput(expectedOutput, { + await npmRunner.waitForOutput(expectedOutput, { timeout: 120_000, }), ).toBe(true); - testEnv.kill(); + npmRunner.kill(); } /** @@ -740,15 +578,34 @@ export async function checkIfRunsOnProdMode( expectedOutput: string, startCommand = 'start', ) { - const testEnv = new WizardTestEnv('npm', ['run', startCommand], { + const npmRunner = new ProcessRunner('npm', ['run', startCommand], { cwd: projectDir, }); expect( - await testEnv.waitForOutput(expectedOutput, { + await npmRunner.waitForOutput(expectedOutput, { timeout: 120_000, }), ).toBe(true); - testEnv.kill(); + npmRunner.kill(); +} + +/** + * Initialize a git repository in the given directory + * @param projectDir + */ +function initGit(projectDir: string): void { + try { + execSync('git init', { cwd: projectDir }); + // Add all files to the git repo + execSync('git add -A', { cwd: projectDir }); + // Add author info to avoid git commit error + execSync('git config user.email test@test.sentry.io', { cwd: projectDir }); + execSync('git config user.name Test', { cwd: projectDir }); + execSync('git commit -m init', { cwd: projectDir }); + } catch (e) { + log.error('Error initializing git'); + log.error(e); + } }