diff --git a/internal/driver/chrome/driver.go b/internal/driver/chrome/driver.go index 002e35e..a56c985 100644 --- a/internal/driver/chrome/driver.go +++ b/internal/driver/chrome/driver.go @@ -381,7 +381,11 @@ func (d *Driver) Hierarchy(ctx context.Context) (string, error) { defer cancel() script := ` (function() { - const route = window.location.hash.replace(/^#/, '').split('?')[0] || '/'; + // Hash first (a HashRouter names the screen there), then the pathname, which + // is where a path-routed SPA keeps it. Reporting '/' for every step of a + // BrowserRouter app made every screen look like the same screen. + const route = window.location.hash.replace(/^#/, '').split('?')[0] || + window.location.pathname || '/'; // clickable and editable are resolved through the SAME selector sets // pkg/spec/src/web-runtime.ts uses, so the goja host (which reads this dump) // and the V8 host (which reads the DOM directly) cannot mean different things diff --git a/internal/driver/chrome/driver_test.go b/internal/driver/chrome/driver_test.go index f779ed3..84fee8d 100644 --- a/internal/driver/chrome/driver_test.go +++ b/internal/driver/chrome/driver_test.go @@ -544,3 +544,49 @@ func TestInputText_ReplacesTextInsideAShadowRoot(t *testing.T) { t.Errorf("field holds %q after EraseText, want empty", shown) } } + +// TestHierarchy_ScreenFallsBackToThePathname pins the route the goja host reads +// off the dump. Reading location.hash alone reported "/" on every step of a +// path-routed SPA (react-router's BrowserRouter, which the replay UI itself +// uses), so every screen looked like the same screen and no route-scoped +// property or action could tell them apart. +func TestHierarchy_ScreenFallsBackToThePathname(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/html") + _, _ = w.Write([]byte(`