Skip to content

Commit 3ad9fec

Browse files
committed
test: Next.js integration tests for Server and Browser
1 parent b7a4d1f commit 3ad9fec

20 files changed

+838
-1
lines changed

packages/nextjs/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,13 @@
5252
"fix": "run-s fix:eslint fix:prettier",
5353
"fix:eslint": "eslint . --format stylish --fix",
5454
"fix:prettier": "prettier --write \"{src,test}/**/*.ts\"",
55-
"test": "jest",
55+
"test": "run-s test:unit test:integration",
5656
"test:watch": "jest --watch",
57+
"test:unit": "jest",
58+
"test:integration": "run-s test:integration:build test:integration:server test:integration:browser",
59+
"test:integration:build": "cd test/integration && yarn && yarn build && cd ../..",
60+
"test:integration:server": "node test/integration/test/server.js",
61+
"test:integration:browser": "node test/integration/test/browser.js",
5762
"pack": "npm pack",
5863
"vercel:branch": "source vercel/set-up-branch-for-test-app-use.sh",
5964
"vercel:project": "source vercel/make-project-use-current-branch.sh"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"rules": {
3+
"no-console": "off",
4+
"guard-for-in": "off"
5+
}
6+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
const fs = require('fs').promises;
2+
const { createServer } = require('http');
3+
const { parse } = require('url');
4+
const path = require('path');
5+
6+
const next = require('next');
7+
const puppeteer = require('puppeteer');
8+
9+
const {
10+
isSentryRequest,
11+
isEventRequest,
12+
DEBUG_MODE,
13+
logDebug,
14+
extractEventFromRequest,
15+
extractEnvelopeFromRequest,
16+
isSessionRequest,
17+
isTransactionRequest,
18+
} = require('./utils');
19+
const { log } = console;
20+
21+
/**
22+
* Usage: node browser.js <filename>
23+
*
24+
* ENV Variables:
25+
* DEBUG=[bool] - enable requests logging
26+
* DEBUG_DEPTH=[int] - set logging depth
27+
*
28+
* Arguments:
29+
* <filename> - filter tests based on filename partial match
30+
*/
31+
32+
const FILES_FILTER = process.argv[2];
33+
34+
(async () => {
35+
let scenarios = await fs.readdir(path.resolve(__dirname, './browser'));
36+
37+
if (FILES_FILTER) {
38+
scenarios = scenarios.filter(file => file.toLowerCase().includes(FILES_FILTER));
39+
}
40+
41+
if (scenarios.length === 0) {
42+
console.log('No tests founds.');
43+
process.exit(0);
44+
} else {
45+
if (DEBUG_MODE) {
46+
scenarios.forEach(s => console.log(`⊙ Tests Found: ${s}`));
47+
}
48+
}
49+
50+
// Silence all the unnecessary server noise. We are capturing errors manualy anyway.
51+
if (!DEBUG_MODE) {
52+
console.log = () => {};
53+
console.error = () => {};
54+
}
55+
56+
const app = next({ dev: false, dir: path.resolve(__dirname, '..') });
57+
const handle = app.getRequestHandler();
58+
await app.prepare();
59+
const server = createServer((req, res) => handle(req, res, parse(req.url, true)));
60+
61+
const browser = await puppeteer.launch({
62+
devtools: false,
63+
});
64+
65+
const success = await new Promise(resolve => {
66+
server.listen(0, err => {
67+
if (err) throw err;
68+
69+
const cases = scenarios.map(async testCase => {
70+
const page = await browser.newPage();
71+
page.setDefaultTimeout(2000);
72+
73+
if (DEBUG_MODE) page.on('console', msg => logDebug(msg.text()));
74+
75+
await page.setRequestInterception(true);
76+
page.on('request', request => {
77+
if (
78+
isSentryRequest(request) ||
79+
// Used for testing http tracing
80+
request.url().includes('example.com')
81+
) {
82+
request.respond({
83+
status: 200,
84+
contentType: 'application/json',
85+
headers: {
86+
'Access-Control-Allow-Origin': '*',
87+
},
88+
});
89+
} else {
90+
request.continue();
91+
}
92+
93+
if (isEventRequest(request)) {
94+
if (DEBUG_MODE) logDebug('Intercepted Event', extractEventFromRequest(request));
95+
testInput.requests.events.push(request);
96+
}
97+
98+
if (isSessionRequest(request)) {
99+
if (DEBUG_MODE) logDebug('Intercepted Session', extractEnvelopeFromRequest(request));
100+
testInput.requests.sessions.push(request);
101+
}
102+
103+
if (isTransactionRequest(request)) {
104+
if (DEBUG_MODE) logDebug('Intercepted Transaction', extractEnvelopeFromRequest(request));
105+
testInput.requests.transactions.push(request);
106+
}
107+
});
108+
109+
// Capturing requests this way allows us to have a reproducible order,
110+
// where using `Promise.all([page.waitForRequest(isEventRequest), page.waitForRequest(isEventRequest)])` doesn't guarantee it.
111+
const testInput = {
112+
page,
113+
url: `http://localhost:${server.address().port}`,
114+
requests: {
115+
events: [],
116+
sessions: [],
117+
transactions: [],
118+
},
119+
};
120+
121+
try {
122+
await require(`./browser/${testCase}`)(testInput);
123+
log(`\x1b[32m✓ Test Succeded: ${testCase}\x1b[0m`);
124+
return true;
125+
} catch (error) {
126+
const testCaseFrames = error.stack.split('\n').filter(l => l.includes(testCase));
127+
const line = testCaseFrames[0].match(/.+:(\d+):/)[1];
128+
log(`\x1b[31mX Test Failed: ${testCase} (line: ${line})\x1b[0m\n${error.message}`);
129+
return false;
130+
}
131+
});
132+
133+
Promise.all(cases).then(result => {
134+
// Awaiting server being correctly closed and resolving promise in it's callback
135+
// adds ~4-5sec overhead for some reason. It should be safe to skip it though.
136+
server.close();
137+
resolve(result.every(Boolean));
138+
});
139+
});
140+
});
141+
142+
await browser.close();
143+
144+
if (success) {
145+
log(`\x1b[32m✓ All Tests Succeded\x1b[0m`);
146+
process.exit(0);
147+
} else {
148+
log(`\x1b[31mX Some Tests Failed\x1b[0m`);
149+
process.exit(1);
150+
}
151+
})();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const { expectRequestCount, waitForAll, isEventRequest, expectEvent } = require('../utils');
2+
3+
module.exports = async ({ page, url, requests }) => {
4+
await page.goto(`${url}/errorClick`);
5+
6+
await waitForAll([page.click('button'), page.waitForRequest(isEventRequest)]);
7+
8+
expectEvent(requests.events[0], {
9+
exception: {
10+
values: [
11+
{
12+
type: 'Error',
13+
value: 'Sentry Frontend Error',
14+
},
15+
],
16+
},
17+
});
18+
19+
await expectRequestCount(requests, { events: 1 });
20+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const { expectRequestCount, waitForAll, isEventRequest, expectEvent } = require('../utils');
2+
3+
module.exports = async ({ page, url, requests }) => {
4+
await waitForAll([page.goto(`${url}/crashed`), page.waitForRequest(isEventRequest)]);
5+
6+
expectEvent(requests.events[0], {
7+
exception: {
8+
values: [
9+
{
10+
type: 'Error',
11+
value: 'Crashed',
12+
},
13+
],
14+
},
15+
});
16+
17+
await expectRequestCount(requests, { events: 1 });
18+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const { expectRequestCount, waitForAll, isSessionRequest, expectSession } = require('../utils');
2+
3+
module.exports = async ({ page, url, requests }) => {
4+
await waitForAll([
5+
page.goto(`${url}/crashed`),
6+
page.waitForRequest(isSessionRequest),
7+
page.waitForRequest(isSessionRequest),
8+
]);
9+
10+
expectSession(requests.sessions[0], {
11+
init: true,
12+
status: 'ok',
13+
errors: 0,
14+
});
15+
16+
expectSession(requests.sessions[1], {
17+
init: false,
18+
status: 'crashed',
19+
errors: 1,
20+
});
21+
22+
await expectRequestCount(requests, { sessions: 2 });
23+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const { expectRequestCount, waitForAll, expectSession, isSessionRequest } = require('../utils');
2+
3+
module.exports = async ({ page, url, requests }) => {
4+
await waitForAll([page.goto(`${url}/healthy`), page.waitForRequest(isSessionRequest)]);
5+
6+
expectSession(requests.sessions[0], {
7+
init: true,
8+
status: 'ok',
9+
errors: 0,
10+
});
11+
12+
await expectRequestCount(requests, { sessions: 1 });
13+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const { expectRequestCount, waitForAll, isSessionRequest, expectSession } = require('../utils');
2+
3+
module.exports = async ({ page, url, requests }) => {
4+
await waitForAll([page.goto(`${url}/healthy`), page.waitForRequest(isSessionRequest)]);
5+
6+
expectSession(requests.sessions[0], {
7+
init: true,
8+
status: 'ok',
9+
errors: 0,
10+
});
11+
12+
await waitForAll([
13+
page.click('a#alsoHealthy'),
14+
page.waitForRequest(isSessionRequest),
15+
page.waitForRequest(isSessionRequest),
16+
]);
17+
18+
expectSession(requests.sessions[1], {
19+
init: false,
20+
status: 'exited',
21+
errors: 0,
22+
});
23+
24+
expectSession(requests.sessions[2], {
25+
init: true,
26+
status: 'ok',
27+
errors: 0,
28+
});
29+
30+
await waitForAll([
31+
page.click('a#healthy'),
32+
page.waitForRequest(isSessionRequest),
33+
page.waitForRequest(isSessionRequest),
34+
]);
35+
36+
expectSession(requests.sessions[3], {
37+
init: false,
38+
status: 'exited',
39+
errors: 0,
40+
});
41+
42+
expectSession(requests.sessions[4], {
43+
init: true,
44+
status: 'ok',
45+
errors: 0,
46+
});
47+
48+
await expectRequestCount(requests, { sessions: 5 });
49+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const { expectRequestCount, isTransactionRequest, expectTransaction } = require('../utils');
2+
3+
module.exports = async ({ page, url, requests }) => {
4+
await page.goto(`${url}/users/102`);
5+
await page.waitForRequest(isTransactionRequest);
6+
7+
expectTransaction(requests.transactions[0], {
8+
transaction: '/users/[id]',
9+
type: 'transaction',
10+
contexts: {
11+
trace: {
12+
op: 'pageload',
13+
},
14+
},
15+
});
16+
17+
await expectRequestCount(requests, { transactions: 1 });
18+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { expectRequestCount, expectTransaction, isTransactionRequest } = require('../utils');
2+
3+
module.exports = async ({ page, url, requests }) => {
4+
await page.goto(`${url}/fetch`);
5+
await page.click('button');
6+
await page.waitForRequest(isTransactionRequest);
7+
8+
expectTransaction(requests.transactions[0], {
9+
transaction: '/fetch',
10+
type: 'transaction',
11+
contexts: {
12+
trace: {
13+
op: 'pageload',
14+
},
15+
},
16+
spans: [
17+
{
18+
data: { method: 'GET', url: 'http://example.com', type: 'fetch' },
19+
description: 'GET http://example.com',
20+
op: 'http',
21+
},
22+
],
23+
});
24+
25+
await expectRequestCount(requests, { transactions: 1 });
26+
};

0 commit comments

Comments
 (0)