Skip to content

Commit cc61369

Browse files
AbhiPrasadLms24
andauthored
test(sveltekit): Add e2e build test for sveltekit (#7881)
Co-authored-by: Lukas Stracke <[email protected]>
1 parent 2b27989 commit cc61369

19 files changed

+580
-0
lines changed

packages/e2e-tests/lib/buildApp.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export async function buildApp(appDir: string, recipeInstance: RecipeInstance, e
4040
...env,
4141
...prefixObjectKeys(env, 'NEXT_PUBLIC_'),
4242
...prefixObjectKeys(env, 'REACT_APP_'),
43+
...prefixObjectKeys(env, 'PUBLIC_'),
4344
},
4445
});
4546

packages/e2e-tests/run.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ async function run(): Promise<void> {
1818
const envVarsToInject = {
1919
REACT_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
2020
NEXT_PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
21+
PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
2122
BASE_PORT: '27496', // just some random port
2223
};
2324

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.DS_Store
2+
node_modules
3+
/build
4+
/.svelte-kit
5+
/package
6+
.env
7+
.env.*
8+
!.env.example
9+
vite.config.js.timestamp-*
10+
vite.config.ts.timestamp-*
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://localhost:4873
2+
@sentry-internal:registry=http://localhost:4873
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# create-svelte
2+
3+
Everything you need to build a Svelte project, powered by
4+
[`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
5+
6+
## Creating a project
7+
8+
If you're seeing this, you've probably already done this step. Congrats!
9+
10+
```bash
11+
# create a new project in the current directory
12+
npm create svelte@latest
13+
14+
# create a new project in my-app
15+
npm create svelte@latest my-app
16+
```
17+
18+
## Developing
19+
20+
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a
21+
development server:
22+
23+
```bash
24+
npm run dev
25+
26+
# or start the server and open the app in a new browser tab
27+
npm run dev -- --open
28+
```
29+
30+
## Building
31+
32+
To create a production version of your app:
33+
34+
```bash
35+
npm run build
36+
```
37+
38+
You can preview the production build with `npm run preview`.
39+
40+
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target
41+
> environment.
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
// This is vendored from packages/e2e-tests/test-utils/event-proxy-server.ts
2+
// This is done because otherwise we were getting `ERR_MODULE_NOT_FOUND`:
3+
//
4+
// CustomError: Cannot find module '/sentry-javascript/packages/e2e-tests/test-utils/event-proxy-server' imported from /sentry-javascript/packages/e2e-tests/test-applications/sveltekit/start-event-proxy.ts
5+
// at finalizeResolution (/sentry-javascript/packages/e2e-tests/test-applications/sveltekit/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:366:11)
6+
// at moduleResolve (/sentry-javascript/packages/e2e-tests/test-applications/sveltekit/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:801:10)
7+
// at Object.defaultResolve (/sentry-javascript/packages/e2e-tests/test-applications/sveltekit/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:912:11)
8+
// at /sentry-javascript/packages/e2e-tests/test-applications/sveltekit/node_modules/ts-node/src/esm.ts:218:35
9+
// at entrypointFallback (/sentry-javascript/packages/e2e-tests/test-applications/sveltekit/node_modules/ts-node/src/esm.ts:168:34)
10+
// at /sentry-javascript/packages/e2e-tests/test-applications/sveltekit/node_modules/ts-node/src/esm.ts:217:14
11+
// at addShortCircuitFlag (/sentry-javascript/packages/e2e-tests/test-applications/sveltekit/node_modules/ts-node/src/esm.ts:409:21)
12+
// at resolve (/sentry-javascript/packages/e2e-tests/test-applications/sveltekit/node_modules/ts-node/src/esm.ts:197:12)
13+
// at resolve (/sentry-javascript/packages/e2e-tests/test-applications/sveltekit/node_modules/ts-node/src/child/child-loader.ts:15:39)
14+
// at nextResolve (node:internal/modules/esm/loader:163:28)
15+
16+
import type { Envelope, EnvelopeItem, Event } from '@sentry/types';
17+
import { parseEnvelope } from '@sentry/utils';
18+
import * as fs from 'fs';
19+
import * as http from 'http';
20+
import * as https from 'https';
21+
import type { AddressInfo } from 'net';
22+
import * as os from 'os';
23+
import * as path from 'path';
24+
import * as util from 'util';
25+
26+
const readFile = util.promisify(fs.readFile);
27+
const writeFile = util.promisify(fs.writeFile);
28+
29+
interface EventProxyServerOptions {
30+
/** Port to start the event proxy server at. */
31+
port: number;
32+
/** The name for the proxy server used for referencing it with listener functions */
33+
proxyServerName: string;
34+
}
35+
36+
interface SentryRequestCallbackData {
37+
envelope: Envelope;
38+
rawProxyRequestBody: string;
39+
rawSentryResponseBody: string;
40+
sentryResponseStatusCode?: number;
41+
}
42+
43+
/**
44+
* Starts an event proxy server that will proxy events to sentry when the `tunnel` option is used. Point the `tunnel`
45+
* option to this server (like this `tunnel: http://localhost:${port option}/`).
46+
*/
47+
export async function startEventProxyServer(options: EventProxyServerOptions): Promise<void> {
48+
const eventCallbackListeners: Set<(data: string) => void> = new Set();
49+
50+
const proxyServer = http.createServer((proxyRequest, proxyResponse) => {
51+
const proxyRequestChunks: Uint8Array[] = [];
52+
53+
proxyRequest.addListener('data', (chunk: Buffer) => {
54+
proxyRequestChunks.push(chunk);
55+
});
56+
57+
proxyRequest.addListener('error', err => {
58+
throw err;
59+
});
60+
61+
proxyRequest.addListener('end', () => {
62+
const proxyRequestBody = Buffer.concat(proxyRequestChunks).toString();
63+
const envelopeHeader: { dsn?: string } = JSON.parse(proxyRequestBody.split('\n')[0]);
64+
65+
if (!envelopeHeader.dsn) {
66+
throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.');
67+
}
68+
69+
const { origin, pathname, host } = new URL(envelopeHeader.dsn);
70+
71+
const projectId = pathname.substring(1);
72+
const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`;
73+
74+
proxyRequest.headers.host = host;
75+
76+
const sentryResponseChunks: Uint8Array[] = [];
77+
78+
const sentryRequest = https.request(
79+
sentryIngestUrl,
80+
{ headers: proxyRequest.headers, method: proxyRequest.method },
81+
sentryResponse => {
82+
sentryResponse.addListener('data', (chunk: Buffer) => {
83+
proxyResponse.write(chunk, 'binary');
84+
sentryResponseChunks.push(chunk);
85+
});
86+
87+
sentryResponse.addListener('end', () => {
88+
eventCallbackListeners.forEach(listener => {
89+
const rawProxyRequestBody = Buffer.concat(proxyRequestChunks).toString();
90+
const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString();
91+
92+
const data: SentryRequestCallbackData = {
93+
envelope: parseEnvelope(rawProxyRequestBody, new TextEncoder(), new TextDecoder()),
94+
rawProxyRequestBody,
95+
rawSentryResponseBody,
96+
sentryResponseStatusCode: sentryResponse.statusCode,
97+
};
98+
99+
listener(Buffer.from(JSON.stringify(data)).toString('base64'));
100+
});
101+
proxyResponse.end();
102+
});
103+
104+
sentryResponse.addListener('error', err => {
105+
throw err;
106+
});
107+
108+
proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers);
109+
},
110+
);
111+
112+
sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary');
113+
sentryRequest.end();
114+
});
115+
});
116+
117+
const proxyServerStartupPromise = new Promise<void>(resolve => {
118+
proxyServer.listen(options.port, () => {
119+
resolve();
120+
});
121+
});
122+
123+
const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => {
124+
eventCallbackResponse.statusCode = 200;
125+
eventCallbackResponse.setHeader('connection', 'keep-alive');
126+
127+
const callbackListener = (data: string): void => {
128+
eventCallbackResponse.write(data.concat('\n'), 'utf8');
129+
};
130+
131+
eventCallbackListeners.add(callbackListener);
132+
133+
eventCallbackRequest.on('close', () => {
134+
eventCallbackListeners.delete(callbackListener);
135+
});
136+
137+
eventCallbackRequest.on('error', () => {
138+
eventCallbackListeners.delete(callbackListener);
139+
});
140+
});
141+
142+
const eventCallbackServerStartupPromise = new Promise<void>(resolve => {
143+
eventCallbackServer.listen(0, () => {
144+
const port = String((eventCallbackServer.address() as AddressInfo).port);
145+
void registerCallbackServerPort(options.proxyServerName, port).then(resolve);
146+
});
147+
});
148+
149+
await eventCallbackServerStartupPromise;
150+
await proxyServerStartupPromise;
151+
return;
152+
}
153+
154+
export async function waitForRequest(
155+
proxyServerName: string,
156+
callback: (eventData: SentryRequestCallbackData) => Promise<boolean> | boolean,
157+
): Promise<SentryRequestCallbackData> {
158+
const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName);
159+
160+
return new Promise<SentryRequestCallbackData>((resolve, reject) => {
161+
const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => {
162+
let eventContents = '';
163+
164+
response.on('error', err => {
165+
reject(err);
166+
});
167+
168+
response.on('data', (chunk: Buffer) => {
169+
const chunkString = chunk.toString('utf8');
170+
chunkString.split('').forEach(char => {
171+
if (char === '\n') {
172+
const eventCallbackData: SentryRequestCallbackData = JSON.parse(
173+
Buffer.from(eventContents, 'base64').toString('utf8'),
174+
);
175+
const callbackResult = callback(eventCallbackData);
176+
if (typeof callbackResult !== 'boolean') {
177+
callbackResult.then(
178+
match => {
179+
if (match) {
180+
response.destroy();
181+
resolve(eventCallbackData);
182+
}
183+
},
184+
err => {
185+
throw err;
186+
},
187+
);
188+
} else if (callbackResult) {
189+
response.destroy();
190+
resolve(eventCallbackData);
191+
}
192+
eventContents = '';
193+
} else {
194+
eventContents = eventContents.concat(char);
195+
}
196+
});
197+
});
198+
});
199+
200+
request.end();
201+
});
202+
}
203+
204+
export function waitForEnvelopeItem(
205+
proxyServerName: string,
206+
callback: (envelopeItem: EnvelopeItem) => Promise<boolean> | boolean,
207+
): Promise<EnvelopeItem> {
208+
return new Promise((resolve, reject) => {
209+
waitForRequest(proxyServerName, async eventData => {
210+
const envelopeItems = eventData.envelope[1];
211+
for (const envelopeItem of envelopeItems) {
212+
if (await callback(envelopeItem)) {
213+
resolve(envelopeItem);
214+
return true;
215+
}
216+
}
217+
return false;
218+
}).catch(reject);
219+
});
220+
}
221+
222+
export function waitForError(
223+
proxyServerName: string,
224+
callback: (transactionEvent: Event) => Promise<boolean> | boolean,
225+
): Promise<Event> {
226+
return new Promise((resolve, reject) => {
227+
waitForEnvelopeItem(proxyServerName, async envelopeItem => {
228+
const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
229+
if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) {
230+
resolve(envelopeItemBody as Event);
231+
return true;
232+
}
233+
return false;
234+
}).catch(reject);
235+
});
236+
}
237+
238+
export function waitForTransaction(
239+
proxyServerName: string,
240+
callback: (transactionEvent: Event) => Promise<boolean> | boolean,
241+
): Promise<Event> {
242+
return new Promise((resolve, reject) => {
243+
waitForEnvelopeItem(proxyServerName, async envelopeItem => {
244+
const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
245+
if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) {
246+
resolve(envelopeItemBody as Event);
247+
return true;
248+
}
249+
return false;
250+
}).catch(reject);
251+
});
252+
}
253+
254+
const TEMP_FILE_PREFIX = 'event-proxy-server-';
255+
256+
async function registerCallbackServerPort(serverName: string, port: string): Promise<void> {
257+
const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
258+
await writeFile(tmpFilePath, port, { encoding: 'utf8' });
259+
}
260+
261+
async function retrieveCallbackServerPort(serverName: string): Promise<string> {
262+
const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
263+
return await readFile(tmpFilePath, 'utf8');
264+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "sveltekit",
3+
"version": "0.0.1",
4+
"private": true,
5+
"scripts": {
6+
"dev": "vite dev",
7+
"build": "vite build",
8+
"preview": "vite preview",
9+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
10+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
11+
"test:prod": "TEST_ENV=production playwright test",
12+
"test:dev": "TEST_ENV=development playwright test"
13+
},
14+
"dependencies": {
15+
"@sentry/sveltekit": "*"
16+
},
17+
"devDependencies": {
18+
"@playwright/test": "^1.27.1",
19+
"@sveltejs/adapter-auto": "^2.0.0",
20+
"@sveltejs/adapter-node": "^1.2.4",
21+
"@sveltejs/kit": "^1.5.0",
22+
"svelte": "^3.54.0",
23+
"svelte-check": "^3.0.1",
24+
"ts-node": "10.9.1",
25+
"tslib": "^2.4.1",
26+
"typescript": "^5.0.0",
27+
"vite": "^4.2.0",
28+
"wait-port": "1.0.4"
29+
},
30+
"type": "module"
31+
}

0 commit comments

Comments
 (0)