chore: implement mapHandle and filterHandle (#10716)

This commit is contained in:
jrandolf 2023-08-09 16:36:58 +02:00 committed by GitHub
parent 47dfc3578d
commit 0c59e9a1cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 14 deletions

View File

@ -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<From, To extends From = From> =
| ((value: From) => value is To)
| ((value: From) => Awaitable<boolean>);
/**
* @internal
*/
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
*/
@ -41,9 +48,9 @@ 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;
}
@ -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>;

View File

@ -48,6 +48,7 @@ import {
Action,
AwaitedLocator,
FilteredLocator,
HandleMapper,
MappedLocator,
Mapper,
Predicate,
@ -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);
});
}
/**
@ -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>

View File

@ -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';
@ -28,10 +27,18 @@ export type Mapper<From, To> = (value: From) => Awaitable<To>;
/**
* @internal
*/
export class MappedLocator<From, To> extends DelegatedLocator<From, To> {
#mapper: Mapper<From, To>;
export type HandleMapper<From, To> = (
value: HandleFor<From>,
signal?: AbortSignal
) => Awaitable<HandleFor<To>>;
constructor(base: Locator<From>, mapper: Mapper<From, To>) {
/**
* @internal
*/
export class MappedLocator<From, To> extends DelegatedLocator<From, To> {
#mapper: HandleMapper<From, To>;
constructor(base: Locator<From>, mapper: HandleMapper<From, To>) {
super(base);
this.#mapper = mapper;
}
@ -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)));
})
);
}