Jemand meldet, dass die Seite kaputtgeht. Du folgst den Schritten ganz genau. Alles funktioniert. Du schließt das Ticket als nicht reproduzierbar, und drei Wochen später melden vier weitere Leute dasselbe.
„Bei mir läuft’s“ gilt als Witz, ist aber eine präzise und nützliche Aussage: Der Bug hängt von irgendeinem Unterschied zwischen deiner Umgebung und der anderen ab. Damit wird aus einer unbeantwortbaren Frage eine lösbare. Nicht „warum ist dieser Bug so seltsam“, sondern „was ist an diesen beiden Rechnern anders?“
So viele Unterschiede kommen gar nicht infrage. In Browser-Anwendungen fallen praktisch alle in acht Kategorien, und jede hinterlässt einen erkennbaren Fingerabdruck.
1. Zustand
Die mit Abstand häufigste Ursache. Du reproduzierst nach frischem Laden. Die andere Person ist seit zwei Stunden in diesem Tab.
Langlebige Single-Page-Anwendungen sammeln einiges an: veraltete Einträge in einem clientseitigen Cache, ein WebSocket, der sich mit einer anderen Session neu verbunden hat, ein Store, den ein Codepfad von vor einer Stunde verändert hat, ein Access-Token, das sich mittendrin erneuert hat. Deine frisch geladene Seite hat nichts von dieser Geschichte.
Fingerabdruck: Nach einem Neuladen funktioniert es. Wenn jemand schreibt „Neuladen hilft“, bist du mit ziemlicher Sicherheit in dieser Kategorie, und für die Reproduktion brauchst du nicht die gemeldeten Schritte, sondern dieselben Schritte nach zwanzig Minuten ganz anderer Nutzung.
So fängst du es ein: mit der geordneten Liste dessen, was die Person in der Session tatsächlich getan hat, nicht dessen, was sie für relevant hielt. Das liefert ein Protokoll der Interaktionen, ein Feld „Schritte zur Reproduktion“ dagegen nie, denn der entscheidende Schritt ist der, den niemand für erwähnenswert hielt.
2. Timing
Auf deinem Rechner ist alles schnell. Lokale API, warmer Cache, schnelle Festplatte. Auf einem Handy im Zug dauert ein Request, der bei dir 40 ms braucht, 4 Sekunden, und eine Race Condition, die auf deinem Rechner nie verliert, verliert dort zuverlässig.
Fingerabdruck: sporadisch. „Passiert vielleicht jedes fünfte Mal.“ Sporadisch heißt fast immer Timing. Es heißt, dass die beiden konkurrierenden Operationen so ähnlich lange dauern, dass normale Schwankungen den Gewinner vertauschen.
So fängst du es ein: mit Netzwerk-Timings. Schau, was gleichzeitig unterwegs war und wie lange jeder Request gedauert hat. Das klassische Muster sind zwei Requests, die zusammen gestartet sind, während der Code davon ausging, dass der erste zuerst fertig wird.
So reproduzierst du es: drosseln. Netzwerk- und CPU-Drosselung in den DevTools machen aus sehr vielen „nicht reproduzierbar“-Bugs ein „jedes Mal reproduzierbar“. Das sind die wertvollsten fünf Minuten in diesem ganzen Text.
3. Umgebung
Browser, Version, Betriebssystem und, am meisten unterschätzt, die Viewport-Größe.
Du testest mit 1440 px in Chrome auf dem Desktop. Die andere Person hat 390 px in Safari auf iOS, oder 1280 px bei 125 % Browser-Zoom, und das ist nicht dasselbe wie 1024 px: Gerade diese Unterschiede lassen Layouts brechen. Vor allem Safari weicht immer noch ab: beim Parsen von Datumsangaben, bei Backdrop-Filtern, beim Scrollverhalten und beim Umgang mit Storage im privaten Modus.
Fingerabdruck: bei der meldenden Person immer, bei dir nie. Auf ihrem Rechner tritt es in 100 % der Fälle auf.
So fängst du es ein: mit Browser, Betriebssystem und Viewport, die automatisch mit dem Bericht erfasst werden. In keiner anderen Kategorie ist das Verhältnis von „trivial behoben, sobald bekannt“ zu „unmöglich zu erraten“ so hoch. Genau deshalb ist es eine so schlechte Idee, eine ganze Support-Runde damit zu verbringen, nach der Browser-Version zu fragen.
4. Identität und Berechtigungen
Du bist als Admin angemeldet, in deinem eigenen Account, in deiner eigenen Organisation. Die andere Person ist ein Mitglied mit reinem Lesezugriff in einem Team von vierzig Leuten, in einem Plan, in dem ein Feature-Flag aus ist.
Rollenabhängige Bugs bleiben systematisch unentdeckt, weil im Entwicklungsteam fast nie jemand das eigene Produkt mit den geringsten Rechten durchklickt.
Fingerabdruck: tritt bei einer Gruppe von Leuten auf, bei einer anderen nicht. Oft melden es mehrere Personen mit derselben Rolle gleichzeitig, und dann sieht es nach einer plötzlichen Regression aus, obwohl es seit Monaten kaputt ist.
So fängst du es ein: Hänge Rolle, Plan und Flag-Zustand der Person an den Bericht. Jedes ordentliche SDK nimmt eigenen Kontext an – in BugDump ist das identify() für die Person und setContext() für beliebigen Zustand –, und Rolle und Plan sind die beiden Felder, die du vor allen anderen mitschicken solltest.
5. Daten
In ihrem Account gibt es einen Datensatz mit einem Emoji im Namen, oder ein null, wo du einen String erwartet hast, oder 4.000 Zeilen, wo deine Fixtures 12 haben, oder eine Währung ohne Nachkommastellen, oder ein Datum im Jahr 1970, weil ein Import schiefging.
Fingerabdruck: tritt bei einem bestimmten Datensatz auf und nirgends sonst. Oft heißt es „nur dieses eine Projekt macht das“, und damit bekommst du die ganze Diagnose geschenkt.
So fängst du es ein: mit der ID des Datensatzes und der fehlgeschlagenen API-Antwort. Ein Response-Body, in dem der problematische Wert steht, erledigt diese Kategorie sofort. Das macht Response-Bodys so nützlich, und deshalb muss man auch vorsichtig mit ihnen umgehen: Dasselbe Feld, in dem das kaputte Datum steht, enthält auch alles andere über die Person.
6. Erweiterungen und eingeschleuste Skripte
Adblocker blockieren deinen Analytics-Endpunkt, und dein Code hat nicht damit gerechnet, dass dieser Request fehlschlägt. Passwortmanager schleusen DOM in dein Formular ein und stören das Event-Handling. Übersetzungserweiterungen schreiben Textknoten um und zerlegen die Reconciliation von React. Firmen-Proxys schreiben Antworten um.
Fingerabdruck: Niemand kann es reproduzieren, auch andere Leute nicht, und in der Console steht ein Request, der ohne Status fehlgeschlagen ist, oder ein DOM-Fehler, der auf ein Element zeigt, das du nie gerendert hast.
So fängst du es ein: Fehlgeschlagene Requests ohne Antwort verraten diese Kategorie. Ebenso ein Session-Replay, das DOM zeigt, das deine Anwendung gar nicht erzeugt.
7. Das Netzwerk selbst
Captive Portals, TLS-Interception im Firmennetz, DNS-Fehler, ein CDN-Edge, der einer Region ein veraltetes Bundle ausliefert, ein laufendes Deployment, bei dem das HTML schon neu ist und der Chunk, auf den es verweist, schon weg.
Fingerabdruck: räumlich oder zeitlich gehäuft. Mehrere Berichte in kurzer Zeit oder alle aus einem Land.
So fängst du es ein: mit den Zeitstempeln und der URL der Datei, die nicht geladen wurde. Ein ChunkLoadError in der Console gehört fast per Definition in diese Kategorie, und er bedeutet, dass deine Deploy-Strategie alte Chunks löscht, bevor alte Tabs mit ihnen fertig sind.
8. Zeit
Zeitzonen, die Umstellung auf Sommer- und Winterzeit, Monatsgrenzen, lokale Datumsformate und der Rechner, dessen Uhr elf Minuten falsch geht. Das reicht schon, damit ein Token gleich bei der Ankunft abgelaufen wirkt.
Fingerabdruck: tritt nur zu bestimmten Uhrzeiten, nur in bestimmten Regionen oder nur gegen Monatsende auf.
So fängst du es ein: mit dem Zeitzonen-Offset und der Ortszeit der meldenden Person neben dem Zeitstempel des Servers. Das zu erfassen kostet fast nichts und klärt diese Kategorie auf einen Blick.
Die Methode
Wenn du einen Bericht nicht reproduzieren kannst, hilft es nicht, dich noch mehr anzustrengen. Ordne ihn einer der acht Kategorien zu. Für jede gibt es eine Frage, mit der du sie schnell erkennst:
Hilft Neuladen? → Zustand
Tritt es nur sporadisch auf? → Timing
Passiert es bei der Person jedes Mal? → Umgebung
Teilt sie Rolle oder Plan mit anderen, die es gemeldet haben? → Identität
Betrifft es einen bestimmten Datensatz? → Daten
Ist ein Request ohne Status fehlgeschlagen? → Erweiterungen oder Netzwerk
Häuft es sich zeitlich oder räumlich? → Netzwerk
Nur zu bestimmten Uhrzeiten? → Zeit
Jede dieser Fragen beantworten Daten, kein Gespräch. Und das ist der eigentliche Grund, warum „nicht reproduzierbar“ so oft vorkommt: Die Belege, die den Bug seiner Kategorie zuordnen würden, lagen im Browser der Person, als es kaputtging, und niemand hat sie erfasst.
Erfasse den Unterschied, nicht die Beschreibung
Es lohnt sich, das zu systematisieren, weil nicht reproduzierbare Bugs keine seltenen Randfälle sind. Sie sind die, die offen bleiben. Reproduzierbare Bugs werden in derselben Woche behoben. Der Backlog besteht aus der anderen Sorte, und die andere Sorte ist fast ausschließlich ein Datenproblem.
Kommt ein Bericht mit Browser, Betriebssystem, Viewport, Zeitzone, Rolle, Session-Verlauf, Console und Netzwerk-Log an, muss niemand eine Kategorie erraten: Der Unterschied zwischen dem anderen Rechner und deinem steht im Bericht. Genau das erfasst BugDump automatisch über ein einziges Script-Tag, sodass die acht Fragen oben beantwortet sind, bevor jemand eine davon stellen muss.
Das Seltenste und Wertvollste in dieser Liste ist die zweite Person, die denselben Bug meldet. Ein nicht reproduzierbarer Bericht ist ein Rätsel. Drei davon mit drei verschiedenen Umgebungen sind ein kontrolliertes Experiment: Die Variablen, die sie gemeinsam haben, sind die Ursache, der Rest ist Rauschen. Doppelte Berichte in einer Aufgabe zu bündeln, sieht nach Aufräumen aus. Bei genau den Bugs, die du nicht reproduzieren kannst, ist es die Diagnose.
Was tun mit einem Fehlerbericht, den du nicht reproduzieren kannst – was du konkret tust, wenn ein gemeldeter Bug bei dir nicht auftritt.
Session-Replay zum Debuggen: was vor dem Bug passiert ist – wie eine Aufzeichnung der Session den Zustand und die Schritte zeigt, die in keinem Bericht stehen.
So erfasst du Console-Logs und Netzwerk-Requests aus fremden Browsern – wie Console und Netzwerk aus dem Browser der meldenden Person in den Bericht kommen.