feat(core): Add data-sentry-label support in htmlTreeAsString
Summary
This PR adds support for the data-sentry-label attribute in htmlTreeAsString function, allowing developers to annotate DOM elements with stable identifiers for better breadcrumb and INP span identification.
This is a partial solution to https://github.com/getsentry/sentry/issues/104128.
Changes
- Added
data-sentry-labelattribute detection in_htmlElementAsString()- takes priority overdata-sentry-componentanddata-sentry-element - Added
_getSentryLabel()helper function that traverses up to 15 DOM levels to find the nearestdata-sentry-labelattribute - If
data-sentry-labelis found on an ancestor (beyond the standard 5-level traversal), it prefixes the CSS selector:[data-sentry-label="ProductCard"] div.container > button.btn - Added comprehensive unit tests for the new functionality
Why this helps
In React Native Web and other frameworks with long/generated CSS class names, the current 80-character limit causes selectors to be truncated immediately, producing highly ambiguous selectors that can match 100+ elements on a page.
With data-sentry-label, developers can annotate interactive elements with stable identifiers:
<div data-sentry-label="Product-ItemRow">
<div class="css-175oi2r r-1awozwy r-18u37iz r-1wtj0ep">
<div class="css-175oi2r r-1awozwy r-6koalj r-18u37iz">
<div class="css-1jxf684 r-bcqeeo r-1ttztb7 r-qvutc0 r-poiln3">
Item Title
</div>
</div>
</div>
</div>
Before (current behavior):
div.css-1jxf684.r-bcqeeo.r-1ttztb7.r-qvutc0.r-poiln3
This selector hits the 80-character limit immediately. If these classes represent common text styling, the selector matches every text element with that styling across the entire application.
After (with this PR):
[data-sentry-label="Product-ItemRow"] div.css-1jxf684.r-bcqeeo.r-1ttztb7.r-qvutc0.r-poiln3
The label prefix identifies which specific row the interaction occurred in.
This significantly improves the precision and usefulness of generated CSS selectors in INP spans.
Checklist
Before submitting a pull request, please take a look at our Contributing guidelines and verify:
- [x] If you've added code that should be tested, please add tests.
- [x] Ensure your code lints and the test suite passes (
yarn lint) & (yarn test).
We have a solution for this already: https://docs.sentry.io/platforms/javascript/guides/react/features/component-names/
You can set data-sentry-component to be the component name, and optionally data-sentry-source-file to be the source file. The component name should be included in the selector.
<div
data-sentry-component="MyAwesomeComponent"
data-sentry-source-file="myAwesomeComponent.jsx"
>
This is a really cool and awesome component!
</div>
If you're using React Native, this is bundled in by default: https://docs.sentry.io/platforms/react-native/integrations/component-names/.
const { getDefaultConfig } = require("@react-native/metro-config");
const { withSentryConfig } = require("@sentry/react-native/metro");
const config = getDefaultConfig(__dirname);
module.exports = withSentryConfig(config, {
annotateReactComponents: true,
});
I started proposing this solution because I thought Sentry rarely marked elements with its own attributes.
When I checked today, it turned out that these attributes are from a different library; Sentry attributes don't work for us at all.
We use webpack/babel to build React Native for the web, and adding the following code doesn't change anything in our HTML:
sentryWebpackPlugin({
reactComponentAnnotation: {
enabled: true,
},
})
I will try to find out why this configuration does not work for us and will get back to you with information under this PR.
I've added a patch to React Native Web that extends forwardPropsList with:
+ // Sentry props
+ dataSentryComponent: true,
+ dataSentryElement: true,
+ dataSentrySourceFile: true
and now adding attributes to HTML works, but unfortunately they don't alleviate the problem.
They add information about generic components, such as GenericPressable and OfflineWithFeedback, which are used for every button and container in our app.
Do you have any ideas on how we can improve the behavior regarding which attribute values are added to HTML?
Do you have any ideas on how we can improve the behavior regarding which attribute values are added to HTML?
Is the issue that you want to control what components get the attribute value? Can you set custom one's yourself?
Or is it that for some components the injected component name is too generic so you actually lose information by enabling reactComponentAnnotation.
Is the issue that you want to control what components get the attribute value? Can you set custom one's yourself?
I'd like Sentry to have an element handler for the INP metric that narrows down what was actually clicked, rather than a selector that points to every button in our app. It can be automatically generated or configured by the developer.
Or is it that for some components the injected component name is too generic so you actually lose information by enabling reactComponentAnnotation.
This is also a problem.
I've now reviewed the React component structure, and there doesn't seem to be any way to make the reactComponentAnnotation solution work well there.
Furthermore, even if I create a wrapper around GenericPressable called ViewDetailsPressable, reactComponentAnnotation will still take on the GenericPressable for data-sentry-component.
Instead of using reactComponentAnnotation, could you set the data-sentry-component value yourself?
Instead of using reactComponentAnnotation, could you set the data-sentry-component value yourself?
Unfortunately, adding this attribute to all elements seems impossible. If I don't, I end up with the following example:
The current CSS selector generator used for INP spans is too limited (function htmlTreeAsString):
- it only walks ~5 parent levels up the DOM,
- it stops once the selector exceeds ~80 characters.
In this example I will never get the component information because the div will hit the 80 character limit via the css selector.
<button data-sentry-component="DetailsButton">
<div class="css-view-g5y9jx r-WebkitUserSelect-9ffhgg r-alignItems-1awozwy r-backgroundColor-18lll9h r-flexDirection-18u37iz r-justifyContent-1h0z5md r-paddingBottom-kzbkwu r-userSelect-lrvibr r-width-13qz1uu r-paddingBlock-1mmae3n r-paddingInline-1fkl15p">
Text
</div>
</button>
That's why it is so important for me to do the bypass:
Added _getSentryLabel() helper function that traverses up to 15 DOM levels to find the nearest data-sentry-label attribute If data-sentry-label is found on an ancestor (beyond the standard 5-level traversal), it prefixes the CSS selector: [data-sentry-label="ProductCard"] div.container > button.btn
In this example I will never get the component information because the div will hit the 80 character limit via the css selector.
I think we can evaluate bumping the 80 char limit. We have this user configurable option, maxValueLength. https://docs.sentry.io/platforms/javascript/guides/react/configuration/options/#maxValueLength. We can just update the code to respect that instead of using DEFAULT_MAX_STRING_LENGTH.
I think we can evaluate bumping the 80 char limit. We have this user configurable option, maxValueLength. https://docs.sentry.io/platforms/javascript/guides/react/configuration/options/#maxValueLength. We can just update the code to respect that instead of using DEFAULT_MAX_STRING_LENGTH.
Thanks for the suggestion!
Raising the 80-char limit would help, but only partially.
I see a few problems with this solution:
- selectors are unreadable at a glance
- class names may not be stable across releases (the longer the selector, the higher the chance it changes)
- conditional UI is hard to identify based only on generated selectors — e.g. if the user clicks a button inside a modal, it’s difficult to locate it later because it’s not in the initial DOM
data-sentry-component partially improves this, but has its own limitations:
- the selector can’t be copied directly into the browser because it’s not a valid CSS selector
- traversal only goes 5 levels up, which is problematic for clicks inside deeper card layouts
- we would still need to add the attribute manually on our components
Overall, data-sentry-label has the potential to solve all of these issues with minimal cost.
We’re still leaning toward the patch approach.
Is there any chance you could implement a solution from this PR, or something similar?