Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reinstate diff between 10.1.2 and 10.1.3 minus @jest/globals support + misc #606

Merged
merged 1 commit into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
514 changes: 179 additions & 335 deletions README.md

Large diffs are not rendered by default.

37 changes: 19 additions & 18 deletions packages/expect-puppeteer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Modify your Jest configuration:

Writing integration test is very hard, especially when you are testing a Single Page Applications. Data are loaded asynchronously and it is difficult to know exactly when an element will be displayed in the page.

[Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md) is great, but it is low level and not designed for integration testing.
[Puppeteer API](https://pptr.dev/api) is great, but it is low level and not designed for integration testing.

This API is designed for integration testing:

Expand Down Expand Up @@ -81,11 +81,11 @@ await expect(page).toMatchElement("div.inner", { text: "some text" });

Expect an element to be in the page or element, then click on it.

- `instance` <[Page]|[ElementHandle]> Context
- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to click on.
- `options` <[Object]> Optional parameters
- `button` <"left"|"right"|"middle"> Defaults to `left`.
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
- `count` <[number]> defaults to 1. See [UIEvent.detail].
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
- `text` <[string]|[RegExp]> A text or a RegExp to match in element `textContent`.

Expand All @@ -111,8 +111,8 @@ const dialog = await expect(page).toDisplayDialog(async () => {

Expect a control to be in the page or element, then fill it with text.

- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match field
- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match field
- `value` <[string]> Value to fill
- `options` <[Object]> Optional parameters
- `delay` <[number]> delay to pass to [the puppeteer `element.type` API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#elementhandletypetext-options)
Expand All @@ -125,8 +125,8 @@ await expect(page).toFill('input[name="firstName"]', "James");

Expect a form to be in the page or element, then fill its controls.

- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match form
- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match form
- `values` <[Object]> Values to fill
- `options` <[Object]> Optional parameters
- `delay` <[number]> delay to pass to [the puppeteer `element.type` API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#elementhandletypetext-options)
Expand All @@ -142,7 +142,7 @@ await expect(page).toFillForm('form[name="myForm"]', {

Expect a text or a string RegExp to be present in the page or element.

- `instance` <[Page]|[ElementHandle]> Context
- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `matcher` <[string]|[RegExp]> A text or a RegExp to match in page
- `options` <[Object]> Optional parameters
- `polling` <[string]|[number]> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values:
Expand All @@ -162,8 +162,8 @@ await expect(page).toMatchTextContent(/lo.*/);

Expect an element be present in the page or element.

- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match element
- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match element
- `options` <[Object]> Optional parameters
- `polling` <[string]|[number]> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values:
- `raf` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
Expand All @@ -183,8 +183,8 @@ await expect(row).toClick("td:nth-child(3) a");

Expect a select control to be present in the page or element, then select the specified option.

- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match select [element]
- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match select [element]
- `valueOrText` <[string]> Value or text matching option

```js
Expand All @@ -195,9 +195,9 @@ await expect(page).toSelect('select[name="choices"]', "Choice 1");

Expect a input file control to be present in the page or element, then fill it with a local file.

- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match input [element]
- `filePath` <[string]> A file path
- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match input [element]
- `filePath` <[string]|[Array]<[string]>> A file path or array of file paths

```js
import { join } from "node:path";
Expand All @@ -208,7 +208,7 @@ await expect(page).toUploadFile(
);
```

### <a name="MatchSelector"></a>{type: [string], value: [string]}
### <a name="MatchSelector"></a>Match Selector

An object used as parameter in order to select an element.

Expand Down Expand Up @@ -242,6 +242,7 @@ setDefaultOptions({ timeout: 1000 });
[element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
[map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
[page]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page "Page"
[elementhandle]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-elementhandle "ElementHandle"
[page]: https://pptr.dev/api/puppeteer.page "Page"
[frame]: https://pptr.dev/api/puppeteer.frame "Frame"
[elementhandle]: https://pptr.dev/api/puppeteer.elementhandle/ "ElementHandle"
[uievent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail
17 changes: 17 additions & 0 deletions packages/expect-puppeteer/src/globals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// import jest globals
import { xdescribe, beforeAll, it, expect } from "@jest/globals";

// import jest-puppeteer globals
import "jest-puppeteer";
import "expect-puppeteer";

// test explicit imports from @jest/globals (incompatible with matchers implementation)
xdescribe("Google", (): void => {
beforeAll(async (): Promise<void> => {
await page.goto("https://google.com");
});

it('should display "google" text on page', async (): Promise<void> => {
await expect(page).not.toMatchTextContent("google", {});
});
});
1 change: 0 additions & 1 deletion packages/expect-puppeteer/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { getDefaultOptions, setDefaultOptions } from "expect-puppeteer";

// import globals
import "jest-puppeteer";
import "expect-puppeteer";

expect.addSnapshotSerializer({
print: () => "hello",
Expand Down
85 changes: 43 additions & 42 deletions packages/expect-puppeteer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ type Wrapper<T> = T extends (
? (...args: A) => R
: never;

// declare matchers list
type PuppeteerMatchers<T> = T extends PuppeteerInstance
// declare common matchers list
type InstanceMatchers<T> = T extends PuppeteerInstance
? {
// common
toClick: Wrapper<typeof toClick>;
Expand All @@ -64,24 +64,24 @@ type PuppeteerMatchers<T> = T extends PuppeteerInstance
: never;

// declare page matchers list
interface PageMatchers extends PuppeteerMatchers<Page> {
interface PageMatchers extends InstanceMatchers<Page> {
// instance specific
toDisplayDialog: Wrapper<typeof toDisplayDialog>;
// inverse matchers
not: PuppeteerMatchers<Page>[`not`] & {};
not: InstanceMatchers<Page>[`not`] & {};
}

// declare frame matchers list
interface FrameMatchers extends PuppeteerMatchers<Frame> {
interface FrameMatchers extends InstanceMatchers<Frame> {
// inverse matchers
not: PuppeteerMatchers<Frame>[`not`] & {};
not: InstanceMatchers<Frame>[`not`] & {};
}

// declare element matchers list
interface ElementHandleMatchers
extends PuppeteerMatchers<ElementHandle<Element>> {
extends InstanceMatchers<ElementHandle<Element>> {
// inverse matchers
not: PuppeteerMatchers<ElementHandle<Element>>[`not`] & {};
not: InstanceMatchers<ElementHandle<Element>>[`not`] & {};
}

// declare matchers per instance type
Expand All @@ -103,40 +103,41 @@ type GlobalWithExpect = typeof globalThis & { expect: PuppeteerExpect };

// ---------------------------

// extend global jest object
// not possible to use PMatchersPerType directly ...
interface PuppeteerMatchers<T> {
// common
toClick: T extends PuppeteerInstance ? Wrapper<typeof toClick> : never;
toFill: T extends PuppeteerInstance ? Wrapper<typeof toFill> : never;
toFillForm: T extends PuppeteerInstance ? Wrapper<typeof toFillForm> : never;
toMatchTextContent: T extends PuppeteerInstance
? Wrapper<typeof toMatchTextContent>
: never;
toMatchElement: T extends PuppeteerInstance
? Wrapper<typeof toMatchElement>
: never;
toSelect: T extends PuppeteerInstance ? Wrapper<typeof toSelect> : never;
toUploadFile: T extends PuppeteerInstance
? Wrapper<typeof toUploadFile>
: never;
// page
toDisplayDialog: T extends Page ? Wrapper<typeof toDisplayDialog> : never;
// inverse matchers
not: {
toMatchTextContent: T extends PuppeteerInstance
? Wrapper<typeof notToMatchTextContent>
: never;
toMatchElement: T extends PuppeteerInstance
? Wrapper<typeof notToMatchElement>
: never;
};
}

// support for @types/jest
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace jest {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Matchers<R, T> {
// common
toClick: T extends PuppeteerInstance ? Wrapper<typeof toClick> : never;
toFill: T extends PuppeteerInstance ? Wrapper<typeof toFill> : never;
toFillForm: T extends PuppeteerInstance
? Wrapper<typeof toFillForm>
: never;
toMatchTextContent: T extends PuppeteerInstance
? Wrapper<typeof toMatchTextContent>
: never;
toMatchElement: T extends PuppeteerInstance
? Wrapper<typeof toMatchElement>
: never;
toSelect: T extends PuppeteerInstance ? Wrapper<typeof toSelect> : never;
toUploadFile: T extends PuppeteerInstance
? Wrapper<typeof toUploadFile>
: never;
// page
toDisplayDialog: T extends Page ? Wrapper<typeof toDisplayDialog> : never;
// inverse matchers
not: {
toMatchTextContent: T extends PuppeteerInstance
? Wrapper<typeof notToMatchTextContent>
: never;
toMatchElement: T extends PuppeteerInstance
? Wrapper<typeof notToMatchElement>
: never;
};
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars
interface Matchers<R, T> extends PuppeteerMatchers<T> {}
}
}

Expand All @@ -151,7 +152,7 @@ const wrapMatcher = <T extends PuppeteerInstance>(
instance: T,
) =>
async function throwingMatcher(...args: unknown[]): Promise<unknown> {
// ???
// update the assertions counter
jestExpect.getState().assertionCalls += 1;
try {
// run async matcher
Expand All @@ -176,7 +177,7 @@ const puppeteerExpect = <T extends PuppeteerInstance>(instance: T) => {
];

if (!isPage && !isFrame && !isHandle)
throw new Error(`${instance} is not supported`);
throw new Error(`${instance.constructor.name} is not supported`);

// retrieve matchers
const expectation = {
Expand Down Expand Up @@ -237,7 +238,7 @@ const expectPuppeteer = (<T>(actual: T) => {

Object.keys(jestExpect).forEach((prop) => {
// @ts-expect-error add jest expect properties to expect-puppeteer implementation
expectPuppeteer[prop] = jestExpect[prop];
expectPuppeteer[prop] = jestExpect[prop] as unknown;
});

export { expectPuppeteer as expect };
Expand Down
4 changes: 2 additions & 2 deletions packages/expect-puppeteer/src/matchers/toClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function toClick(
selector: Selector | string,
options: ToClickOptions = {},
) {
const { delay, button, clickCount, offset, ...otherOptions } = options;
const { delay, button, count, offset, ...otherOptions } = options;
const element = await toMatchElement(instance, selector, otherOptions);
await element.click({ delay, button, clickCount, offset });
await element.click({ delay, button, count, offset });
}
28 changes: 25 additions & 3 deletions packages/jest-environment-puppeteer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,33 @@ describe("Google", () => {
});
```

## TypeScript Setup

If you’re using TypeScript, `jest-puppeteer` natively supports it from version `8.0.0`. To get started with TypeScript, follow these steps:

1. Make sure your project is using the correct type definitions. If you’ve upgraded to version `10.1.2` or above, uninstall old types:

```bash
npm uninstall --save-dev @types/jest-environment-puppeteer @types/expect-puppeteer
```

2. Install `@types/jest` (`jest-puppeteer` does not support `@jest/globals`) :

```bash
npm install --save-dev @types/jest
```

3. Import the `jest-puppeteer` module to expose the global API :

```ts
import "jest-puppeteer";
```

## API

### `global.browser`

Give access to the [Puppeteer Browser](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-browser).
Give access to the [Puppeteer Browser](https://pptr.dev/api/puppeteer.browser).

```js
it("should open a new page", async () => {
Expand All @@ -52,7 +74,7 @@ it("should open a new page", async () => {

### `global.page`

Give access to a [Puppeteer Page](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page) opened at start (you will use it most of time).
Give access to a [Puppeteer Page](https://pptr.dev/api/puppeteer.page) opened at start (you will use it most of time).

```js
it("should fill an input", async () => {
Expand All @@ -62,7 +84,7 @@ it("should fill an input", async () => {

### `global.context`

Give access to a [browser context](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-browsercontext) that is instantiated when the browser is launched. You can control whether each test has its own isolated browser context using the `browserContext` option in config.
Give access to a [browser context](https://pptr.dev/api/puppeteer.browsercontext) that is instantiated when the browser is launched. You can control whether each test has its own isolated browser context using the `browserContext` option in config.

### `global.jestPuppeteer.debug()`

Expand Down
1 change: 0 additions & 1 deletion packages/jest-environment-puppeteer/tests/basic.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("Basic", () => {
beforeAll(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("browserContext", () => {
const test = process.env.INCOGNITO ? it : it.skip;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("browserContext", () => {
const test = process.env.INCOGNITO ? it : it.skip;
Expand Down
1 change: 0 additions & 1 deletion packages/jest-environment-puppeteer/tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { readConfig } from "../src/config";

// import globals
import "jest-puppeteer";
import "expect-puppeteer";

// This test does not run on Node.js < v20 (segfault)
xdescribe("readConfig", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("resetBrowser", () => {
test("should reset browser", async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("resetPage", () => {
test("should reset page", async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("runBeforeUnloadOnClose", () => {
it("shouldn’t call page.close with runBeforeUnload by default", async () => {
Expand Down
Loading