feat: implement Locator.prototype.map
(#10630)
This commit is contained in:
parent
5d34d42d15
commit
47eecf5bb1
@ -172,6 +172,7 @@ sidebar_label: API
|
||||
| [KeyPressOptions](./puppeteer.keypressoptions.md) | |
|
||||
| [LocatorClickOptions](./puppeteer.locatorclickoptions.md) | |
|
||||
| [LowerCasePaperFormat](./puppeteer.lowercasepaperformat.md) | |
|
||||
| [Mapper](./puppeteer.mapper.md) | |
|
||||
| [MouseButton](./puppeteer.mousebutton.md) | |
|
||||
| [NodeFor](./puppeteer.nodefor.md) | |
|
||||
| [PaperFormat](./puppeteer.paperformat.md) | All the valid paper format types when printing a PDF. |
|
||||
|
25
docs/api/puppeteer.locator.map.md
Normal file
25
docs/api/puppeteer.locator.map.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
sidebar_label: Locator.map
|
||||
---
|
||||
|
||||
# Locator.map() method
|
||||
|
||||
Maps the locator using the provided mapper.
|
||||
|
||||
#### Signature:
|
||||
|
||||
```typescript
|
||||
class Locator {
|
||||
map<To>(mapper: Mapper<T, To>): Locator<To>;
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | -------------------------------------------- | ----------- |
|
||||
| mapper | [Mapper](./puppeteer.mapper.md)<T, To> | |
|
||||
|
||||
**Returns:**
|
||||
|
||||
[Locator](./puppeteer.locator.md)<To>
|
@ -27,6 +27,7 @@ export declare abstract class Locator<T> extends EventEmitter
|
||||
| [click(this, options)](./puppeteer.locator.click.md) | | |
|
||||
| [fill(this, value, options)](./puppeteer.locator.fill.md) | | Fills out the input identified by the locator using the provided value. The type of the input is determined at runtime and the appropriate fill-out method is chosen based on the type. contenteditable, selector, inputs are supported. |
|
||||
| [hover(this, options)](./puppeteer.locator.hover.md) | | |
|
||||
| [map(mapper)](./puppeteer.locator.map.md) | | Maps the locator using the provided mapper. |
|
||||
| [off(eventName, handler)](./puppeteer.locator.off.md) | | |
|
||||
| [on(eventName, handler)](./puppeteer.locator.on.md) | | |
|
||||
| [once(eventName, handler)](./puppeteer.locator.once.md) | | |
|
||||
|
13
docs/api/puppeteer.mapper.md
Normal file
13
docs/api/puppeteer.mapper.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
sidebar_label: Mapper
|
||||
---
|
||||
|
||||
# Mapper type
|
||||
|
||||
#### Signature:
|
||||
|
||||
```typescript
|
||||
export type Mapper<From, To> = (value: From) => Awaitable<To>;
|
||||
```
|
||||
|
||||
**References:** [Awaitable](./puppeteer.awaitable.md)
|
84
packages/puppeteer-core/src/api/locators/DelegatedLocator.ts
Normal file
84
packages/puppeteer-core/src/api/locators/DelegatedLocator.ts
Normal file
@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright 2023 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Observable} from '../../../third_party/rxjs/rxjs.js';
|
||||
import {HandleFor} from '../../common/common.js';
|
||||
|
||||
import {Locator, VisibilityOption} from './locators.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class DelegatedLocator<T, U> extends Locator<U> {
|
||||
#delegate: Locator<T>;
|
||||
|
||||
constructor(delegate: Locator<T>) {
|
||||
super();
|
||||
|
||||
this.#delegate = delegate;
|
||||
this.copyOptions(this.#delegate);
|
||||
}
|
||||
|
||||
protected get delegate(): Locator<T> {
|
||||
return this.#delegate;
|
||||
}
|
||||
|
||||
override setTimeout(timeout: number): this {
|
||||
super.setTimeout(timeout);
|
||||
this.#delegate.setTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setVisibility<T extends Node, U extends T>(
|
||||
this: DelegatedLocator<T, U>,
|
||||
visibility: VisibilityOption
|
||||
): Locator<U> {
|
||||
super.setVisibility(visibility);
|
||||
this.#delegate.setVisibility(visibility);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setWaitForEnabled<T extends Node, U extends T>(
|
||||
this: DelegatedLocator<T, U>,
|
||||
value: boolean
|
||||
): Locator<U> {
|
||||
super.setWaitForEnabled(value);
|
||||
this.#delegate.setWaitForEnabled(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setEnsureElementIsInTheViewport<T extends Element, U extends T>(
|
||||
this: DelegatedLocator<T, U>,
|
||||
value: boolean
|
||||
): Locator<U> {
|
||||
super.setEnsureElementIsInTheViewport(value);
|
||||
this.#delegate.setEnsureElementIsInTheViewport(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setWaitForStableBoundingBox<T extends Element, U extends T>(
|
||||
this: DelegatedLocator<T, U>,
|
||||
value: boolean
|
||||
): Locator<U> {
|
||||
super.setWaitForStableBoundingBox(value);
|
||||
this.#delegate.setWaitForStableBoundingBox(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
override _wait(): Observable<HandleFor<U>> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
@ -24,7 +24,8 @@ import {
|
||||
import {Awaitable, HandleFor} from '../../common/common.js';
|
||||
import {ElementHandle} from '../ElementHandle.js';
|
||||
|
||||
import {ActionOptions, Locator, VisibilityOption} from './locators.js';
|
||||
import {DelegatedLocator} from './DelegatedLocator.js';
|
||||
import {ActionOptions, Locator} from './locators.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -36,69 +37,19 @@ export type Predicate<From, To extends From = From> =
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class ExpectedLocator<From, To extends From> extends Locator<To> {
|
||||
#base: Locator<From>;
|
||||
export class ExpectedLocator<From, To extends From> extends DelegatedLocator<
|
||||
From,
|
||||
To
|
||||
> {
|
||||
#predicate: Predicate<From, To>;
|
||||
|
||||
constructor(base: Locator<From>, predicate: Predicate<From, To>) {
|
||||
super();
|
||||
|
||||
this.#base = base;
|
||||
super(base);
|
||||
this.#predicate = predicate;
|
||||
|
||||
this.copyOptions(this.#base);
|
||||
}
|
||||
|
||||
override setTimeout(timeout: number): this {
|
||||
super.setTimeout(timeout);
|
||||
this.#base.setTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setVisibility<FromNode extends Node, ToNode extends FromNode>(
|
||||
this: ExpectedLocator<FromNode, ToNode>,
|
||||
visibility: VisibilityOption
|
||||
): Locator<ToNode> {
|
||||
super.setVisibility(visibility);
|
||||
this.#base.setVisibility(visibility);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setWaitForEnabled<FromNode extends Node, ToNode extends FromNode>(
|
||||
this: ExpectedLocator<FromNode, ToNode>,
|
||||
value: boolean
|
||||
): Locator<ToNode> {
|
||||
super.setWaitForEnabled(value);
|
||||
this.#base.setWaitForEnabled(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setEnsureElementIsInTheViewport<
|
||||
FromElement extends Element,
|
||||
ToElement extends FromElement,
|
||||
>(
|
||||
this: ExpectedLocator<FromElement, ToElement>,
|
||||
value: boolean
|
||||
): Locator<ToElement> {
|
||||
super.setEnsureElementIsInTheViewport(value);
|
||||
this.#base.setEnsureElementIsInTheViewport(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setWaitForStableBoundingBox<
|
||||
FromElement extends Element,
|
||||
ToElement extends FromElement,
|
||||
>(
|
||||
this: ExpectedLocator<FromElement, ToElement>,
|
||||
value: boolean
|
||||
): Locator<ToElement> {
|
||||
super.setWaitForStableBoundingBox(value);
|
||||
this.#base.setWaitForStableBoundingBox(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
override _wait(options?: Readonly<ActionOptions>): Observable<HandleFor<To>> {
|
||||
return this.#base._wait(options).pipe(
|
||||
return this.delegate._wait(options).pipe(
|
||||
mergeMap(handle => {
|
||||
return from(
|
||||
(handle as ElementHandle<Node>).frame.waitForFunction(
|
||||
|
@ -48,6 +48,8 @@ import {
|
||||
Action,
|
||||
AwaitedLocator,
|
||||
ExpectedLocator,
|
||||
MappedLocator,
|
||||
Mapper,
|
||||
Predicate,
|
||||
RaceLocator,
|
||||
} from './locators.js';
|
||||
@ -655,6 +657,15 @@ export abstract class Locator<T> extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the locator using the provided mapper.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
map<To>(mapper: Mapper<T, To>): Locator<To> {
|
||||
return new MappedLocator(this, mapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an expectation that is evaluated against located values.
|
||||
*
|
||||
|
46
packages/puppeteer-core/src/api/locators/MappedLocator.ts
Normal file
46
packages/puppeteer-core/src/api/locators/MappedLocator.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright 2023 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type Mapper<From, To> = (value: From) => Awaitable<To>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class MappedLocator<From, To> extends DelegatedLocator<From, To> {
|
||||
#mapper: Mapper<From, To>;
|
||||
|
||||
constructor(base: Locator<From>, mapper: Mapper<From, To>) {
|
||||
super(base);
|
||||
this.#mapper = mapper;
|
||||
}
|
||||
|
||||
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));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -18,3 +18,5 @@ export * from './Locator.js';
|
||||
export * from './NodeLocator.js';
|
||||
export * from './ExpectedLocator.js';
|
||||
export * from './RaceLocator.js';
|
||||
export * from './DelegatedLocator.js';
|
||||
export * from './MappedLocator.js';
|
||||
|
@ -540,6 +540,67 @@ describe('Locator', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Locator.prototype.map', () => {
|
||||
it('should work', async () => {
|
||||
const {page} = await getTestState();
|
||||
await page.setContent(`<div>test</div>`);
|
||||
await expect(
|
||||
page
|
||||
.locator('::-p-text(test)')
|
||||
.map(element => {
|
||||
return element.getAttribute('clickable');
|
||||
})
|
||||
.wait()
|
||||
).resolves.toEqual(null);
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('div')?.setAttribute('clickable', 'true');
|
||||
});
|
||||
await expect(
|
||||
page
|
||||
.locator('::-p-text(test)')
|
||||
.map(element => {
|
||||
return element.getAttribute('clickable');
|
||||
})
|
||||
.wait()
|
||||
).resolves.toEqual('true');
|
||||
});
|
||||
it('should work with throws', async () => {
|
||||
const {page} = await getTestState();
|
||||
await page.setContent(`<div>test</div>`);
|
||||
const result = page
|
||||
.locator('::-p-text(test)')
|
||||
.map(element => {
|
||||
const clickable = element.getAttribute('clickable');
|
||||
if (!clickable) {
|
||||
throw new Error('Missing `clickable` as an attribute');
|
||||
}
|
||||
return clickable;
|
||||
})
|
||||
.wait();
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('div')?.setAttribute('clickable', 'true');
|
||||
});
|
||||
await expect(result).resolves.toEqual('true');
|
||||
});
|
||||
it('should work with expect', async () => {
|
||||
const {page} = await getTestState();
|
||||
await page.setContent(`<div>test</div>`);
|
||||
const result = page
|
||||
.locator('::-p-text(test)')
|
||||
.expect(element => {
|
||||
return element.getAttribute('clickable') !== null;
|
||||
})
|
||||
.map(element => {
|
||||
return element.getAttribute('clickable');
|
||||
})
|
||||
.wait();
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('div')?.setAttribute('clickable', 'true');
|
||||
});
|
||||
await expect(result).resolves.toEqual('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Locator.prototype.expect', () => {
|
||||
it('should resolve as soon as the predicate matches', async () => {
|
||||
const clock = sinon.useFakeTimers({
|
||||
|
Loading…
Reference in New Issue
Block a user