microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
66.05k stars 3.6k forks source link

[Question] Add custom matchers to Playwright with Typescript support #12264

Closed HamedFathi closed 2 years ago

HamedFathi commented 2 years ago

In here you are explaining how to write a custom matcher

// playwright.config.ts
import { expect, PlaywrightTestConfig } from '@playwright/test';

expect.extend({
  toBeWithinRange(received: number, floor: number, ceiling: number) {
    const pass = received >= floor && received <= ceiling;
    if (pass) {
      return {
        message: () => 'passed',
        pass: true,
      };
    } else {
      return {
        message: () => 'failed',
        pass: false,
      };
    }
  },
});

const config: PlaywrightTestConfig = {};
export default config;

but in the last line, you have mentioned:

For TypeScript, also add the following to global.d.ts. You don't need it for JavaScript. There is no following code!

Could you please help me to find out how to write a Typescript compatible code in global.d.ts?

I asked this in stackoverflow first but based on the answer maybe there is a problem.

JnfLS

mxschmitt commented 2 years ago

There was a bug in our docs, that the snippet didn't got rendered. Thats what you need to use:

// global.d.ts
declare global {
 namespace PlaywrightTest {
    interface Matchers<R> {
      toBeWithinRange(a: number, b: number): R;
    }
  }
}

We'll fix it soonish. Please also mark the stackoverflow answer as an answer.

altamashali92 commented 1 year ago

How to specify the type of receiver argument while declaring a custom matcher?

Consider the declaration:

// global.d.ts
declare global {
 namespace PlaywrightTest {
    interface Matchers<R> {
      toBeWithinRange(a: number, b: number): R;
    }
  }
}

This matcher can be used as:

expect(1). toBeWithinRange(0,5);

// How to make it type safe so that we can't pass string inside expect:
expect("hello world"). toBeWithinRange(0,5);
somebody1234 commented 11 months ago

@altamashali92 Late to the party but I have a working (if janky) solution:

Playground

declare global {
 namespace PlaywrightTest {
    const r: unique symbol;
    const t: unique symbol;
    interface Matchers<R, T> {
      [r]: R;
      [t]: T;
      toBeWithinRange(this: Matchers<unknown, number>, a: number, b: number): R;
    }
  }
}

the funny unique symbols are needed otherwise the typecheck doesn't fail, because TS is structurally typed meaning that if R/T is never used in a property in Matchers, then there is no difference between a Matchers<R, T> and a Matchers<R, U>

somebody1234 commented 11 months ago

@mxschmitt it looks like the docs are again missing (again due to some kind of formatting error?) from the new page?