playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Question] Implementing custom locators and assertions for three.js

Open gsimone opened this issue 1 year ago • 3 comments

Hello, I recently tried to add scene-graph tests to my playwright setup. I got some inspiration from the react and vue locator engines but they ultimately expect to receive dom nodes, while my three.js setup would receive three.js' Object3D.

An example of what I'd like to achieve:

const someMesh = await page.locator('three').getByName("my-mesh")
expect(someMesh.material.color).toBe(0xff0000)

Or something like this (not sure about a nice API yet).

So the broader question is, how would I go about implementing a system that's similar to locators but works with any kind of object? I'm mostly worried about respecting the async nature of PW, keeping tooling as useful as it can be and reusing as much of the retry/waiting features of PW as possible.

Any hint or idea is welcome!

gsimone avatar May 15 '23 08:05 gsimone

@gsimone Playwright does not have any facility to reference javascript objects in the page except for JSHandle. You can use handles with page.evaluate() or handle.evaluate() calls to get any JS values from the page.

To make a retrying assertion, you can always expect.poll it:

await expect.poll(() => page.evaluate('window.threeJs.someMesh.material.color')).toBe(0xff0000);

Let me know if this helps.

dgozman avatar May 15 '23 17:05 dgozman

Ok, it did indeed help, it's a bit fiddly but it can work for simple use cases, I guess there's nothing bad in "abusing" expect.poll for an idiomatic retry. I still feel a bit iffy about passing objects to the upper scope, eg.

// imagine obfuscating this away in the real test
let mesh;

await expect.poll(() => {
  return page.evaluate(() => {
    mesh = threeJsScene.find('my-mesh') // abstraction pseudo-code
    return true
  })
}).toBe(truthy)

expect(mesh.material.color).toBe(0x000000)

Is this in any way brittle?

gsimone avatar May 19 '23 09:05 gsimone

The snippet above assigns a variable from the outer scope inside page.evaluate(), which is impossible. I'd recommend something like this:

// helper function
async function getMeshColor(meshId) {
  return page.evaluate(() => {
    const mesh = threeJsScene.find(meshId); // abstraction pseudo-code
    return mesh.material.color;
  });
}

// Actual assertion that auto-waits and produces a nice error message.
await expect.poll(() => getMeshColor('my-mesh')).toBe(0x000000);

Now we can create as many helper functions as needed and assert them in any combinations.

dgozman avatar May 19 '23 15:05 dgozman

It seems like the issue has been resolved. If you still encounter problems, please file a new issue with a repro and link to this one.

dgozman avatar May 22 '23 15:05 dgozman