Skip to content

Commit

Permalink
test(node): Add test for errors-only ESM app (#12046)
Browse files Browse the repository at this point in the history
This tests a node app that uses ESM, but no `--import` flag.
Somehow this works for `http` (but not other packages...) but this is
fine for errors-only mode, for now.

Missing: We do show the warning for missing express instrumentation
there, still 😬 we may need to tweak this...
  • Loading branch information
mydea committed May 16, 2024
1 parent ef26423 commit 89377da
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,7 @@ jobs:
'create-remix-app-express-vite-dev',
'debug-id-sourcemaps',
'node-express-esm-loader',
'node-express-esm-without-loader',
'nextjs-app-dir',
'nextjs-14',
'react-create-hash-router',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@sentry:registry=http://127.0.0.1:4873
@sentry-internal:registry=http://127.0.0.1:4873
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "node-express-esm-without-loader",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node src/app.mjs",
"clean": "npx rimraf node_modules pnpm-lock.yaml",
"test:build": "pnpm install",
"test:assert": "playwright test"
},
"dependencies": {
"@sentry/node": "latest || *",
"@sentry/opentelemetry": "latest || *",
"express": "4.19.2"
},
"devDependencies": {
"@sentry-internal/event-proxy-server": "link:../../../event-proxy-server",
"@playwright/test": "^1.27.1"
},
"volta": {
"extends": "../../package.json",
"node": "18.19.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';

// Fix urls not resolving to localhost on Node v17+
// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575
import { setDefaultResultOrder } from 'dns';
setDefaultResultOrder('ipv4first');

const eventProxyPort = 3031;
const expressPort = 3030;

/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './tests',
/* Maximum time one test can run for. */
timeout: 150_000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000,
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: 0,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'list',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,

/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: `http://localhost:${expressPort}`,
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
],

/* Run your local dev server before starting the tests */
webServer: [
{
command: 'node start-event-proxy.mjs',
port: eventProxyPort,
stdout: 'pipe',
stderr: 'pipe',
},
{
command: 'pnpm start',
port: expressPort,
stdout: 'pipe',
stderr: 'pipe',
},
],
};

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import './instrument.mjs';

// Below other imports
import * as Sentry from '@sentry/node';
import express from 'express';

const app = express();
const port = 3030;

app.get('/test-success', function (req, res) {
setTimeout(() => {
res.status(200).end();
}, 100);
});

app.get('/test-params/:param', function (req, res) {
const { param } = req.params;
Sentry.setTag(`param-${param}`, 'yes');
Sentry.captureException(new Error(`Error for param ${param}`));

setTimeout(() => {
res.status(200).end();
}, 100);
});

app.get('/test-error', function (req, res) {
Sentry.captureException(new Error('This is an error'));
setTimeout(() => {
Sentry.flush(2000).then(() => {
res.status(200).end();
});
}, 100);
});

Sentry.setupExpressErrorHandler(app);

app.use(function onError(err, req, res, next) {
// The error id is attached to `res.sentry` to be returned
// and optionally displayed to the user for support.
res.statusCode = 500;
res.end(res.sentry + '\n');
});

app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as Sentry from '@sentry/node';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { startEventProxyServer } from '@sentry-internal/event-proxy-server';

startEventProxyServer({
port: 3031,
proxyServerName: 'node-express-esm-without-loader',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect, test } from '@playwright/test';
import { waitForError } from '@sentry-internal/event-proxy-server';

test('Should record exceptions captured inside handlers', async ({ request }) => {
const errorEventPromise = waitForError('node-express-esm-without-loader', errorEvent => {
return !!errorEvent?.exception?.values?.[0]?.value?.includes('This is an error');
});

await request.get('/test-error');

await expect(errorEventPromise).resolves.toBeDefined();
});

test('Isolates requests', async ({ request }) => {
const errorEventPromise = waitForError('node-express-esm-without-loader', errorEvent => {
return !!errorEvent?.exception?.values?.[0]?.value?.includes('Error for param 1');
});

const errorEventPromise2 = waitForError('node-express-esm-without-loader', errorEvent => {
return !!errorEvent?.exception?.values?.[0]?.value?.includes('Error for param 2');
});

await request.get('/test-params/1');
await request.get('/test-params/2');

const errorEvent1 = await errorEventPromise;
const errorEvent2 = await errorEventPromise2;

expect(errorEvent1.tags).toEqual({ 'param-1': 'yes' });
expect(errorEvent2.tags).toEqual({ 'param-2': 'yes' });

// Transaction is not set, since we have no expressIntegration by default
expect(errorEvent1.transaction).toBeUndefined();
expect(errorEvent2.transaction).toBeUndefined();
});

0 comments on commit 89377da

Please sign in to comment.