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) | |
|
| [KeyPressOptions](./puppeteer.keypressoptions.md) | |
|
||||||
| [LocatorClickOptions](./puppeteer.locatorclickoptions.md) | |
|
| [LocatorClickOptions](./puppeteer.locatorclickoptions.md) | |
|
||||||
| [LowerCasePaperFormat](./puppeteer.lowercasepaperformat.md) | |
|
| [LowerCasePaperFormat](./puppeteer.lowercasepaperformat.md) | |
|
||||||
|
| [Mapper](./puppeteer.mapper.md) | |
|
||||||
| [MouseButton](./puppeteer.mousebutton.md) | |
|
| [MouseButton](./puppeteer.mousebutton.md) | |
|
||||||
| [NodeFor](./puppeteer.nodefor.md) | |
|
| [NodeFor](./puppeteer.nodefor.md) | |
|
||||||
| [PaperFormat](./puppeteer.paperformat.md) | All the valid paper format types when printing a PDF. |
|
| [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) | | |
|
| [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. |
|
| [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) | | |
|
| [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) | | |
|
| [off(eventName, handler)](./puppeteer.locator.off.md) | | |
|
||||||
| [on(eventName, handler)](./puppeteer.locator.on.md) | | |
|
| [on(eventName, handler)](./puppeteer.locator.on.md) | | |
|
||||||
| [once(eventName, handler)](./puppeteer.locator.once.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 {Awaitable, HandleFor} from '../../common/common.js';
|
||||||
import {ElementHandle} from '../ElementHandle.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
|
* @public
|
||||||
@ -36,69 +37,19 @@ export type Predicate<From, To extends From = From> =
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class ExpectedLocator<From, To extends From> extends Locator<To> {
|
export class ExpectedLocator<From, To extends From> extends DelegatedLocator<
|
||||||
#base: Locator<From>;
|
From,
|
||||||
|
To
|
||||||
|
> {
|
||||||
#predicate: Predicate<From, To>;
|
#predicate: Predicate<From, To>;
|
||||||
|
|
||||||
constructor(base: Locator<From>, predicate: Predicate<From, To>) {
|
constructor(base: Locator<From>, predicate: Predicate<From, To>) {
|
||||||
super();
|
super(base);
|
||||||
|
|
||||||
this.#base = base;
|
|
||||||
this.#predicate = predicate;
|
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>> {
|
override _wait(options?: Readonly<ActionOptions>): Observable<HandleFor<To>> {
|
||||||
return this.#base._wait(options).pipe(
|
return this.delegate._wait(options).pipe(
|
||||||
mergeMap(handle => {
|
mergeMap(handle => {
|
||||||
return from(
|
return from(
|
||||||
(handle as ElementHandle<Node>).frame.waitForFunction(
|
(handle as ElementHandle<Node>).frame.waitForFunction(
|
||||||
|
@ -48,6 +48,8 @@ import {
|
|||||||
Action,
|
Action,
|
||||||
AwaitedLocator,
|
AwaitedLocator,
|
||||||
ExpectedLocator,
|
ExpectedLocator,
|
||||||
|
MappedLocator,
|
||||||
|
Mapper,
|
||||||
Predicate,
|
Predicate,
|
||||||
RaceLocator,
|
RaceLocator,
|
||||||
} from './locators.js';
|
} 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.
|
* 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 './NodeLocator.js';
|
||||||
export * from './ExpectedLocator.js';
|
export * from './ExpectedLocator.js';
|
||||||
export * from './RaceLocator.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', () => {
|
describe('Locator.prototype.expect', () => {
|
||||||
it('should resolve as soon as the predicate matches', async () => {
|
it('should resolve as soon as the predicate matches', async () => {
|
||||||
const clock = sinon.useFakeTimers({
|
const clock = sinon.useFakeTimers({
|
||||||
|
Loading…
Reference in New Issue
Block a user