Skip to content

fix: support puppeteer 17 #78

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

Merged
merged 9 commits into from
Mar 7, 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
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