Skip to content

Commit 761f120

Browse files
authored
fix: support puppeteer 17+ (#78)
BREAKING CHANGE: drop support node <20 and puppeteer <17
1 parent 860106b commit 761f120

File tree

10 files changed

+4174
-5338
lines changed

10 files changed

+4174
-5338
lines changed

.github/workflows/ci.yml

+6-13
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,21 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
include:
15-
- pptr: 1.6.x
16-
- pptr: 1.8.x
17-
- pptr: 1.12.x
18-
- pptr: 2.x.x
19-
- pptr: 3.x.x
20-
- pptr: 4.x.x
21-
- pptr: 5.x.x
22-
- pptr: 6.x.x
23-
- pptr: 7.x.x
24-
- pptr: latest
15+
- pptr: 17.x.x
16+
- pptr: 20.x.x
17+
- pptr: 21.x.x
18+
- pptr: 22.x.x
2519
steps:
2620
- name: Run git checkout
2721
uses: actions/checkout@v2
2822
with:
2923
fetch-depth: 0
30-
- name: Run nvm install 14
24+
- name: Run nvm install 20
3125
uses: actions/setup-node@v1
3226
with:
33-
node-version: 14.x
27+
node-version: 20.x
3428
- run: npm install
3529
- run: npm install "puppeteer@${{ matrix.pptr }}"
3630
- run: npm install "@types/puppeteer@${{ matrix.pptr }}" || echo "No types available"
3731
- run: npm run rebuild
38-
- run: npm run test:lint
3932
- run: npm run test:unit --coverage --runInBand --verbose

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ const {getByText} = $form.getQueriesForElement()
5454
// ...
5555
```
5656

57+
## Version Compat
58+
59+
| Puppeteer Version | pptr-testing-library Version |
60+
| ----------------- | ---------------------------- |
61+
| 17+ | >0.8.0 |
62+
| <17 | 0.7.x |
63+
5764
## API
5865

5966
Unique methods, not part of `@testing-library/dom`

jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module.exports = {
22
collectCoverageFrom: ['**/*.ts', '!**/*.d.ts'],
33
transform: {
4-
'\\.ts$': 'ts-jest',
4+
'\\.ts$': ['ts-jest', {diagnostics: false}],
55
},
66
moduleFileExtensions: ['ts', 'js', 'json'],
77
testMatch: ['**/*.test.ts'],

lib/extend.ts

+22-9
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,40 @@ function requireOrUndefined(path: string): any {
1212
} catch (err) {}
1313
}
1414

15+
const PREFIXES = [
16+
'puppeteer-core/lib/cjs/puppeteer/api', // puppeteer v18+
17+
'puppeteer/lib/cjs/puppeteer/common', // puppeteer v5-v18
18+
'puppeteer/lib', // puppeteer <v5
19+
]
20+
1521
try {
16-
const libPrefix = requireOrUndefined(`puppeteer/lib/cjs/puppeteer/common/Page.js`)
17-
? 'puppeteer/lib/cjs/puppeteer/common'
18-
: 'puppeteer/lib'
22+
const apiPrefix = PREFIXES.find(dir => requireOrUndefined(`${dir}/Page.js`))
23+
if (!apiPrefix) {
24+
const fs = require('fs')
25+
const resolvedPath = require.resolve('puppeteer').replace(/node_modules\/puppeteer.*/, 'node_modules')
26+
const paths = PREFIXES.map(prefix => resolvedPath + '/' + prefix)
27+
const files = paths.flatMap(dir => fs.existsSync(dir) ? fs.readdirSync(dir) : [])
28+
const debugData = `Available Files:\n - ${files.join('\n - ')}`
29+
throw new Error(`Could not find Page class\n${debugData}`)
30+
}
31+
32+
Page = requireOrUndefined(`${apiPrefix}/Page.js`) // tslint:disable-line
33+
if (Page && Page.Page) Page = Page.Page
1934

20-
Page = requireOrUndefined(`${libPrefix}/Page.js`) // tslint:disable-line
21-
if (Page.Page) Page = Page.Page
2235

23-
ElementHandle = requireOrUndefined(`${libPrefix}/ElementHandle.js`) // tslint:disable-line variable-name
36+
ElementHandle = requireOrUndefined(`${apiPrefix}/ElementHandle.js`) // tslint:disable-line variable-name
2437
if (ElementHandle && ElementHandle.ElementHandle) ElementHandle = ElementHandle.ElementHandle
2538

2639
if (!ElementHandle) {
27-
const ExecutionContext = requireOrUndefined(`${libPrefix}/ExecutionContext.js`) // tslint:disable-line variable-name
40+
const ExecutionContext = requireOrUndefined(`${apiPrefix}/ExecutionContext.js`) // tslint:disable-line variable-name
2841
if (ExecutionContext && ExecutionContext.ElementHandle) {
2942
ElementHandle = ExecutionContext.ElementHandle
3043
}
3144
}
3245
if (ElementHandle && ElementHandle.ElementHandle) ElementHandle = ElementHandle.ElementHandle
3346

3447
if (!ElementHandle) {
35-
const JSHandle = require(`${libPrefix}/JSHandle.js`) // tslint:disable-line
48+
const JSHandle = require(`${apiPrefix}/JSHandle.js`) // tslint:disable-line
3649
if (JSHandle && JSHandle.ElementHandle) {
3750
ElementHandle = JSHandle.ElementHandle
3851
}
@@ -52,8 +65,8 @@ try {
5265
return getQueriesForElement(this)
5366
}
5467
} catch (err) {
55-
// tslint:disable-next-line
5668
console.error('Could not augment puppeteer functions, do you have a conflicting version?')
69+
console.error((err as any).stack)
5770
throw err
5871
}
5972

lib/index.ts

+32-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {readFileSync} from 'fs'
22
import * as path from 'path'
3-
import {ElementHandle, EvaluateFn, JSHandle, Page} from 'puppeteer'
3+
import {ElementHandle, Frame, JSHandle, Page} from 'puppeteer'
44
import waitForExpect from 'wait-for-expect'
55

66
import {IConfigureOptions, IQueryUtils, IScopedQueryUtils} from './typedefs'
@@ -43,6 +43,17 @@ function convertRegExpToProxy(o: any, depth: number): any {
4343
return {__regex: o.source, __flags: o.flags}
4444
}
4545

46+
function getExecutionContextFromHandle(
47+
elementHandle: ElementHandle,
48+
): Pick<Frame, 'evaluate' | 'evaluateHandle'> {
49+
if (!elementHandle.frame) {
50+
// @ts-ignore - Support versions of puppeteer before v17.
51+
return elementHandle.executionContext()
52+
}
53+
54+
return elementHandle.frame
55+
}
56+
4657
const delegateFnBodyToExecuteInPageInitial = `
4758
${domLibraryAsString};
4859
${convertProxyToRegExp.toString()};
@@ -56,16 +67,16 @@ const delegateFnBodyToExecuteInPageInitial = `
5667

5768
let delegateFnBodyToExecuteInPage = delegateFnBodyToExecuteInPageInitial
5869

59-
type DOMReturnType = ElementHandle | ElementHandle[] | null
70+
type DOMReturnType = ElementHandle<Node> | Array<ElementHandle<Node>> | null
6071

6172
type ContextFn = (...args: any[]) => ElementHandle
6273

63-
async function createElementHandleArray(handle: JSHandle): Promise<ElementHandle[]> {
74+
async function createElementHandleArray(handle: JSHandle): Promise<Array<ElementHandle<Node>>> {
6475
const lengthHandle = await handle.getProperty('length')
6576
if (!lengthHandle) throw new Error(`Failed to assess length property`)
6677
const length = (await lengthHandle.jsonValue()) as number
6778

68-
const elements: ElementHandle[] = []
79+
const elements: Array<ElementHandle<Node>> = []
6980
for (let i = 0; i < length; i++) {
7081
const jsElement = await handle.getProperty(i.toString())
7182
if (!jsElement) throw new Error(`Failed to assess ${i.toString()} property`)
@@ -76,7 +87,7 @@ async function createElementHandleArray(handle: JSHandle): Promise<ElementHandle
7687
return elements
7788
}
7889

79-
async function createElementHandle(handle: JSHandle): Promise<ElementHandle | null> {
90+
async function createElementHandle(handle: JSHandle): Promise<ElementHandle<Node> | null> {
8091
const element = handle.asElement()
8192
if (element) return element
8293
await handle.dispose()
@@ -88,31 +99,37 @@ async function covertToElementHandle(handle: JSHandle, asArray: boolean): Promis
8899
}
89100

90101
function processNodeText(handles: IHandleSet): Promise<string> {
91-
return handles.containerHandle
92-
.executionContext()
93-
.evaluate(handles.evaluateFn, handles.containerHandle, 'getNodeText')
102+
return getExecutionContextFromHandle(handles.containerHandle).evaluate(
103+
handles.evaluateFn,
104+
handles.containerHandle,
105+
'getNodeText',
106+
)
94107
}
95108

96109
async function processQuery(handles: IHandleSet): Promise<DOMReturnType> {
97110
const {containerHandle, evaluateFn, fnName, argsToForward} = handles
98111

99112
try {
100-
const handle = await containerHandle
101-
.executionContext()
102-
.evaluateHandle(evaluateFn, containerHandle, fnName, ...argsToForward)
113+
const handle = await getExecutionContextFromHandle(containerHandle).evaluateHandle(
114+
evaluateFn,
115+
containerHandle,
116+
fnName,
117+
...argsToForward,
118+
)
103119
return await covertToElementHandle(handle, fnName.includes('All'))
104120
} catch (err) {
121+
if (typeof err !== 'object' || !err || !(err instanceof Error)) throw err
105122
err.message = err.message.replace('[fnName]', `[${fnName}]`)
106-
err.stack = err.stack.replace('[fnName]', `[${fnName}]`)
123+
err.stack = (err.stack || '').replace('[fnName]', `[${fnName}]`)
107124
throw err
108125
}
109126
}
110127

111128
interface IHandleSet {
112129
containerHandle: ElementHandle
113-
evaluateFn: EvaluateFn
114130
fnName: string
115131
argsToForward: any[]
132+
evaluateFn(...params: any[]): any
116133
}
117134

118135
function createDelegateFor<T = DOMReturnType>(
@@ -145,13 +162,13 @@ function createDelegateFor<T = DOMReturnType>(
145162
}
146163
}
147164

148-
export async function getDocument(_page?: Page): Promise<ElementHandle> {
165+
export async function getDocument(_page?: Page): Promise<ElementHandle<Element>> {
149166
// @ts-ignore
150167
const page: Page = _page || this
151168
const documentHandle = await page.mainFrame().evaluateHandle('document')
152169
const document = documentHandle.asElement()
153170
if (!document) throw new Error('Could not find document')
154-
return document
171+
return document as ElementHandle<Element>
155172
}
156173

157174
export function wait(

0 commit comments

Comments
 (0)