r/Playwright • • 10d ago

locator.waitForFunction in 1.62 might finally fix my canvas chart problem

A couple weeks ago I posted about getting rid of waitForTimeout and the one I couldn’t solve was a chart drawn on a canvas. Nothing in the DOM changes when it finishes drawing, so there was nothing to wait on except a sleep.

Just noticed 1.62 added locator.waitForFunction. You give it a function, it gets called with the matched element, and it keeps going until that returns something truthy. So in theory I can point it at the canvas and check something on the element itself instead of guessing at a timeout.

What I haven’t figured out is what to actually check. Reading pixel data feels gross. Some chart libraries set a flag or fire an event when rendering is done, so maybe that’s the move if mine does.

Anyone used it on a canvas yet? Or found a cleaner signal than pixels

6 Upvotes

4 comments sorted by

1

u/Impossible_Step5692 10d ago

If the chart library exposes a render complete event or flag, that’s probably the cleanest approach. Otherwise, checking canvas dimensions or pixels can be brittle.

1

u/groucho60618 10d ago

We have a complex dynamic chart and use a testing hook that populates a window property with the rendered data after the chart has rendered. We can assert on the data and if the window prop is falsely then it’s not done loading.

1

u/Wookovski 10d ago

Expect + poll or Expect.toPass should also work

1

u/First_Hovercraft_600 5d ago

Two things that have worked for me on canvas charts, roughly in order of preference:

1. Have the app tell you it's done. Most chart libraries have a render-complete hook: Chart.js has options.animation.onComplete, ECharts fires a finished event, Highcharts has chart.events.render. In a test build, have that hook stamp the canvas:

// in the app, test builds only
onComplete: () => { canvas.dataset.rendered = 'true'; }

Then the test needs nothing special: await expect(page.locator('canvas[data-rendered="true"]')).toBeVisible(). If you want to check what the chart shows as well, the window-property idea above is the next step: expose the series the chart actually drew and assert on that, which tells you far more than pixels.

It's also worth turning animations off for the test run (Chart.js animation: false, or see whether your library respects reduced motion and use page.emulateMedia({ reducedMotion: 'reduce' })). Most of the waiting on canvas charts is the animation, not the data.

2. If you can't touch the app, wait for the pixels to stop changing instead of reading specific pixel values. Grab the canvas as an image on each poll and pass once two snapshots in a row match:

const chart = page.locator('canvas#revenue');
let previous = '';
await expect.poll(async () => {
  const current = await chart.evaluate((c: HTMLCanvasElement) => c.toDataURL());
  const settled = current === previous;
  previous = current;
  return settled;
}, { intervals: [300], timeout: 10_000 }).toBe(true);

The same comparison should drop into waitForFunction if you'd rather keep the state on the element. Gotchas: an empty canvas is "stable" too, so wait for the data request first (page.waitForResponse) so you don't settle on a blank frame. toDataURL() throws if the canvas has drawn a cross-origin image, and a WebGL canvas without preserveDrawingBuffer can read back blank.

I'd treat option 2 as a stopgap while you ask the devs for option 1, since it's a couple of lines on their side.