Skip to content

Commit

Permalink
fix: support puppeteer 17+ (#78)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: drop support node <20 and puppeteer <17
  • Loading branch information
patrickhulce committed Mar 7, 2024
1 parent 860106b commit 761f120
Show file tree
Hide file tree
Showing 10 changed files with 4,174 additions and 5,338 deletions.
19 changes: 6 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,21 @@ jobs:
fail-fast: false
matrix:
include:
- pptr: 1.6.x
- pptr: 1.8.x
- pptr: 1.12.x
- pptr: 2.x.x
- pptr: 3.x.x
- pptr: 4.x.x
- pptr: 5.x.x
- pptr: 6.x.x
- pptr: 7.x.x
- pptr: latest
- pptr: 17.x.x
- pptr: 20.x.x
- pptr: 21.x.x
- pptr: 22.x.x
steps:
- name: Run git checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Run nvm install 14
- name: Run nvm install 20
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 20.x
- run: npm install
- run: npm install "puppeteer@${{ matrix.pptr }}"
- run: npm install "@types/puppeteer@${{ matrix.pptr }}" || echo "No types available"
- run: npm run rebuild
- run: npm run test:lint
- run: npm run test:unit --coverage --runInBand --verbose
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ const {getByText} = $form.getQueriesForElement()
// ...
```

## Version Compat

| Puppeteer Version | pptr-testing-library Version |
| ----------------- | ---------------------------- |
| 17+ | >0.8.0 |
| <17 | 0.7.x |

## API

Unique methods, not part of `@testing-library/dom`
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
collectCoverageFrom: ['**/*.ts', '!**/*.d.ts'],
transform: {
'\\.ts$': 'ts-jest',
'\\.ts$': ['ts-jest', {diagnostics: false}],
},
moduleFileExtensions: ['ts', 'js', 'json'],
testMatch: ['**/*.test.ts'],
Expand Down
31 changes: 22 additions & 9 deletions lib/extend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,40 @@ function requireOrUndefined(path: string): any {
} catch (err) {}
}

const PREFIXES = [
'puppeteer-core/lib/cjs/puppeteer/api', // puppeteer v18+
'puppeteer/lib/cjs/puppeteer/common', // puppeteer v5-v18
'puppeteer/lib', // puppeteer <v5
]

try {
const libPrefix = requireOrUndefined(`puppeteer/lib/cjs/puppeteer/common/Page.js`)
? 'puppeteer/lib/cjs/puppeteer/common'
: 'puppeteer/lib'
const apiPrefix = PREFIXES.find(dir => requireOrUndefined(`${dir}/Page.js`))
if (!apiPrefix) {
const fs = require('fs')
const resolvedPath = require.resolve('puppeteer').replace(/node_modules\/puppeteer.*/, 'node_modules')
const paths = PREFIXES.map(prefix => resolvedPath + '/' + prefix)
const files = paths.flatMap(dir => fs.existsSync(dir) ? fs.readdirSync(dir) : [])
const debugData = `Available Files:\n - ${files.join('\n - ')}`
throw new Error(`Could not find Page class\n${debugData}`)
}

Page = requireOrUndefined(`${apiPrefix}/Page.js`) // tslint:disable-line
if (Page && Page.Page) Page = Page.Page

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

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

if (!ElementHandle) {
const ExecutionContext = requireOrUndefined(`${libPrefix}/ExecutionContext.js`) // tslint:disable-line variable-name
const ExecutionContext = requireOrUndefined(`${apiPrefix}/ExecutionContext.js`) // tslint:disable-line variable-name
if (ExecutionContext && ExecutionContext.ElementHandle) {
ElementHandle = ExecutionContext.ElementHandle
}
}
if (ElementHandle && ElementHandle.ElementHandle) ElementHandle = ElementHandle.ElementHandle

if (!ElementHandle) {
const JSHandle = require(`${libPrefix}/JSHandle.js`) // tslint:disable-line
const JSHandle = require(`${apiPrefix}/JSHandle.js`) // tslint:disable-line
if (JSHandle && JSHandle.ElementHandle) {
ElementHandle = JSHandle.ElementHandle
}
Expand All @@ -52,8 +65,8 @@ try {
return getQueriesForElement(this)
}
} catch (err) {
// tslint:disable-next-line
console.error('Could not augment puppeteer functions, do you have a conflicting version?')
console.error((err as any).stack)
throw err
}

Expand Down
47 changes: 32 additions & 15 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {readFileSync} from 'fs'
import * as path from 'path'
import {ElementHandle, EvaluateFn, JSHandle, Page} from 'puppeteer'
import {ElementHandle, Frame, JSHandle, Page} from 'puppeteer'
import waitForExpect from 'wait-for-expect'

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

function getExecutionContextFromHandle(
elementHandle: ElementHandle,
): Pick<Frame, 'evaluate' | 'evaluateHandle'> {
if (!elementHandle.frame) {
// @ts-ignore - Support versions of puppeteer before v17.
return elementHandle.executionContext()
}

return elementHandle.frame
}

const delegateFnBodyToExecuteInPageInitial = `
${domLibraryAsString};
${convertProxyToRegExp.toString()};
Expand All @@ -56,16 +67,16 @@ const delegateFnBodyToExecuteInPageInitial = `

let delegateFnBodyToExecuteInPage = delegateFnBodyToExecuteInPageInitial

type DOMReturnType = ElementHandle | ElementHandle[] | null
type DOMReturnType = ElementHandle<Node> | Array<ElementHandle<Node>> | null

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

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

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

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

function processNodeText(handles: IHandleSet): Promise<string> {
return handles.containerHandle
.executionContext()
.evaluate(handles.evaluateFn, handles.containerHandle, 'getNodeText')
return getExecutionContextFromHandle(handles.containerHandle).evaluate(
handles.evaluateFn,
handles.containerHandle,
'getNodeText',
)
}

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

try {
const handle = await containerHandle
.executionContext()
.evaluateHandle(evaluateFn, containerHandle, fnName, ...argsToForward)
const handle = await getExecutionContextFromHandle(containerHandle).evaluateHandle(
evaluateFn,
containerHandle,
fnName,
...argsToForward,
)
return await covertToElementHandle(handle, fnName.includes('All'))
} catch (err) {
if (typeof err !== 'object' || !err || !(err instanceof Error)) throw err
err.message = err.message.replace('[fnName]', `[${fnName}]`)
err.stack = err.stack.replace('[fnName]', `[${fnName}]`)
err.stack = (err.stack || '').replace('[fnName]', `[${fnName}]`)
throw err
}
}

interface IHandleSet {
containerHandle: ElementHandle
evaluateFn: EvaluateFn
fnName: string
argsToForward: any[]
evaluateFn(...params: any[]): any
}

function createDelegateFor<T = DOMReturnType>(
Expand Down Expand Up @@ -145,13 +162,13 @@ function createDelegateFor<T = DOMReturnType>(
}
}

export async function getDocument(_page?: Page): Promise<ElementHandle> {
export async function getDocument(_page?: Page): Promise<ElementHandle<Element>> {
// @ts-ignore
const page: Page = _page || this
const documentHandle = await page.mainFrame().evaluateHandle('document')
const document = documentHandle.asElement()
if (!document) throw new Error('Could not find document')
return document
return document as ElementHandle<Element>
}

export function wait(
Expand Down
Loading

0 comments on commit 761f120

Please sign in to comment.