heytappr.book a demo

why your product tour broke on tuesday

nobody touched the tour. somebody touched a button. that's all it takes, and it happens every sprint.

by shritupdated 6 min read

still from From Up on Poppy Hill: students in masks and headscarves scrubbing and repairing the dusty old clubhouse
cleaning up the Latin Quarter, from From Up on Poppy Hill (Studio Ghibli, 2011). still shared by studio ghibli for free use.

tl;dr (too long, didn't read)

tours and in-app guides break because each step is glued to a css selector, and most selectors describe how an element looks or where it sits. change either and the step points at nothing.

the usual culprits are generated class names, :nth-child, long chains of wrapper divs, and ids that react makes up at runtime. a/b tests and translations break the rest.

the fix is mostly a habit: give guided elements a stable attribute like data-testid, agree on it with engineering, and get told the moment a target disappears instead of hearing it from a customer.

the tour didn't change. the page did.

someone on the growth team builds a lovely five-step tour for the billing page. it works. people finish it. then, a few weeks later, a customer emails support to say the help bubble is floating over an empty corner of the screen and won't go away.

nobody edited the tour. what happened is that an engineer refactored the billing page. maybe they swapped a component library, maybe they wrapped the form in one more div for a layout fix. the page looks identical to a human. to the tour, which was told to find "the third button inside the second div of the main panel", it's a different planet.

this is the most common way in-app guidance fails, and it's strangely under-discussed. demos never show it, because demos happen on the day the tour was built. the problem shows up in month three, quietly, on a page nobody on the tour team was watching.

One Piece, via GIPHY

what a selector actually promises

every step in a tour needs to find one element on the page. almost every tool does that with a css selector, which is a small description like button.export or #settings > div:nth-child(2). the tool stores the description, and each time the step runs, it asks the browser for whatever matches.

the trap is that a selector is a promise about the future. when you write .css-1x2y3z, you're promising that this class name will still exist next month. when you write :nth-child(3), you're promising nobody will add a button before this one. most tour builders generate these promises for you when you click an element in their editor, so nobody even notices they were made.

some promises are safe and some are obviously fragile once you look at them. these are the ones that break most often:

selector habitexamplewhat breaks it
generated class names.css-1x2y3z, .sc-bdVaJaany rebuild of the css, often with no visible change
positionli:nth-child(3)adding, removing or reordering a sibling
wrapper chainsmain > div > div > div > buttona layout refactor that adds or removes one div
runtime ids#:r4:, #ember512the next render, sometimes the next page load
utility classes.flex.gap-2.bg-blue-500a restyle, a dark mode, a design system update
visible texttext equals "Export"a copy change or a translation

the sneaky ones: tests, flags and translations

even a well-chosen selector can fail for reasons that have nothing to do with code quality.

a/b tests are the classic case. half your users see the new checkout with the button moved into a sticky footer. the tour was built against the old version, so for that half it breaks, and for the other half it works, which makes the bug reports confusing enough that people start doubting the reporters.

feature flags do the same thing more slowly. an element only exists for accounts with a flag switched on, so the step works in the tour builder (where the builder's own account has everything on) and fails for a real customer on a lower plan.

translations are the last one. if any step matches on visible text, it works in english and silently fails in german. teams that ship in several languages usually find this out from a single annoyed ticket in the least-watched locale.

the fix is a convention, not a tool

almost all of this goes away with one agreement between whoever builds guidance and whoever builds the product: elements that guidance depends on get a stable attribute that exists only to be selected.

most teams already have one. if you write end-to-end tests with Playwright or Cypress, you probably use data-testid. reuse it. the attribute says nothing about color, position or wording, so a redesign leaves it alone, and anyone deleting it can see that something depends on it.

html
<!-- before: the tour builder picked this for you -->
<div class="sc-bdVaJa">
  <div class="css-1x2y3z">
    <button class="btn btn-primary css-9k2m1q">Export</button>
  </div>
</div>
<!-- step selector: div.sc-bdVaJa > div > button.css-9k2m1q -->

<!-- after: one attribute, agreed with engineering -->
<button class="btn btn-primary css-9k2m1q" data-testid="reports-export">
  Export
</button>
<!-- step selector: [data-testid="reports-export"] -->

the second selector survives a new component library, a new wrapper div, a new button color and a translation. it only breaks if someone removes the export button, and at that point the tour should break.

a few small rules make the convention stick. name attributes after what the thing does (reports-export), not where it is (top-right-button). put the list of guided attributes somewhere engineers will see it, even a comment in the component. and when you're unsure about a selector somebody else wrote, paste it into our free css selector checker, which scores how likely it is to survive a redesign and says why.

find out before your customers do

conventions drift. someone new joins, a page gets rebuilt in a hurry, and a stable attribute disappears. so the other half of the fix is noticing quickly.

the low-tech version is adding your most important tours to the release checklist. before each deploy, someone clicks through them on staging. it's tedious and it works, as long as someone actually does it every time, which is roughly never after the third release.

the better version is making the software tell you. in heytappr, every step of every guide points at a css selector, and heytappr checks those selectors on live sessions. when a release breaks one, the step shows up in a needs-attention list in the console, so the team can fix the guide before a user runs into it. we built it that way because we kept watching tours die quietly, and the people who owned them only found out weeks later.

Spy x Family, via GIPHY

whatever tool you use, ask its vendor one question before you buy: what happens when my team ships a redesign? if the answer involves someone manually re-clicking every step, budget for that person's time, because you'll need it.

a short checklist for the next sprint

  1. list the elements your top five tours or guides point at.
  2. add a data-testid (or your existing test attribute) to each one.
  3. rewrite those steps to use the attribute and nothing else.
  4. run every other selector through the selector checker and fix anything scoring under 65.
  5. tell engineering which attributes guidance depends on, in writing.
  6. set up an alert, or at least a release checklist item, for missing targets.

none of this takes long, and all of it compounds. a tour that survives ten releases is worth far more than a better-looking tour that breaks on the second.

see it in your product

fifteen minutes: heytappr answering a real question by walking a user through a real interface, out loud.

questions people ask

Why does my product tour break after an update?

Each step is attached to a CSS selector. When an update renames a generated class, adds a wrapper element, or reorders siblings, the selector stops matching and the step can't find its target.

What is the most reliable selector for in-app guides?

A dedicated attribute such as data-testid on the element itself. It doesn't change with styling, layout or copy, so it survives most releases.

How do I know when an in-app guide is broken?

Either check your key tours on staging before every release, or use a tool that monitors targets on live sessions. HeyTappr flags steps whose selectors stopped matching in a needs-attention list.

Do A/B tests break product tours?

Often. If a test moves or replaces an element, users in the new variant can get a step that points at nothing. Build tours against stable attributes that exist in every variant.

go deeper

keep reading

spotted something out of date? tell us and it gets fixed.