Skip to content

Commit

Permalink
chore: implement mapHandle and filterHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
jrandolf committed Aug 9, 2023
1 parent 47dfc35 commit 532b6f1
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 13 deletions.
22 changes: 14 additions & 8 deletions packages/puppeteer-core/src/api/locators/FilteredLocator.ts
Expand Up @@ -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';
Expand All @@ -34,16 +34,23 @@ export type Predicate<From, To extends From = From> =
| ((value: From) => value is To)
| ((value: From) => Awaitable<boolean>);

/**
* @public
*/
export type HandlePredicate<From, To extends From = From> =
| ((value: HandleFor<From>, signal?: AbortSignal) => value is HandleFor<To>)
| ((value: HandleFor<From>, signal?: AbortSignal) => Awaitable<boolean>);

/**
* @internal
*/
export class FilteredLocator<From, To extends From> extends DelegatedLocator<
From,
To
> {
#predicate: Predicate<From, To>;
#predicate: HandlePredicate<From, To>;

constructor(base: Locator<From>, predicate: Predicate<From, To>) {
constructor(base: Locator<From>, predicate: HandlePredicate<From, To>) {
super(base);
this.#predicate = predicate;
}
Expand All @@ -59,12 +66,11 @@ export class FilteredLocator<From, To extends From> extends DelegatedLocator<
return this.delegate._wait(options).pipe(
mergeMap(handle => {
return from(
(handle as ElementHandle<Node>).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<To>;
Expand Down
35 changes: 34 additions & 1 deletion packages/puppeteer-core/src/api/locators/Locator.ts
Expand Up @@ -48,6 +48,7 @@ import {
Action,
AwaitedLocator,
FilteredLocator,
HandleMapper,
MappedLocator,
Mapper,
Predicate,
Expand Down Expand Up @@ -702,7 +703,10 @@ export abstract class Locator<T> extends EventEmitter {
* @public
*/
map<To>(mapper: Mapper<T, To>): Locator<To> {
return new MappedLocator(this._clone(), mapper);
return new MappedLocator(this._clone(), handle => {
// SAFETY: TypeScript cannot deduce the type.
return (handle as any).evaluateHandle(mapper);
});
}

/**
Expand All @@ -713,9 +717,38 @@ export abstract class Locator<T> extends EventEmitter {
* @public
*/
filter<S extends T>(predicate: Predicate<T, S>): Locator<S> {
return new FilteredLocator(this._clone(), async (handle, signal) => {
await (handle as ElementHandle<Node>).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<S extends T>(
predicate: Predicate<HandleFor<T>, HandleFor<S>>
): Locator<S> {
return new FilteredLocator(this._clone(), predicate);
}

/**
* Maps the locator using the provided mapper.
*
* @internal
*/
mapHandle<To>(mapper: HandleMapper<T, To>): Locator<To> {
return new MappedLocator(this._clone(), mapper);
}

click<ElementType extends Element>(
this: Locator<ElementType>,
options?: Readonly<LocatorClickOptions>
Expand Down
15 changes: 11 additions & 4 deletions packages/puppeteer-core/src/api/locators/MappedLocator.ts
Expand Up @@ -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';

Expand All @@ -25,13 +24,21 @@ import {ActionOptions, DelegatedLocator, Locator} from './locators.js';
*/
export type Mapper<From, To> = (value: From) => Awaitable<To>;

/**
* @internal
*/
export type HandleMapper<From, To> = (
value: HandleFor<From>,
signal?: AbortSignal
) => Awaitable<HandleFor<To>>;

/**
* @internal
*/
export class MappedLocator<From, To> extends DelegatedLocator<From, To> {
#mapper: Mapper<From, To>;
#mapper: HandleMapper<From, To>;

constructor(base: Locator<From>, mapper: Mapper<From, To>) {
constructor(base: Locator<From>, mapper: HandleMapper<From, To>) {
super(base);
this.#mapper = mapper;
}
Expand All @@ -45,7 +52,7 @@ export class MappedLocator<From, To> extends DelegatedLocator<From, To> {
override _wait(options?: Readonly<ActionOptions>): Observable<HandleFor<To>> {
return this.delegate._wait(options).pipe(
mergeMap(handle => {
return from((handle as JSHandle<From>).evaluateHandle(this.#mapper));
return from(Promise.resolve(this.#mapper(handle, options?.signal)));
})
);
}
Expand Down

0 comments on commit 532b6f1

Please sign in to comment.