From a9289fe0c03e0b21ed642440617925e49090dbfe Mon Sep 17 00:00:00 2001 From: Randolf Date: Wed, 9 Aug 2023 16:06:09 +0200 Subject: [PATCH] chore: implement `mapHandle` and `filterHandle` --- .../src/api/locators/FilteredLocator.ts | 22 +++++++----- .../src/api/locators/Locator.ts | 35 ++++++++++++++++++- .../src/api/locators/MappedLocator.ts | 15 +++++--- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/packages/puppeteer-core/src/api/locators/FilteredLocator.ts b/packages/puppeteer-core/src/api/locators/FilteredLocator.ts index ca2a9da5b88e6..9f999fa8c3c3b 100644 --- a/packages/puppeteer-core/src/api/locators/FilteredLocator.ts +++ b/packages/puppeteer-core/src/api/locators/FilteredLocator.ts @@ -16,13 +16,13 @@ import { Observable, + filter, from, map, mergeMap, throwIfEmpty, } from '../../../third_party/rxjs/rxjs.js'; import {Awaitable, HandleFor} from '../../common/common.js'; -import {ElementHandle} from '../ElementHandle.js'; import {DelegatedLocator} from './DelegatedLocator.js'; import {ActionOptions, Locator} from './locators.js'; @@ -34,6 +34,13 @@ export type Predicate = | ((value: From) => value is To) | ((value: From) => Awaitable); +/** + * @internal + */ +export type HandlePredicate = + | ((value: HandleFor, signal?: AbortSignal) => value is HandleFor) + | ((value: HandleFor, signal?: AbortSignal) => Awaitable); + /** * @internal */ @@ -41,9 +48,9 @@ export class FilteredLocator extends DelegatedLocator< From, To > { - #predicate: Predicate; + #predicate: HandlePredicate; - constructor(base: Locator, predicate: Predicate) { + constructor(base: Locator, predicate: HandlePredicate) { super(base); this.#predicate = predicate; } @@ -59,12 +66,11 @@ export class FilteredLocator extends DelegatedLocator< return this.delegate._wait(options).pipe( mergeMap(handle => { return from( - (handle as ElementHandle).frame.waitForFunction( - this.#predicate, - {signal: options?.signal, timeout: this._timeout}, - handle - ) + Promise.resolve(this.#predicate(handle, options?.signal)) ).pipe( + filter(value => { + return value; + }), map(() => { // SAFETY: It passed the predicate, so this is correct. return handle as HandleFor; diff --git a/packages/puppeteer-core/src/api/locators/Locator.ts b/packages/puppeteer-core/src/api/locators/Locator.ts index 19a769c7f0c55..ede9449877e6f 100644 --- a/packages/puppeteer-core/src/api/locators/Locator.ts +++ b/packages/puppeteer-core/src/api/locators/Locator.ts @@ -48,6 +48,7 @@ import { Action, AwaitedLocator, FilteredLocator, + HandleMapper, MappedLocator, Mapper, Predicate, @@ -702,7 +703,10 @@ export abstract class Locator extends EventEmitter { * @public */ map(mapper: Mapper): Locator { - return new MappedLocator(this._clone(), mapper); + return new MappedLocator(this._clone(), handle => { + // SAFETY: TypeScript cannot deduce the type. + return (handle as any).evaluateHandle(mapper); + }); } /** @@ -713,9 +717,38 @@ export abstract class Locator extends EventEmitter { * @public */ filter(predicate: Predicate): Locator { + return new FilteredLocator(this._clone(), async (handle, signal) => { + await (handle as ElementHandle).frame.waitForFunction( + predicate, + {signal, timeout: this._timeout}, + handle + ); + return true; + }); + } + + /** + * Creates an expectation that is evaluated against located handles. + * + * If the expectations do not match, then the locator will retry. + * + * @internal + */ + filterHandle( + predicate: Predicate, HandleFor> + ): Locator { return new FilteredLocator(this._clone(), predicate); } + /** + * Maps the locator using the provided mapper. + * + * @internal + */ + mapHandle(mapper: HandleMapper): Locator { + return new MappedLocator(this._clone(), mapper); + } + click( this: Locator, options?: Readonly diff --git a/packages/puppeteer-core/src/api/locators/MappedLocator.ts b/packages/puppeteer-core/src/api/locators/MappedLocator.ts index 4d71a9ade8b71..96c515b5cd347 100644 --- a/packages/puppeteer-core/src/api/locators/MappedLocator.ts +++ b/packages/puppeteer-core/src/api/locators/MappedLocator.ts @@ -16,7 +16,6 @@ import {Observable, from, mergeMap} from '../../../third_party/rxjs/rxjs.js'; import {Awaitable, HandleFor} from '../../common/common.js'; -import {JSHandle} from '../JSHandle.js'; import {ActionOptions, DelegatedLocator, Locator} from './locators.js'; @@ -25,13 +24,21 @@ import {ActionOptions, DelegatedLocator, Locator} from './locators.js'; */ export type Mapper = (value: From) => Awaitable; +/** + * @internal + */ +export type HandleMapper = ( + value: HandleFor, + signal?: AbortSignal +) => Awaitable>; + /** * @internal */ export class MappedLocator extends DelegatedLocator { - #mapper: Mapper; + #mapper: HandleMapper; - constructor(base: Locator, mapper: Mapper) { + constructor(base: Locator, mapper: HandleMapper) { super(base); this.#mapper = mapper; } @@ -45,7 +52,7 @@ export class MappedLocator extends DelegatedLocator { override _wait(options?: Readonly): Observable> { return this.delegate._wait(options).pipe( mergeMap(handle => { - return from((handle as JSHandle).evaluateHandle(this.#mapper)); + return from(Promise.resolve(this.#mapper(handle, options?.signal))); }) ); }