question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Question][Docs] Extending expect with custom DOM-related assertions (and using locators)

See original GitHub issue

Playwright documentation comes with an example on how to use expect.extend to add a custom matcher:

That example is pretty much identical to the one in jest docs, and as such, not useful:

The reason it is not useful is because I want to write a custom matcher that takes a locator and checks its attributes. However, as far as I understand, to do that fully I’d need to implement retries by myself.

In the end, to achieve roughly what I wanted, I wrote something like this:

expect.extend({
  async toHaveAmount(locator, expected) {
    const baseAmount = locator.locator('.base-amount')

    await expect.soft(baseAmount).toHaveAttribute('data-amount', expected)
    const received = await baseAmount.getAttribute('data-amount')

    const options = {
      comment: 'toHaveAmount (baseAmount check)',
      isNot: this.isNot,
      promise: this.promise,
    }

    const pass = expected === received
    const message = pass
      ? () => this.utils.matcherHint('toBe', undefined, undefined, options) +
          '\n\n' +
          `Expected: ${this.isNot ? 'not' : ''}${this.utils.printExpected(expected)}\n` +
          `Received: ${this.utils.printReceived(received)}`
      : () =>  this.utils.matcherHint('toBe', undefined, undefined, options) +
          '\n\n' +
          `Expected: ${this.utils.printExpected(expected)}\n` +
          `Received: ${this.utils.printReceived(received)}`

    return { actual: received, message, pass }
  },
})

Note that to avoid implementing retries by myself I’m adding a .soft assertion, but I still have to implement a full-blown matcher in the end so that I can return valid data. Maybe I am missing something and it can be done in a simpler way, but that’s exactly why it’d be great to have a working example of using extend with playwright locators.

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
AlexDanielcommented, Jul 29, 2022

@pavelfeldman what information do you need? Basically, the point of the ticket is that Playwright docs should explain how to add playwright-related expect extensions (anything that works with the DOM). Any simple example will do.

1reaction
tsvetan-ganevcommented, Oct 15, 2022

@pavelfeldman, I think @AlexDaniel’s point is absolutely valid. I expect the docs to contain a real-world example of a custom matcher that works with a Locator reference and is retry-able like the Playwright matchers that come out of the box. It seems that the functions used internally by the library to create retry-able matchers aren’t exposed to the outside world as part of the public API.

At first I resorted to using expect.poll and it seemed like a hacky workaround - I didn’t know about your suggestion in this thread back then. Then I decided to call the Playwright locator matchers inside of my own matcher function:

/**
 * Asserts an action is disabled due to user role permissions.
 */
async function toBeDisabledForRole(locator: Locator): Promise<ExpectationResult> {
    // serialize the DOM node to show in case failed assertion
    const elementDomString = await locator.evaluate((node) => node.outerHTML);

    try {
        const hasActionDisabledAttribute = expect(locator).toHaveAttribute('data-action-disabled', 'true');
        const hasActionDisabledClass = expect(locator).toHaveClass('action-disabled');
        const isActuallyDisabled = expect(locator).toBeDisabled();

        // retry until all assertions are correct or timeout is reached
        await Promise.all([hasActionDisabledAttribute, hasActionDisabledClass, isActuallyDisabled]);

        return {
            pass: true,
            message: () => `Element should not be disabled due to role permissions.\n${elementDomString}`,
        };
    } catch (error) {
        return {
            pass: false,
            message: () => `Element should be disabled due to role permissions.\n${elementDomString}`,
        };
    }
}

It seems to be working for my use cases so far, but I have the feeling it’s not a solid solution and it might break in unexpected ways going forward.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Playwright Assertions with Custom Matchers extended from ...
In this video, we will discuss Assertions with Custom Matchers extended in Playwright with Typescript Language binding.
Read more >
Making custom Jest assertion matchers in JavaScript and ...
For adding a new matcher, we use the expect.extend method. We pass in an object with each matcher function we want to add...
Read more >
Practical Guide to Custom Jest Matchers - Redd Developer
Custom matchers help you to encapsulate repetitive assertions. This guide will teach you how to create custom matchers in Jest—both ...
Read more >
How to add custom message to Jest expect? - Stack Overflow
You try this lib that extends jest: https://github.com/mattphillips/jest-expect-message test('returns 2 when adding 1 and 1', ...
Read more >
Going too far with Jest Custom Matchers in TypeScript
The docs are okay, and they even mention how to use custom matchers in ... Write the core logic for our assertion; Register...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found