Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions packages/browser/src/integrations/breadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export class Breadcrumbs implements Integration {
{
event: handlerData.event,
name: handlerData.name,
global: handlerData.global,
},
);
}
Expand Down
154 changes: 152 additions & 2 deletions packages/browser/test/integration/suites/breadcrumbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,15 +446,14 @@ describe("breadcrumbs", function() {
});
});

it("should bail out if accessing the `type` and `target` properties of an event throw an exception", function() {
it("should bail out if accessing the `target` property of an event throws an exception", function() {
// see: https://github.com/getsentry/sentry-javascript/issues/768
return runInSandbox(sandbox, function() {
// click <input/>
var click = new MouseEvent("click");
function kaboom() {
throw new Error("lol");
}
Object.defineProperty(click, "type", { get: kaboom });
Object.defineProperty(click, "target", { get: kaboom });

var input = document.querySelector(".a"); // leaf node
Expand Down Expand Up @@ -500,6 +499,121 @@ describe("breadcrumbs", function() {
});
});

it("should correctly capture multiple consecutive breadcrumbs if they are of different type", function() {
return runInSandbox(sandbox, function() {
var input = document.getElementsByTagName("input")[0];

var clickHandler = function() {};
input.addEventListener("click", clickHandler);
var keypressHandler = function() {};
input.addEventListener("keypress", keypressHandler);

input.dispatchEvent(new MouseEvent("click"));
input.dispatchEvent(new KeyboardEvent("keypress"));

Sentry.captureMessage("test");
}).then(function(summary) {
if (IS_LOADER) {
// The async loader doesn't wrap event listeners, but we should receive the event without breadcrumbs
assert.lengthOf(summary.events, 1);
} else {
// Breadcrumb should be captured by the global event listeners, not a specific one
assert.equal(summary.breadcrumbs.length, 2);
assert.equal(summary.breadcrumbs[0].category, "ui.click");
assert.equal(
summary.breadcrumbs[0].message,
'body > form#foo-form > input[name="foo"]'
);
assert.equal(summary.breadcrumbs[1].category, "ui.input");
assert.equal(
summary.breadcrumbs[0].message,
'body > form#foo-form > input[name="foo"]'
);
assert.equal(summary.breadcrumbHints[0].global, false);
assert.equal(summary.breadcrumbHints[1].global, false);
assert.isUndefined(summary.events[0].exception);
}
});
});

it("should debounce multiple consecutive identical breadcrumbs but allow for switching to a different type", function() {
return runInSandbox(sandbox, function() {
var input = document.getElementsByTagName("input")[0];

var clickHandler = function() {};
input.addEventListener("click", clickHandler);
var keypressHandler = function() {};
input.addEventListener("keypress", keypressHandler);

input.dispatchEvent(new MouseEvent("click"));
input.dispatchEvent(new MouseEvent("click"));
input.dispatchEvent(new MouseEvent("click"));
input.dispatchEvent(new KeyboardEvent("keypress"));
input.dispatchEvent(new KeyboardEvent("keypress"));
input.dispatchEvent(new KeyboardEvent("keypress"));

Sentry.captureMessage("test");
}).then(function(summary) {
if (IS_LOADER) {
// The async loader doesn't wrap event listeners, but we should receive the event without breadcrumbs
assert.lengthOf(summary.events, 1);
} else {
// Breadcrumb should be captured by the global event listeners, not a specific one
assert.equal(summary.breadcrumbs.length, 2);
assert.equal(summary.breadcrumbs[0].category, "ui.click");
assert.equal(
summary.breadcrumbs[0].message,
'body > form#foo-form > input[name="foo"]'
);
assert.equal(summary.breadcrumbs[1].category, "ui.input");
assert.equal(
summary.breadcrumbs[0].message,
'body > form#foo-form > input[name="foo"]'
);
assert.equal(summary.breadcrumbHints[0].global, false);
assert.equal(summary.breadcrumbHints[1].global, false);
assert.isUndefined(summary.events[0].exception);
}
});
});

it("should debounce multiple consecutive identical breadcrumbs but allow for switching to a different target", function() {
return runInSandbox(sandbox, function() {
var input = document.querySelector("#foo-form input");
var div = document.querySelector("#foo-form div");

var clickHandler = function() {};
input.addEventListener("click", clickHandler);
div.addEventListener("click", clickHandler);

input.dispatchEvent(new MouseEvent("click"));
div.dispatchEvent(new MouseEvent("click"));

Sentry.captureMessage("test");
}).then(function(summary) {
if (IS_LOADER) {
// The async loader doesn't wrap event listeners, but we should receive the event without breadcrumbs
assert.lengthOf(summary.events, 1);
} else {
// Breadcrumb should be captured by the global event listeners, not a specific one
assert.equal(summary.breadcrumbs.length, 2);
assert.equal(summary.breadcrumbs[0].category, "ui.click");
assert.equal(
summary.breadcrumbs[0].message,
'body > form#foo-form > input[name="foo"]'
);
assert.equal(summary.breadcrumbs[1].category, "ui.click");
assert.equal(
summary.breadcrumbs[1].message,
"body > form#foo-form > div.contenteditable"
);
assert.equal(summary.breadcrumbHints[0].global, false);
assert.equal(summary.breadcrumbHints[1].global, false);
assert.isUndefined(summary.events[0].exception);
}
});
});

it(
optional(
"should flush keypress breadcrumbs when an error is thrown",
Expand Down Expand Up @@ -659,6 +773,42 @@ describe("breadcrumbs", function() {
});
});

it("should remove breadcrumb instrumentation when all event listeners are detached", function() {
return runInSandbox(sandbox, function() {
var input = document.getElementsByTagName("input")[0];

var clickHandler = function() {};
var otherClickHandler = function() {};
input.addEventListener("click", clickHandler);
input.addEventListener("click", otherClickHandler);
input.removeEventListener("click", clickHandler);
input.removeEventListener("click", otherClickHandler);

var keypressHandler = function() {};
var otherKeypressHandler = function() {};
input.addEventListener("keypress", keypressHandler);
input.addEventListener("keypress", otherKeypressHandler);
input.removeEventListener("keypress", keypressHandler);
input.removeEventListener("keypress", otherKeypressHandler);

input.dispatchEvent(new MouseEvent("click"));
input.dispatchEvent(new KeyboardEvent("keypress"));

Sentry.captureMessage("test");
}).then(function(summary) {
if (IS_LOADER) {
// The async loader doesn't wrap event listeners, but we should receive the event without breadcrumbs
assert.lengthOf(summary.events, 1);
} else {
// Breadcrumb should be captured by the global event listeners, not a specific one
assert.equal(summary.breadcrumbs.length, 2);
assert.equal(summary.breadcrumbHints[0].global, true);
assert.equal(summary.breadcrumbHints[1].global, true);
assert.isUndefined(summary.events[0].exception);
}
});
});

it(
optional(
"should record history.[pushState|replaceState] changes as navigation breadcrumbs",
Expand Down
Loading