sentry-javascript icon indicating copy to clipboard operation
sentry-javascript copied to clipboard

feat(core): Add data-sentry-label support in htmlTreeAsString

Open dariusz-biela opened this issue 1 month ago • 10 comments

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-label attribute detection in _htmlElementAsString() - takes priority over data-sentry-component and data-sentry-element
  • 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
  • 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).

dariusz-biela avatar Dec 03 '25 17:12 dariusz-biela

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,
});

AbhiPrasad avatar Dec 03 '25 18:12 AbhiPrasad

I started proposing this solution because I thought Sentry rarely marked elements with its own attributes.

image

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.

dariusz-biela avatar Dec 04 '25 12:12 dariusz-biela

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.

image

dariusz-biela avatar Dec 04 '25 16:12 dariusz-biela

Do you have any ideas on how we can improve the behavior regarding which attribute values ​​are added to HTML?

dariusz-biela avatar Dec 04 '25 16:12 dariusz-biela

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.

AbhiPrasad avatar Dec 04 '25 16:12 AbhiPrasad

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.

image

dariusz-biela avatar Dec 04 '25 17:12 dariusz-biela

Instead of using reactComponentAnnotation, could you set the data-sentry-component value yourself?

AbhiPrasad avatar Dec 04 '25 18:12 AbhiPrasad

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

dariusz-biela avatar Dec 04 '25 18:12 dariusz-biela

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.

AbhiPrasad avatar Dec 05 '25 18:12 AbhiPrasad

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?

dariusz-biela avatar Dec 08 '25 16:12 dariusz-biela