Skip to content

Commit b724bb9

Browse files
authored
test: Support variations in projects on E2E tests. (#703)
1 parent 487827f commit b724bb9

File tree

4 files changed

+433
-130
lines changed

4 files changed

+433
-130
lines changed

e2e-tests/README.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# End-to-end Tests for Sentry Wizard
2+
3+
## Structure
4+
5+
```
6+
test-applications/
7+
|---- nextjs-test-app/
8+
|---- remix-test-app/
9+
|---- sveltekit-test-app/
10+
tests/
11+
|---- nextjs.test.ts
12+
|---- remix.test.ts
13+
|---- sveltekit.test.ts
14+
```
15+
16+
### Utilities
17+
18+
`utils/` contains helpers such as the wizard runner, assertion tools and file modifiers that can be used in (`*.test.ts`).
19+
20+
#### Helpers
21+
22+
- `startWizardInstance` - Starts a new instance of `WizardTestEnv`.
23+
24+
- `initGit` - Initializes a temporary git repository in the test project.
25+
- `cleanupGit` - Cleans up the temporary git repository in the test project.
26+
- `revertLocalChanges` - Reverts local changes (git tracked or untracked) in the test project.
27+
28+
- `createFile` - Creates a file (optionally with content) in the test project.
29+
- `modifyFile` - Modifies a file in the test project.
30+
31+
- `checkFileExists` - Checks if a file exists in the test project.
32+
- `checkPackageJson` - Checks if the `@sentry/[integration]` package exists in the dependencies of the test project's `package.json`.
33+
- `checkSentryCliConfig` - Checks if the `.sentryclirc` file contains the Sentry auth token.
34+
- `checkEnvBuildPlugin` - Cheks if `.env.sentry-build-plugin` contains the Sentry auth token.
35+
36+
- `checkIfBuilds` - Checks if the test project builds successfully.
37+
- `checkIfRunsOnDevMode` - Checks if the test project runs on dev mode successfully.
38+
- `checkIfRunsOnProdMode` - Checks if the test project runs on prod mode successfully.
39+
40+
41+
#### `WizardTestEnv`
42+
43+
`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.
44+
45+
## Running Tests Locally
46+
47+
First, you need to create a `.env` file set the environment variables from the `.env.example` file in the root of the project.
48+
49+
Tests can be run locally from the root of the project with:
50+
51+
`yarn test:e2e`
52+
53+
To run a specific test application
54+
55+
`yarn test:e2e [Remix | NextJS | SvelteKit]`
56+
57+
## Writing Tests
58+
59+
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.

e2e-tests/tests/remix.test.ts

+163-50
Original file line numberDiff line numberDiff line change
@@ -9,68 +9,122 @@ import {
99
checkIfRunsOnProdMode,
1010
checkPackageJson,
1111
cleanupGit,
12+
createFile,
1213
KEYS,
14+
modifyFile,
1315
revertLocalChanges,
1416
startWizardInstance,
1517
TEST_ARGS,
1618
} from '../utils';
1719
import * as path from 'path';
1820

19-
describe('Remix', () => {
20-
const integration = Integration.remix;
21-
const projectDir = path.resolve(
22-
__dirname,
23-
'../test-applications/remix-test-app',
24-
);
21+
const SERVER_TEMPLATE = `import { createRequestHandler } from '@remix-run/express';
22+
import { installGlobals } from '@remix-run/node';
23+
import compression from 'compression';
24+
import express from 'express';
25+
import morgan from 'morgan';
26+
27+
installGlobals();
28+
29+
const viteDevServer =
30+
process.env.NODE_ENV === 'production'
31+
? undefined
32+
: await import('vite').then(vite =>
33+
vite.createServer({
34+
server: { middlewareMode: true },
35+
}),
36+
);
37+
38+
const app = express();
39+
40+
app.use(compression());
41+
app.disable('x-powered-by');
42+
43+
if (viteDevServer) {
44+
app.use(viteDevServer.middlewares);
45+
} else {
46+
app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' }));
47+
}
48+
49+
app.use(express.static('build/client', { maxAge: '1h' }));
50+
app.use(morgan('tiny'));
51+
52+
app.all(
53+
'*',
54+
createRequestHandler({
55+
build: viteDevServer
56+
? () => viteDevServer.ssrLoadModule('virtual:remix/server-build')
57+
: await import('./build/server/index.js'),
58+
}),
59+
);
60+
61+
app.listen(0, () => console.log('Express server listening'));
62+
`;
2563

26-
beforeAll(async () => {
27-
const wizardInstance = startWizardInstance(integration, projectDir);
28-
const packageManagerPrompted = await wizardInstance.waitForOutput(
64+
65+
async function runWizardOnRemixProject(projectDir: string, integration: Integration, fileModificationFn?: (projectDir: string, integration: Integration) => unknown) {
66+
const wizardInstance = startWizardInstance(integration, projectDir);
67+
let packageManagerPrompted = false;
68+
69+
if (fileModificationFn) {
70+
fileModificationFn(projectDir, integration);
71+
72+
await wizardInstance.waitForOutput(
73+
'Do you want to continue anyway?',
74+
);
75+
76+
packageManagerPrompted = await wizardInstance.sendStdinAndWaitForOutput(
77+
[KEYS.ENTER],
2978
'Please select your package manager.',
3079
);
80+
} else {
3181

32-
const tracingOptionPrompted =
33-
packageManagerPrompted &&
34-
(await wizardInstance.sendStdinAndWaitForOutput(
35-
// Selecting `yarn` as the package manager
36-
[KEYS.DOWN, KEYS.ENTER],
37-
// "Do you want to enable Tracing", sometimes doesn't work as `Tracing` can be printed in bold.
38-
'to track the performance of your application?',
39-
{
40-
timeout: 240_000,
41-
},
42-
));
43-
44-
const replayOptionPrompted =
45-
tracingOptionPrompted &&
46-
(await wizardInstance.sendStdinAndWaitForOutput(
47-
[KEYS.ENTER],
48-
// "Do you want to enable Sentry Session Replay", sometimes doesn't work as `Sentry Session Replay` can be printed in bold.
49-
'to get a video-like reproduction of errors during a user session?',
50-
));
51-
52-
replayOptionPrompted &&
53-
(await wizardInstance.sendStdinAndWaitForOutput(
54-
[KEYS.ENTER],
55-
'Do you want to create an example page',
56-
{
57-
optional: true,
58-
},
59-
));
60-
61-
await wizardInstance.sendStdinAndWaitForOutput(
62-
[KEYS.ENTER, KEYS.ENTER],
63-
'Sentry has been successfully configured for your Remix project',
82+
packageManagerPrompted = await wizardInstance.waitForOutput(
83+
'Please select your package manager.',
6484
);
85+
}
6586

66-
wizardInstance.kill();
67-
});
87+
const tracingOptionPrompted =
88+
packageManagerPrompted &&
89+
(await wizardInstance.sendStdinAndWaitForOutput(
90+
// Selecting `yarn` as the package manager
91+
[KEYS.DOWN, KEYS.ENTER],
92+
// "Do you want to enable Tracing", sometimes doesn't work as `Tracing` can be printed in bold.
93+
'to track the performance of your application?',
94+
{
95+
timeout: 240_000,
96+
},
97+
));
6898

69-
afterAll(() => {
70-
revertLocalChanges(projectDir);
71-
cleanupGit(projectDir);
72-
});
99+
const replayOptionPrompted =
100+
tracingOptionPrompted &&
101+
(await wizardInstance.sendStdinAndWaitForOutput(
102+
[KEYS.ENTER],
103+
// "Do you want to enable Sentry Session Replay", sometimes doesn't work as `Sentry Session Replay` can be printed in bold.
104+
'to get a video-like reproduction of errors during a user session?',
105+
));
106+
107+
replayOptionPrompted &&
108+
(await wizardInstance.sendStdinAndWaitForOutput(
109+
[KEYS.ENTER],
110+
'Do you want to create an example page',
111+
{
112+
optional: true,
113+
},
114+
));
73115

116+
await wizardInstance.sendStdinAndWaitForOutput(
117+
[KEYS.ENTER, KEYS.ENTER],
118+
'Sentry has been successfully configured for your Remix project',
119+
);
120+
121+
wizardInstance.kill();
122+
};
123+
124+
function checkRemixProject(projectDir: string, integration: Integration, options?: {
125+
devModeExpectedOutput?: string;
126+
prodModeExpectedOutput?: string;
127+
}) {
74128
test('package.json is updated correctly', () => {
75129
checkPackageJson(projectDir, integration);
76130
});
@@ -140,15 +194,74 @@ describe('Remix', () => {
140194
]);
141195
});
142196

143-
test('builds correctly', async () => {
197+
test('builds successfully', async () => {
144198
await checkIfBuilds(projectDir, 'built');
145199
});
146200

147201
test('runs on dev mode correctly', async () => {
148-
await checkIfRunsOnDevMode(projectDir, 'to expose');
202+
await checkIfRunsOnDevMode(projectDir, options?.devModeExpectedOutput || 'to expose');
149203
});
150204

151205
test('runs on prod mode correctly', async () => {
152-
await checkIfRunsOnProdMode(projectDir, '[remix-serve]');
206+
await checkIfRunsOnProdMode(projectDir, options?.prodModeExpectedOutput || '[remix-serve]');
207+
});
208+
}
209+
210+
describe('Remix', () => {
211+
describe('with empty project', () => {
212+
const integration = Integration.remix;
213+
const projectDir = path.resolve(
214+
__dirname,
215+
'../test-applications/remix-test-app',
216+
);
217+
218+
beforeAll(async () => {
219+
await runWizardOnRemixProject(projectDir, integration);
220+
});
221+
222+
afterAll(() => {
223+
revertLocalChanges(projectDir);
224+
cleanupGit(projectDir);
225+
});
226+
227+
checkRemixProject(projectDir, integration);
228+
});
229+
230+
describe('with existing custom Express server', () => {
231+
const integration = Integration.remix;
232+
const projectDir = path.resolve(
233+
__dirname,
234+
'../test-applications/remix-test-app',
235+
);
236+
237+
beforeAll(async () => {
238+
await runWizardOnRemixProject(projectDir, integration, (projectDir) => {
239+
createFile(
240+
`${projectDir}/server.mjs`,
241+
SERVER_TEMPLATE,
242+
);
243+
244+
modifyFile(`${projectDir}/package.json`, {
245+
'"start": "remix-serve ./build/server/index.js"': '"start": "node ./server.mjs"',
246+
'"dev": "remix vite:dev"': '"dev": "node ./server.mjs"',
247+
});
248+
});
249+
});
250+
251+
afterAll(() => {
252+
revertLocalChanges(projectDir);
253+
cleanupGit(projectDir);
254+
});
255+
256+
checkRemixProject(projectDir, integration, {
257+
devModeExpectedOutput: 'Express server listening',
258+
prodModeExpectedOutput: 'Express server listening',
259+
});
260+
261+
test('server.mjs contains instrumentation file import', () => {
262+
checkFileContents(`${projectDir}/server.mjs`, [
263+
"import './instrumentation.server.mjs';",
264+
]);
265+
});
153266
});
154267
});

0 commit comments

Comments
 (0)