Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/browser/src/integrations/breadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ function _consoleBreadcrumb(handlerData: HandlerData & { args: unknown[]; level:
/**
* Creates breadcrumbs from XHR API calls
*/
function _xhrBreadcrumb(handlerData: HandlerData & { xhr: SentryWrappedXMLHttpRequest }): void {
function _xhrBreadcrumb(handlerData: HandlerData & { xhr: XMLHttpRequest & SentryWrappedXMLHttpRequest }): void {
if (handlerData.endTimestamp) {
// We only capture complete, non-sentry requests
if (handlerData.xhr.__sentry_own_request__) {
Expand Down Expand Up @@ -248,7 +248,7 @@ function _xhrBreadcrumb(handlerData: HandlerData & { xhr: SentryWrappedXMLHttpRe
/**
* Creates breadcrumbs from fetch API calls
*/
function _fetchBreadcrumb(handlerData: HandlerData & HandlerDataFetch): void {
function _fetchBreadcrumb(handlerData: HandlerData & HandlerDataFetch & { response?: Response }): void {
// We only capture complete fetch requests
if (!handlerData.endTimestamp) {
return;
Expand Down
4 changes: 4 additions & 0 deletions packages/e2e-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ current state.

Prerequisites: Docker

- Copy `.env.example` to `.env`
- Fill in auth information in `.env` for an example Sentry project
- The `E2E_TEST_AUTH_TOKEN` must have all the default permissions

```bash
yarn test:e2e
```
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
2 changes: 2 additions & 0 deletions packages/e2e-tests/test-applications/node-express-app/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@sentry:registry=http://localhost:4873
@sentry-internal:registry=http://localhost:4873
23 changes: 23 additions & 0 deletions packages/e2e-tests/test-applications/node-express-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "node-express-app",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "yarn tsc",
"start": "node dist/app.js",
"test": "yarn playwright test"
},
"dependencies": {
"@sentry/integrations": "*",
"@sentry/node": "*",
"@sentry/tracing": "*",
"@sentry/types": "*",
"express": "4.18.2",
"@types/express": "4.17.17",
"@types/node": "18.15.1",
"typescript": "4.9.5"
},
"devDependencies": {
"@playwright/test": "^1.27.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';

/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './tests',
/* Maximum time one test can run for. */
timeout: 60 * 1000,
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:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
// For now we only test Chrome!
// {
// name: 'firefox',
// use: {
// ...devices['Desktop Firefox'],
// },
// },
// {
// name: 'webkit',
// use: {
// ...devices['Desktop Safari'],
// },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn start',
port: 3000,
},
};

export default config;
73 changes: 73 additions & 0 deletions packages/e2e-tests/test-applications/node-express-app/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as Sentry from '@sentry/node';
import '@sentry/tracing';
import * as Integrations from '@sentry/integrations';
import express from 'express';

declare global {
namespace globalThis {
var transactionIds: string[];
}
}

Sentry.init({
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
integrations: [new Integrations.HttpClient()],
debug: true,
tracesSampleRate: 1,
});

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

app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());

app.get('/test-success', function (req, res) {
res.send({ version: 'v1' });
});

app.get('/test-param/:param', function (req, res) {
res.send({ paramWas: req.params.param });
});

app.get('/test-transaction', async function (req, res) {
const transaction = Sentry.startTransaction({ name: 'test-transaction', op: 'e2e-test' });
Sentry.getCurrentHub().configureScope(scope => scope.setSpan(transaction));

const span = transaction.startChild();

span.finish();
transaction.finish();

await Sentry.flush();

res.send({
transactionIds: global.transactionIds || [],
});
});

app.get('/test-error', async function (req, res) {
const exceptionId = Sentry.captureException(new Error('This is an error'));

await Sentry.flush(2000);

res.send({ exceptionId });
});

app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});

Sentry.addGlobalEventProcessor(event => {
global.transactionIds = global.transactionIds || [];

if (event.type === 'transaction') {
const eventId = event.event_id;

if (eventId) {
global.transactionIds.push(eventId);
}
}

return event;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "../../test-recipe-schema.json",
"testApplicationName": "Node Express App",
"buildCommand": "yarn install && yarn build",
"tests": [
{
"testName": "Test express server",
"testCommand": "yarn test",
"timeoutSeconds": 60
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { test, expect } from '@playwright/test';
import axios, { AxiosError } from 'axios';

const authToken = process.env.E2E_TEST_AUTH_TOKEN;
const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT;
const EVENT_POLLING_TIMEOUT = 30_000;

test('Sends exception to Sentry', async ({ baseURL }) => {
const { data } = await axios.get(`${baseURL}/test-error`);
const { exceptionId } = data;

const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionId}/`;

console.log(`Polling for error eventId: ${exceptionId}`);

await expect
.poll(
async () => {
try {
const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } });

return response.status;
} catch (e) {
if (e instanceof AxiosError && e.response) {
if (e.response.status !== 404) {
throw e;
} else {
return e.response.status;
}
} else {
throw e;
}
}
},
{ timeout: EVENT_POLLING_TIMEOUT },
)
.toBe(200);
});

test('Sends transactions to Sentry', async ({ baseURL }) => {
const { data } = await axios.get(`${baseURL}/test-transaction`);
const { transactionIds } = data;

console.log(`Polling for transaction eventIds: ${JSON.stringify(transactionIds)}`);

expect(transactionIds.length).toBeGreaterThan(0);

await Promise.all(
transactionIds.map(async (transactionId: string) => {
const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionId}/`;

await expect
.poll(
async () => {
try {
const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } });

return response.status;
} catch (e) {
if (e instanceof AxiosError && e.response) {
if (e.response.status !== 404) {
throw e;
} else {
return e.response.status;
}
} else {
throw e;
}
}
},
{ timeout: EVENT_POLLING_TIMEOUT },
)
.toBe(200);
}),
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"types": ["node"],
"esModuleInterop": true,
"lib": ["ES6"],
"strict": true,
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}
Loading