Skip to content

Commit e5b2e1a

Browse files
authored
fix: fix types resolution when importing jest types from @jest/globals (#602)
Also improve the guide for TypeScript in README.
1 parent 6a91899 commit e5b2e1a

File tree

13 files changed

+140
-97
lines changed

13 files changed

+140
-97
lines changed

README.md

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
1. [Getting Started](#getting-started)
1212
- [Install the packages](#install-the-packages)
1313
- [Write a test](#write-a-test)
14+
- [Use with Typescript](#use-with-typescript)
1415
- [Visual testing with Argos](#visual-testing-with-argos)
1516
2. [Recipes](#recipes)
1617
- [Enhance testing with `expect-puppeteer` lib](#enhance-testing-with-expect-puppeteer-lib)
@@ -64,6 +65,59 @@ describe("Google", () => {
6465
});
6566
```
6667

68+
### Use with TypeScript
69+
70+
TypeScript is natively supported from v8.0.0, for previous versions, you have to use [community-provided types](https://github.com/DefinitelyTyped/DefinitelyTyped).
71+
72+
_Note : If you have upgraded to version v10.1.2 or above, we strongly recommend that you uninstall them :_
73+
74+
```bash
75+
npm uninstall --save-dev @types/jest-environment-puppeteer @types/expect-puppeteer
76+
```
77+
78+
Native types definitions are available whether you use `@types/jest` or `@jest/globals` for [jest types](https://jestjs.io/docs/getting-started#type-definitions).
79+
80+
Once setup, import the jest-puppeteer modules in your test file, then write your test logic the same way you would in Javascript.
81+
82+
- If using `@types/jest` :
83+
84+
```ts
85+
// import jest-puppeteer globals
86+
import "jest-puppeteer";
87+
import "expect-puppeteer";
88+
89+
describe("Google", (): void => {
90+
beforeAll(async (): Promise<void> => {
91+
await page.goto("https://google.com");
92+
});
93+
94+
it('should display "google" text on page', async (): Promise<void> => {
95+
await expect(page).toMatchTextContent("google");
96+
});
97+
});
98+
```
99+
100+
- If using `@jest/globals` :
101+
102+
```ts
103+
// import jest types
104+
import { expect, describe, beforeAll, it } from "@jest/globals";
105+
106+
// import jest-puppeteer globals
107+
import "jest-puppeteer";
108+
import "expect-puppeteer";
109+
110+
describe("Google", (): void => {
111+
beforeAll(async (): Promise<void> => {
112+
await page.goto("https://google.com");
113+
});
114+
115+
it('should display "google" text on page', async (): Promise<void> => {
116+
await expect(page).toMatchTextContent("google");
117+
});
118+
});
119+
```
120+
67121
### Visual testing with Argos
68122

69123
[Argos](https://argos-ci.com) is a powerful visual testing tool that allows to review visual changes introduced by each pull request.
@@ -486,34 +540,6 @@ beforeEach(async () => {
486540

487541
## Troubleshooting
488542

489-
### TypeScript
490-
491-
TypeScript is natively supported from v8.0.0, for previous versions, you have to use [community-provided types](https://github.com/DefinitelyTyped/DefinitelyTyped).
492-
493-
Note though that it still requires installation of the [type definitions for jest](https://www.npmjs.com/package/@types/jest) :
494-
495-
```bash
496-
npm install --save-dev @types/jest
497-
```
498-
499-
Once setup, import the modules to enable types resolution for the exposed globals, then write your test logic [the same way you would in Javascript](#recipes).
500-
501-
```ts
502-
// import globals
503-
import "jest-puppeteer";
504-
import "expect-puppeteer";
505-
506-
describe("Google", (): void => {
507-
beforeAll(async (): Promise<void> => {
508-
await page.goto("https://google.com");
509-
});
510-
511-
it('should display "google" text on page', async (): Promise<void> => {
512-
await expect(page).toMatchTextContent("google");
513-
});
514-
});
515-
```
516-
517543
### CI Timeout
518544

519545
Most Continuous Integration (CI) platforms restrict the number of threads you can use. If you run multiple test suites, the tests may timeout due to Jest attempting to run Puppeteer in parallel, and the CI platform being unable to process all parallel jobs in time.

packages/expect-puppeteer/README.md

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Modify your Jest configuration:
2424

2525
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.
2626

27-
[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.
27+
[Puppeteer API](https://pptr.dev/api) is great, but it is low level and not designed for integration testing.
2828

2929
This API is designed for integration testing:
3030

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

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

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

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

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

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

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

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

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

145-
- `instance` <[Page]|[ElementHandle]> Context
145+
- `instance` <[Page]|[Frame]|[ElementHandle]> Context
146146
- `matcher` <[string]|[RegExp]> A text or a RegExp to match in page
147147
- `options` <[Object]> Optional parameters
148148
- `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:
@@ -162,8 +162,8 @@ await expect(page).toMatchTextContent(/lo.*/);
162162

163163
Expect an element be present in the page or element.
164164

165-
- `instance` <[Page]|[ElementHandle]> Context
166-
- `selector` <[string]> A [selector] to match element
165+
- `instance` <[Page]|[Frame]|[ElementHandle]> Context
166+
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match element
167167
- `options` <[Object]> Optional parameters
168168
- `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:
169169
- `raf` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
@@ -183,8 +183,8 @@ await expect(row).toClick("td:nth-child(3) a");
183183

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

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

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

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

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

202202
```js
203203
import { join } from "node:path";
@@ -208,7 +208,7 @@ await expect(page).toUploadFile(
208208
);
209209
```
210210

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

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

@@ -242,6 +242,7 @@ setDefaultOptions({ timeout: 1000 });
242242
[element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
243243
[map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"
244244
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
245-
[page]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page "Page"
246-
[elementhandle]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-elementhandle "ElementHandle"
245+
[page]: https://pptr.dev/api/puppeteer.page "Page"
246+
[frame]: https://pptr.dev/api/puppeteer.frame "Frame"
247+
[elementhandle]: https://pptr.dev/api/puppeteer.elementhandle/ "ElementHandle"
247248
[uievent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail

packages/expect-puppeteer/src/index.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { getDefaultOptions, setDefaultOptions } from "expect-puppeteer";
22

33
// import globals
44
import "jest-puppeteer";
5-
import "expect-puppeteer";
65

76
expect.addSnapshotSerializer({
87
print: () => "hello",

packages/expect-puppeteer/src/index.ts

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ type Wrapper<T> = T extends (
4444
? (...args: A) => R
4545
: never;
4646

47-
// declare matchers list
48-
type PuppeteerMatchers<T> = T extends PuppeteerInstance
47+
// declare common matchers list
48+
type InstanceMatchers<T> = T extends PuppeteerInstance
4949
? {
5050
// common
5151
toClick: Wrapper<typeof toClick>;
@@ -64,24 +64,24 @@ type PuppeteerMatchers<T> = T extends PuppeteerInstance
6464
: never;
6565

6666
// declare page matchers list
67-
interface PageMatchers extends PuppeteerMatchers<Page> {
67+
interface PageMatchers extends InstanceMatchers<Page> {
6868
// instance specific
6969
toDisplayDialog: Wrapper<typeof toDisplayDialog>;
7070
// inverse matchers
71-
not: PuppeteerMatchers<Page>[`not`] & {};
71+
not: InstanceMatchers<Page>[`not`] & {};
7272
}
7373

7474
// declare frame matchers list
75-
interface FrameMatchers extends PuppeteerMatchers<Frame> {
75+
interface FrameMatchers extends InstanceMatchers<Frame> {
7676
// inverse matchers
77-
not: PuppeteerMatchers<Frame>[`not`] & {};
77+
not: InstanceMatchers<Frame>[`not`] & {};
7878
}
7979

8080
// declare element matchers list
8181
interface ElementHandleMatchers
82-
extends PuppeteerMatchers<ElementHandle<Element>> {
82+
extends InstanceMatchers<ElementHandle<Element>> {
8383
// inverse matchers
84-
not: PuppeteerMatchers<ElementHandle<Element>>[`not`] & {};
84+
not: InstanceMatchers<ElementHandle<Element>>[`not`] & {};
8585
}
8686

8787
// declare matchers per instance type
@@ -103,43 +103,50 @@ type GlobalWithExpect = typeof globalThis & { expect: PuppeteerExpect };
103103

104104
// ---------------------------
105105

106-
// extend global jest object
106+
// not possible to use PMatchersPerType directly ...
107+
interface PuppeteerMatchers<T> {
108+
// common
109+
toClick: T extends PuppeteerInstance ? Wrapper<typeof toClick> : never;
110+
toFill: T extends PuppeteerInstance ? Wrapper<typeof toFill> : never;
111+
toFillForm: T extends PuppeteerInstance ? Wrapper<typeof toFillForm> : never;
112+
toMatchTextContent: T extends PuppeteerInstance
113+
? Wrapper<typeof toMatchTextContent>
114+
: never;
115+
toMatchElement: T extends PuppeteerInstance
116+
? Wrapper<typeof toMatchElement>
117+
: never;
118+
toSelect: T extends PuppeteerInstance ? Wrapper<typeof toSelect> : never;
119+
toUploadFile: T extends PuppeteerInstance
120+
? Wrapper<typeof toUploadFile>
121+
: never;
122+
// page
123+
toDisplayDialog: T extends Page ? Wrapper<typeof toDisplayDialog> : never;
124+
// inverse matchers
125+
not: {
126+
toMatchTextContent: T extends PuppeteerInstance
127+
? Wrapper<typeof notToMatchTextContent>
128+
: never;
129+
toMatchElement: T extends PuppeteerInstance
130+
? Wrapper<typeof notToMatchElement>
131+
: never;
132+
};
133+
}
134+
135+
// support for @types/jest
107136
declare global {
108137
// eslint-disable-next-line @typescript-eslint/no-namespace
109138
namespace jest {
110-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
111-
interface Matchers<R, T> {
112-
// common
113-
toClick: T extends PuppeteerInstance ? Wrapper<typeof toClick> : never;
114-
toFill: T extends PuppeteerInstance ? Wrapper<typeof toFill> : never;
115-
toFillForm: T extends PuppeteerInstance
116-
? Wrapper<typeof toFillForm>
117-
: never;
118-
toMatchTextContent: T extends PuppeteerInstance
119-
? Wrapper<typeof toMatchTextContent>
120-
: never;
121-
toMatchElement: T extends PuppeteerInstance
122-
? Wrapper<typeof toMatchElement>
123-
: never;
124-
toSelect: T extends PuppeteerInstance ? Wrapper<typeof toSelect> : never;
125-
toUploadFile: T extends PuppeteerInstance
126-
? Wrapper<typeof toUploadFile>
127-
: never;
128-
// page
129-
toDisplayDialog: T extends Page ? Wrapper<typeof toDisplayDialog> : never;
130-
// inverse matchers
131-
not: {
132-
toMatchTextContent: T extends PuppeteerInstance
133-
? Wrapper<typeof notToMatchTextContent>
134-
: never;
135-
toMatchElement: T extends PuppeteerInstance
136-
? Wrapper<typeof notToMatchElement>
137-
: never;
138-
};
139-
}
139+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars
140+
interface Matchers<R, T> extends PuppeteerMatchers<T> {}
140141
}
141142
}
142143

144+
// support for @jest/types
145+
declare module "@jest/expect" {
146+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars
147+
interface Matchers<R, T> extends PuppeteerMatchers<T> {}
148+
}
149+
143150
// ---------------------------
144151
// @ts-expect-error global node object w/ initial jest expect prop attached
145152
const jestExpect = global.expect as JestExpect;
@@ -151,7 +158,7 @@ const wrapMatcher = <T extends PuppeteerInstance>(
151158
instance: T,
152159
) =>
153160
async function throwingMatcher(...args: unknown[]): Promise<unknown> {
154-
// ???
161+
// update the assertions counter
155162
jestExpect.getState().assertionCalls += 1;
156163
try {
157164
// run async matcher
@@ -176,7 +183,9 @@ const puppeteerExpect = <T extends PuppeteerInstance>(instance: T) => {
176183
];
177184

178185
if (!isPage && !isFrame && !isHandle)
179-
throw new Error(`${instance} is not supported`);
186+
throw new Error(
187+
`${String(instance?.constructor?.name ?? `current instance`)} is not supported`,
188+
);
180189

181190
// retrieve matchers
182191
const expectation = {

packages/expect-puppeteer/src/matchers/toClick.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export async function toClick(
99
selector: Selector | string,
1010
options: ToClickOptions = {},
1111
) {
12-
const { delay, button, clickCount, offset, ...otherOptions } = options;
12+
const { delay, button, count, offset, ...otherOptions } = options;
1313
const element = await toMatchElement(instance, selector, otherOptions);
14-
await element.click({ delay, button, clickCount, offset });
14+
await element.click({ delay, button, count, offset });
1515
}

packages/jest-environment-puppeteer/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ describe("Google", () => {
3737
});
3838
```
3939

40+
## Use with TypeScript
41+
42+
_Note : If you have upgraded to version v10.1.2 or above, we strongly recommend that you uninstall the community provided types :_
43+
44+
```bash
45+
npm uninstall --save-dev @types/jest-environment-puppeteer @types/expect-puppeteer
46+
```
47+
48+
If using TypeScript, jest-puppeteer has to be explicitly imported in order to expose the global API :
49+
50+
```ts
51+
// import jest-puppeteer globals
52+
import "jest-puppeteer";
53+
```
54+
4055
## API
4156

4257
### `global.browser`

0 commit comments

Comments
 (0)