Skip to content

Commit 459171d

Browse files
committed
test(nextjs): Migrate Next SDK's client side tests to Playwright.
1 parent e26e835 commit 459171d

37 files changed

+470
-585
lines changed

packages/integration-tests/utils/helpers.ts

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Page, Request } from '@playwright/test';
2-
import { Event, EventEnvelopeHeaders } from '@sentry/types';
2+
import { EnvelopeItemType, Event, EventEnvelopeHeaders } from '@sentry/types';
33

44
const envelopeUrlRegex = /\.sentry\.io\/api\/\d+\/envelope\//;
55

@@ -19,6 +19,58 @@ export const envelopeHeaderRequestParser = (request: Request | null): EventEnvel
1919
return envelope.split('\n').map(line => JSON.parse(line))[0];
2020
};
2121

22+
export const getEnvelopeType = (request: Request | null): EnvelopeItemType => {
23+
const envelope = request?.postData() || '';
24+
25+
return (envelope.split('\n').map(line => JSON.parse(line))[1] as Record<string, unknown>).type as EnvelopeItemType;
26+
};
27+
28+
export const countEnvelopes = async (
29+
page: Page,
30+
options?: {
31+
url?: string;
32+
timeout?: number;
33+
envelopeType: EnvelopeItemType | EnvelopeItemType[];
34+
},
35+
): Promise<number> => {
36+
const countPromise = new Promise<number>((resolve, reject) => {
37+
let reqCount = 0;
38+
39+
const requestHandler = (request: Request): void => {
40+
if (envelopeUrlRegex.test(request.url())) {
41+
try {
42+
if (options?.envelopeType) {
43+
const envelopeTypeArray = options
44+
? typeof options.envelopeType === 'string'
45+
? [options.envelopeType]
46+
: options.envelopeType || (['event'] as EnvelopeItemType[])
47+
: (['event'] as EnvelopeItemType[]);
48+
49+
if (envelopeTypeArray.includes(getEnvelopeType(request))) {
50+
reqCount++;
51+
}
52+
}
53+
} catch (e) {
54+
reject(e);
55+
}
56+
}
57+
};
58+
59+
page.on('request', requestHandler);
60+
61+
setTimeout(() => {
62+
page.off('request', requestHandler);
63+
resolve(reqCount);
64+
}, options?.timeout || 200);
65+
});
66+
67+
if (options?.url) {
68+
await page.goto(options.url);
69+
}
70+
71+
return countPromise;
72+
};
73+
2274
/**
2375
* Run script at the given path inside the test environment.
2476
*
@@ -51,24 +103,37 @@ async function getSentryEvents(page: Page, url?: string): Promise<Array<Event>>
51103
* If the timout option is configured, this function will abort waiting, even if it hasn't reveived the configured
52104
* amount of requests, and returns all the events recieved up to that point in time.
53105
*/
54-
async function getMultipleRequests(
106+
async function getMultipleRequests<T>(
55107
page: Page,
56108
count: number,
57109
urlRgx: RegExp,
58-
requestParser: (req: Request) => Event,
110+
requestParser: (req: Request) => T,
59111
options?: {
60112
url?: string;
61113
timeout?: number;
114+
envelopeType?: EnvelopeItemType | EnvelopeItemType[];
62115
},
63-
): Promise<Event[]> {
64-
const requests: Promise<Event[]> = new Promise((resolve, reject) => {
116+
): Promise<T[]> {
117+
const requests: Promise<T[]> = new Promise((resolve, reject) => {
65118
let reqCount = count;
66-
const requestData: Event[] = [];
119+
const requestData: T[] = [];
67120
let timeoutId: NodeJS.Timeout | undefined = undefined;
68121

69122
function requestHandler(request: Request): void {
70123
if (urlRgx.test(request.url())) {
71124
try {
125+
if (options?.envelopeType) {
126+
const envelopeTypeArray = options
127+
? typeof options.envelopeType === 'string'
128+
? [options.envelopeType]
129+
: options.envelopeType || (['event'] as EnvelopeItemType[])
130+
: (['event'] as EnvelopeItemType[]);
131+
132+
if (!envelopeTypeArray.includes(getEnvelopeType(request))) {
133+
return;
134+
}
135+
}
136+
72137
reqCount--;
73138
requestData.push(requestParser(request));
74139

@@ -110,11 +175,10 @@ async function getMultipleSentryEnvelopeRequests<T>(
110175
options?: {
111176
url?: string;
112177
timeout?: number;
178+
envelopeType?: EnvelopeItemType | EnvelopeItemType[];
113179
},
114180
requestParser: (req: Request) => T = envelopeRequestParser as (req: Request) => T,
115181
): Promise<T[]> {
116-
// TODO: This is not currently checking the type of envelope, just casting for now.
117-
// We can update this to include optional type-guarding when we have types for Envelope.
118182
return getMultipleRequests(page, count, envelopeUrlRegex, requestParser, options) as Promise<T[]>;
119183
}
120184

packages/nextjs/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@
6868
"test:build": "yarn ts-node test/buildProcess/runTest.ts",
6969
"test:unit": "jest",
7070
"test:integration": "test/run-integration-tests.sh && yarn test:types",
71+
"test:integration:ci": "run-s test:integration:clean test:integration:prepare test:integration:client:ci test:integration:server",
72+
"test:integration:prepare": "(cd test/integration && yarn && yarn build && yarn start)",
73+
"test:integration:clean": "(cd test/integration && rimraf .cache node_modules build)",
74+
"test:integration:client": "yarn playwright install-deps && yarn playwright test test/integration/test/client/",
75+
"test:integration:client:ci": "yarn test:integration:client --browser='all' --reporter='line'",
76+
"test:integration:server": "export NODE_OPTIONS='--stack-trace-limit=25' && jest --config=test/integration/jest.config.js test/integration/test/server/",
7177
"test:types": "cd test/types && yarn test",
7278
"test:watch": "jest --watch",
7379
"vercel:branch": "source vercel/set-up-branch-for-test-app-use.sh",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { PlaywrightTestConfig } from '@playwright/test';
2+
3+
const config: PlaywrightTestConfig = {
4+
retries: 2,
5+
timeout: 12000,
6+
use: {
7+
baseURL: 'http://localhost:3000',
8+
},
9+
workers: 3,
10+
webServer: {
11+
command: 'yarn test:integration:prepare',
12+
port: 3000,
13+
},
14+
};
15+
16+
export default config;

packages/nextjs/test/integration/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
"dev": "next",
66
"build": "next build",
77
"predebug": "source ../integration_test_utils.sh && link_monorepo_packages '../../..' && yarn build",
8-
"start": "next start"
8+
"start": "next start",
9+
"pretest": "run-s build",
10+
"test": "playwright test"
911
},
1012
"dependencies": {
1113
"@sentry/nextjs": "file:../../",
12-
"next": "latest",
14+
"next": "10.x",
1315
"react": "^17.0.1",
1416
"react-dom": "^17.0.1"
1517
},

packages/nextjs/test/integration/test/client.js

Lines changed: 0 additions & 41 deletions
This file was deleted.

packages/nextjs/test/integration/test/client/errorClick.js

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { getMultipleSentryEnvelopeRequests } from './utils/helpers';
2+
import { test, expect } from '@playwright/test';
3+
import { Event } from '@sentry/types';
4+
5+
test('should capture error triggered on click', async ({ page }) => {
6+
await page.goto('/errorClick');
7+
8+
const [_, events] = await Promise.all([
9+
page.click('button'),
10+
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'event' }),
11+
]);
12+
13+
expect(events[0].exception?.values?.[0]).toMatchObject({
14+
type: 'Error',
15+
value: 'Sentry Frontend Error',
16+
});
17+
});

packages/nextjs/test/integration/test/client/errorGlobal.js

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { getMultipleSentryEnvelopeRequests } from './utils/helpers';
2+
import { test, expect } from '@playwright/test';
3+
import { Event } from '@sentry/types';
4+
5+
test('should capture a globally triggered event', async ({ page }) => {
6+
const event = await getMultipleSentryEnvelopeRequests<Event>(page, 1, { url: '/crashed', envelopeType: 'event' });
7+
8+
expect(event[0].exception?.values?.[0]).toMatchObject({
9+
type: 'Error',
10+
value: 'Crashed',
11+
});
12+
});

packages/nextjs/test/integration/test/client/faultyAppGetInitialPropsConfiguration.js

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)