Skip to content

test(browser): Port unhandledrejection tests to playwright #11758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 24, 2024
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
35 changes: 0 additions & 35 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -768,40 +768,6 @@ jobs:
name: playwright-traces
path: dev-packages/browser-integration-tests/test-results

job_browser_integration_tests:
name: Browser (${{ matrix.browser }}) Tests
needs: [job_get_metadata, job_build]
if: needs.job_get_metadata.outputs.changed_browser == 'true' || github.event_name != 'pull_request'
runs-on: ubuntu-20.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
browser:
- ChromeHeadless
- FirefoxHeadless
- WebkitHeadless
steps:
- name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
uses: actions/checkout@v4
with:
ref: ${{ env.HEAD_COMMIT }}
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version-file: 'package.json'
- name: Restore caches
uses: ./.github/actions/restore-cache
env:
DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
- name: Run integration tests
env:
KARMA_BROWSER: ${{ matrix.browser }}
run: |
cd packages/browser
[[ $KARMA_BROWSER == WebkitHeadless ]] && yarn run playwright install-deps webkit
yarn test:integration

job_browser_build_tests:
name: Browser Build Tests
needs: [job_get_metadata, job_build]
Expand Down Expand Up @@ -1236,7 +1202,6 @@ jobs:
job_nextjs_integration_test,
job_node_integration_tests,
job_browser_playwright_tests,
job_browser_integration_tests,
job_browser_loader_tests,
job_remix_integration_tests,
job_e2e_tests,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function run() {
// this isn't how it happens in real life, in that the promise and reason
// values come from an actual PromiseRejectionEvent, but it's enough to test
// how the SDK handles the structure
window.dispatchEvent(
new CustomEvent('unhandledrejection', {
detail: {
promise: new Promise(function () {}),
// we're testing with an error here but it could be anything - really
// all we're testing is that it gets dug out correctly
reason: new Error('promiseError'),
},
}),
);
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

// something, somewhere, (likely a browser extension) effectively casts PromiseRejectionEvents
// to CustomEvents, moving the `promise` and `reason` attributes of the PRE into
// the CustomEvent's `detail` attribute, since they're not part of CustomEvent's spec
// see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent and
// https://github.com/getsentry/sentry-javascript/issues/2380
sentryTest(
'should capture PromiseRejectionEvent cast to CustomEvent with type unhandledrejection',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'Error',
value: 'promiseError',
mechanism: {
type: 'onunhandledrejection',
handled: false,
},
stacktrace: {
frames: expect.any(Array),
},
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function run() {
window.dispatchEvent(new Event('unhandledrejection'));
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

// there's no evidence that this actually happens, but it could, and our code correctly
// handles it, so might as well prevent future regression on that score
sentryTest('should capture a random Event with type unhandledrejection', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'Event',
value: 'Event `Event` (type=unhandledrejection) captured as promise rejection',
mechanism: {
type: 'onunhandledrejection',
handled: false,
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function run() {
const reason = new Error('promiseError');
const promise = Promise.reject(reason);
const event = new PromiseRejectionEvent('unhandledrejection', { promise, reason });
// simulate window.onunhandledrejection without generating a Script error
window.onunhandledrejection(event);
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

sentryTest('should catch thrown errors', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'Error',
value: 'promiseError',
mechanism: {
type: 'onunhandledrejection',
handled: false,
},
stacktrace: {
frames: expect.any(Array),
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function run() {
const reason = null;
const promise = Promise.reject(reason);
const event = new PromiseRejectionEvent('unhandledrejection', { promise, reason });
// simulate window.onunhandledrejection without generating a Script error
window.onunhandledrejection(event);
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

sentryTest('should catch thrown strings', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'UnhandledRejection',
value: 'Non-Error promise rejection captured with value: null',
mechanism: {
type: 'onunhandledrejection',
handled: false,
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function run() {
const reason = 123;
const promise = Promise.reject(reason);
const event = new PromiseRejectionEvent('unhandledrejection', { promise, reason });
// simulate window.onunhandledrejection without generating a Script error
window.onunhandledrejection(event);
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

sentryTest('should catch thrown strings', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'UnhandledRejection',
value: 'Non-Error promise rejection captured with value: 123',
mechanism: {
type: 'onunhandledrejection',
handled: false,
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function run() {
const reason = {
a: '1'.repeat('100'),
b: '2'.repeat('100'),
c: '3'.repeat('100'),
};
reason.d = reason.a;
reason.e = reason;
const promise = Promise.reject(reason);
const event = new PromiseRejectionEvent('unhandledrejection', { promise, reason });
// simulate window.onunhandledrejection without generating a Script error
window.onunhandledrejection(event);
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

sentryTest('should capture unhandledrejection with a complex object', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'UnhandledRejection',
value: 'Object captured as promise rejection with keys: a, b, c, d, e',
mechanism: {
type: 'onunhandledrejection',
handled: false,
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function run() {
const reason = { a: 'b', b: 'c', c: 'd' };
const promise = Promise.reject(reason);
const event = new PromiseRejectionEvent('unhandledrejection', { promise, reason });
// simulate window.onunhandledrejection without generating a Script error
window.onunhandledrejection(event);
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

sentryTest('should capture unhandledrejection with an object', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'UnhandledRejection',
value: 'Object captured as promise rejection with keys: a, b, c',
mechanism: {
type: 'onunhandledrejection',
handled: false,
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function run() {
const reason = 'stringError'.repeat(100);
const promise = Promise.reject(reason);
const event = new PromiseRejectionEvent('unhandledrejection', { promise, reason });
// simulate window.onunhandledrejection without generating a Script error
window.onunhandledrejection(event);
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

sentryTest('should capture unhandledrejection with a large string', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'UnhandledRejection',
value:
'Non-Error promise rejection captured with value: stringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstringErrorstr...',
mechanism: {
type: 'onunhandledrejection',
handled: false,
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function run() {
const reason = 'stringError';
const promise = Promise.reject(reason);
const event = new PromiseRejectionEvent('unhandledrejection', { promise, reason });
// simulate window.onunhandledrejection without generating a Script error
window.onunhandledrejection(event);
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

sentryTest('should catch thrown strings', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'UnhandledRejection',
value: 'Non-Error promise rejection captured with value: stringError',
mechanism: {
type: 'onunhandledrejection',
handled: false,
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function run() {
const reason = undefined;
const promise = Promise.reject(reason);
const event = new PromiseRejectionEvent('unhandledrejection', { promise, reason });
// simulate window.onunhandledrejection without generating a Script error
window.onunhandledrejection(event);
}

run();
Loading
Loading