Skip to content

Commit 64c0d72

Browse files
ryan-williamsclaude
andcommitted
www/e2e: stabilize YTD legend hover + CrashPlot tooltip-order tests
Two flakes surfaced once the duckdb fix unblocked the plot-rendering path: * **YTD legend hover** (`hovering a legend item thickens its trace line`): on Linux CI (and intermittently elsewhere), Playwright's `scrollIntoViewIfNeeded` landed the target legend item near a viewport edge where the sticky `GeoNavBar` or a sibling plot's `controlsWrapper` intercepted pointer events. Explicitly scroll the item to viewport center (`block: 'center'`) before hovering — parks it well clear of either. * **CrashPlot tooltip-order test**: the original assertion expected `tipTypes == stackOrder.reverse()` (top-of-stack first). Plotly's `x unified` hovermode actually lists traces in `gd.data` order (matching `legend.traceorder: 'normal'`) — bottom-first for our stacked bars. Flip the assertion and comment that the UX tradeoff (top-first tooltip would also reverse the legend) is intentional. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2317718 commit 64c0d72

1 file changed

Lines changed: 14 additions & 6 deletions

File tree

www/e2e/legend-ux.spec.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ test.describe('YTD legend', () => {
193193
)
194194

195195
expect(await lineWidth()).toBe(2)
196+
// Center the item in the viewport before hovering — the sticky `GeoNavBar`
197+
// (`z: 100`) at top + various controls/wrappers below the plot can intercept
198+
// pointer events at viewport edges, which trips Playwright's actionability
199+
// check. `block: 'center'` parks the item well clear of either.
200+
await items.nth(idx).evaluate(el => el.scrollIntoView({ block: 'center' }))
196201
await items.nth(idx).hover()
197202
await expect.poll(lineWidth).toBe(5)
198203

@@ -236,18 +241,21 @@ test.describe('Tooltip order', () => {
236241
expect(traceorder).toContain('reversed')
237242
})
238243

239-
test('CrashPlot tooltip order matches stack (top-first)', async ({ page }) => {
244+
test('CrashPlot tooltip order matches stack', async ({ page }) => {
240245
await page.goto('/#njdot')
241246
const plot = page.locator('.js-plotly-plot').nth(4)
242247
await plot.waitFor({ timeout: 15000 })
243248
await page.waitForTimeout(2000)
244249
await plot.scrollIntoViewIfNeeded()
245250
const box = await plot.boundingBox()
246251

247-
// The x-unified tooltip lists traces top-of-stack first — the reverse of
248-
// `gd.data`'s bottom-up order. Asserting against the live stack keeps this
249-
// robust to severity-label changes; the `toPass` retries the hover, which
250-
// can land between bars on the first sweep.
252+
// Plotly's x-unified tooltip lists traces in `gd.data` order (matches
253+
// `layout.legend.traceorder: 'normal'` set on CrashPlot). For our stacked
254+
// bars that's bottom-of-stack first. Top-first would arguably be better
255+
// UX (eye reads top-to-bottom), but plotly couples tooltip order to
256+
// `legend.traceorder` — switching one swaps the other. Asserting against
257+
// the live `data.order` keeps this robust to severity-label changes;
258+
// `toPass` retries the hover in case the first sweep lands between bars.
251259
await expect(async () => {
252260
await page.mouse.move(box!.x + 50, box!.y + box!.height / 2, { steps: 3 })
253261
await page.waitForTimeout(200)
@@ -256,7 +264,7 @@ test.describe('Tooltip order', () => {
256264
const stackOrder = await plot.evaluate(el => (el as any).data.map((t: any) => t.name))
257265
const tip = await plot.locator('.hoverlayer .legend .traces .legendtext').allTextContents()
258266
const tipTypes = tip.map(n => n.split(':')[0].trim())
259-
expect(tipTypes).toEqual([...stackOrder].reverse())
267+
expect(tipTypes).toEqual(stackOrder)
260268
}).toPass({ timeout: 15000 })
261269
})
262270
})

0 commit comments

Comments
 (0)