Skip to content

test(browser): Port builtin tests to playwright #11733

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 4 commits into from
Apr 23, 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
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://[email protected]/1337',
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from '@playwright/test';

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

sentryTest(
'Event listener instrumentation should attach the same event listener only once',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function clickHandler() {
throw new Error('event_listener_error');
}

window.addEventListener('click', clickHandler);

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

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

sentryTest('should capture built-in handlers fn name in mechanism data', 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: 'event_listener_error',
mechanism: {
type: 'instrument',
handled: false,
data: {
function: 'addEventListener',
handler: 'clickHandler',
target: 'EventTarget',
},
},
stacktrace: {
frames: expect.any(Array),
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// store references to original, unwrapped built-ins in order to make assertions re: wrapped functions
window.originalBuiltIns = {
addEventListener: document.addEventListener,
};

import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://[email protected]/1337',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const div = document.createElement('div');
document.body.appendChild(div);
window.capturedCall = false;
const captureFn = function () {
window.capturedCall = true;
};
// Use original addEventListener to simulate non-wrapped behavior (callback is attached without __sentry_wrapped__)
window.originalBuiltIns.addEventListener.call(div, 'click', captureFn);
// Then attach the same callback again, but with already wrapped method
div.addEventListener('click', captureFn);
div.removeEventListener('click', captureFn);
div.dispatchEvent(new MouseEvent('click'));
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect } from '@playwright/test';

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

sentryTest(
'should remove the original callback if it was registered before Sentry initialized (w. original method)',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);

const capturedCalled = await page.evaluate(() => {
// @ts-expect-error defined in subject.js
return window.capturedCall;
});

expect(capturedCalled).toBe(false);
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const div = document.createElement('div');
document.body.appendChild(div);
const fooFn = function () {
throw new Error('foo');
};
const barFn = function () {
throw new Error('bar');
};
div.addEventListener('click', fooFn);
div.addEventListener('click', barFn);
div.removeEventListener('click', barFn);
div.dispatchEvent(new MouseEvent('click'));
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 transparently remove event listeners from wrapped functions', 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: 'foo',
mechanism: {
type: 'instrument',
handled: false,
},
stacktrace: {
frames: expect.any(Array),
},
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from '@playwright/test';

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

sentryTest('Event listener instrumentation preserves "this" context', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

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

sentryTest(
'Event listener instrumentation should capture an error thrown in an event handler',
Expand All @@ -18,6 +18,11 @@ sentryTest(
mechanism: {
type: 'instrument',
handled: false,
data: {
function: 'addEventListener',
handler: '<anonymous>',
target: 'EventTarget',
},
},
stacktrace: {
frames: expect.any(Array),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from '@playwright/test';

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

sentryTest(
'Event listener instrumentation should not wrap event listeners multiple times',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect } from '@playwright/test';

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

sentryTest(
'wrapped callback should preserve correct context - window (not-bound)',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);

const { outsideCtx, requestAnimationFrameCtx } = (await page.evaluate(() => {
return new Promise(resolve => {
const outsideCtx = window as any;
requestAnimationFrame(function () {
// @ts-expect-error re-assigning this
resolve({ outsideCtx, requestAnimationFrameCtx: this as any });
});
});
})) as any;
expect(requestAnimationFrameCtx).toBe(outsideCtx);
},
);

sentryTest(
'wrapped callback should preserve correct context - `bind` bound method',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);

const requestAnimationFrameCtx = (await page.evaluate(() => {
return new Promise(resolve => {
function foo() {
// @ts-expect-error re-assigning this
resolve(this);
}

requestAnimationFrame(foo.bind({ magicNumber: 42 }));
});
})) as any;

expect(requestAnimationFrameCtx.magicNumber).toBe(42);
},
);
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://[email protected]/1337',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
requestAnimationFrame(function () {
throw new Error('requestAnimationFrame_error');
});
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 capture exceptions inside callback', 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: 'requestAnimationFrame_error',
mechanism: {
type: 'instrument',
handled: false,
},
stacktrace: {
frames: expect.any(Array),
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let exceptionInterval = setInterval(function () {
clearInterval(exceptionInterval);
throw new Error('setInterval_error');
}, 0);
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('Instrumentation should capture errors in setInterval', 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: 'setInterval_error',
mechanism: {
type: 'instrument',
handled: false,
},
stacktrace: {
frames: expect.any(Array),
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ sentryTest('Instrumentation should capture errors in setTimeout', async ({ getLo
mechanism: {
type: 'instrument',
handled: false,
data: {
function: 'setTimeout',
},
},
stacktrace: {
frames: expect.any(Array),
Expand Down
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://[email protected]/1337',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
window.calls = {};
const xhr = new XMLHttpRequest();
xhr.open('GET', 'test');
xhr.onreadystatechange = function wat() {
window.calls[xhr.readyState] = window.calls[xhr.readyState] ? window.calls[xhr.readyState] + 1 : 1;
};
xhr.send();
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect } from '@playwright/test';

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

sentryTest(
'should not call XMLHttpRequest onreadystatechange more than once per state',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);

const calls = await page.evaluate(() => {
// @ts-expect-error window.calls defined in subject.js
return window.calls;
});

expect(calls).toEqual({ '4': 1 });
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const xhr = new XMLHttpRequest();
xhr.open('GET', 'test');
// intentionally assign event handlers *after* open, since this is what jQuery does
xhr.onreadystatechange = function wat() {
// replace onreadystatechange with no-op so exception doesn't
// fire more than once as XHR changes loading state
xhr.onreadystatechange = function () {};
throw new Error('xhr_error');
};
xhr.send();
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/types';

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

sentryTest(
'should capture exceptions from XMLHttpRequest event handlers (e.g. onreadystatechange)',
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: 'xhr_error',
mechanism: {
type: 'instrument',
handled: false,
data: {
function: 'onreadystatechange',
},
},
stacktrace: {
frames: expect.any(Array),
},
});
},
);
Loading
Loading