mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: implement typed Locators, expects, and internal contexts (#10573)
This commit is contained in:
parent
dcc08aa6b8
commit
c14f9b64a7
@ -97,6 +97,7 @@ sidebar_label: API
|
|||||||
| [LaunchOptions](./puppeteer.launchoptions.md) | Generic launch options that can be passed when launching any browser. |
|
| [LaunchOptions](./puppeteer.launchoptions.md) | Generic launch options that can be passed when launching any browser. |
|
||||||
| [LocatorEventObject](./puppeteer.locatoreventobject.md) | |
|
| [LocatorEventObject](./puppeteer.locatoreventobject.md) | |
|
||||||
| [LocatorOptions](./puppeteer.locatoroptions.md) | |
|
| [LocatorOptions](./puppeteer.locatoroptions.md) | |
|
||||||
|
| [LocatorScrollOptions](./puppeteer.locatorscrolloptions.md) | |
|
||||||
| [MediaFeature](./puppeteer.mediafeature.md) | |
|
| [MediaFeature](./puppeteer.mediafeature.md) | |
|
||||||
| [Metrics](./puppeteer.metrics.md) | |
|
| [Metrics](./puppeteer.metrics.md) | |
|
||||||
| [MouseClickOptions](./puppeteer.mouseclickoptions.md) | |
|
| [MouseClickOptions](./puppeteer.mouseclickoptions.md) | |
|
||||||
@ -147,7 +148,6 @@ sidebar_label: API
|
|||||||
|
|
||||||
| Type Alias | Description |
|
| Type Alias | Description |
|
||||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| [ActionCondition](./puppeteer.actioncondition.md) | |
|
|
||||||
| [ActionResult](./puppeteer.actionresult.md) | |
|
| [ActionResult](./puppeteer.actionresult.md) | |
|
||||||
| [Awaitable](./puppeteer.awaitable.md) | |
|
| [Awaitable](./puppeteer.awaitable.md) | |
|
||||||
| [AwaitableIterable](./puppeteer.awaitableiterable.md) | |
|
| [AwaitableIterable](./puppeteer.awaitableiterable.md) | |
|
||||||
@ -167,11 +167,13 @@ sidebar_label: API
|
|||||||
| [InterceptResolutionStrategy](./puppeteer.interceptresolutionstrategy.md) | |
|
| [InterceptResolutionStrategy](./puppeteer.interceptresolutionstrategy.md) | |
|
||||||
| [KeyInput](./puppeteer.keyinput.md) | All the valid keys that can be passed to functions that take user input, such as [keyboard.press](./puppeteer.keyboard.press.md) |
|
| [KeyInput](./puppeteer.keyinput.md) | All the valid keys that can be passed to functions that take user input, such as [keyboard.press](./puppeteer.keyboard.press.md) |
|
||||||
| [KeyPressOptions](./puppeteer.keypressoptions.md) | |
|
| [KeyPressOptions](./puppeteer.keypressoptions.md) | |
|
||||||
|
| [LocatorClickOptions](./puppeteer.locatorclickoptions.md) | |
|
||||||
| [LowerCasePaperFormat](./puppeteer.lowercasepaperformat.md) | |
|
| [LowerCasePaperFormat](./puppeteer.lowercasepaperformat.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. |
|
||||||
| [Permission](./puppeteer.permission.md) | |
|
| [Permission](./puppeteer.permission.md) | |
|
||||||
|
| [Predicate](./puppeteer.predicate.md) | |
|
||||||
| [Product](./puppeteer.product.md) | Supported products. |
|
| [Product](./puppeteer.product.md) | Supported products. |
|
||||||
| [ProtocolLifeCycleEvent](./puppeteer.protocollifecycleevent.md) | |
|
| [ProtocolLifeCycleEvent](./puppeteer.protocollifecycleevent.md) | |
|
||||||
| [PuppeteerLifeCycleEvent](./puppeteer.puppeteerlifecycleevent.md) | |
|
| [PuppeteerLifeCycleEvent](./puppeteer.puppeteerlifecycleevent.md) | |
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_label: ActionCondition
|
|
||||||
---
|
|
||||||
|
|
||||||
# ActionCondition type
|
|
||||||
|
|
||||||
#### Signature:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export type ActionCondition = (
|
|
||||||
element: ElementHandle,
|
|
||||||
signal: AbortSignal
|
|
||||||
) => Promise<void>;
|
|
||||||
```
|
|
||||||
|
|
||||||
**References:** [ElementHandle](./puppeteer.elementhandle.md)
|
|
@ -13,6 +13,5 @@ export interface ActionOptions
|
|||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
| Property | Modifiers | Type | Description | Default |
|
| Property | Modifiers | Type | Description | Default |
|
||||||
| ---------- | --------------------- | ----------------------------------------------------- | ----------- | ------- |
|
| -------- | --------------------- | ----------- | ----------- | ------- |
|
||||||
| conditions | | [ActionCondition](./puppeteer.actioncondition.md)\[\] | | |
|
|
||||||
| signal | <code>optional</code> | AbortSignal | | |
|
| signal | <code>optional</code> | AbortSignal | | |
|
||||||
|
@ -10,19 +10,21 @@ Creates a locator for the provided `selector`. See [Locator](./puppeteer.locator
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Frame {
|
class Frame {
|
||||||
locator(selector: string): Locator;
|
locator<Selector extends string>(
|
||||||
|
selector: Selector
|
||||||
|
): Locator<NodeFor<Selector>>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ------ | ----------- |
|
| --------- | -------- | ----------- |
|
||||||
| selector | string | |
|
| selector | Selector | |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
[Locator](./puppeteer.locator.md)
|
[Locator](./puppeteer.locator.md)<[NodeFor](./puppeteer.nodefor.md)<Selector>>
|
||||||
|
|
||||||
## Remarks
|
## Remarks
|
||||||
|
|
||||||
|
@ -8,10 +8,9 @@ sidebar_label: Locator.click
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Locator {
|
class Locator {
|
||||||
abstract click(
|
abstract click<ElementType extends Element>(
|
||||||
clickOptions?: ClickOptions & {
|
this: Locator<ElementType>,
|
||||||
signal?: AbortSignal;
|
options?: Readonly<LocatorClickOptions>
|
||||||
}
|
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -19,8 +18,9 @@ class Locator {
|
|||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| ------------ | --------------------------------------------------------------------------- | ------------ |
|
| --------- | ------------------------------------------------------------------------- | ------------ |
|
||||||
| clickOptions | [ClickOptions](./puppeteer.clickoptions.md) & { signal?: AbortSignal; } | _(Optional)_ |
|
| this | [Locator](./puppeteer.locator.md)<ElementType> | |
|
||||||
|
| options | Readonly<[LocatorClickOptions](./puppeteer.locatorclickoptions.md)> | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
|
@ -10,11 +10,10 @@ Fills out the input identified by the locator using the provided value. The type
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Locator {
|
class Locator {
|
||||||
abstract fill(
|
abstract fill<ElementType extends Element>(
|
||||||
|
this: Locator<ElementType>,
|
||||||
value: string,
|
value: string,
|
||||||
fillOptions?: {
|
options?: Readonly<ActionOptions>
|
||||||
signal?: AbortSignal;
|
|
||||||
}
|
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -22,9 +21,10 @@ class Locator {
|
|||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| ----------- | ------------------------- | ------------ |
|
| --------- | ------------------------------------------------------------- | ------------ |
|
||||||
|
| this | [Locator](./puppeteer.locator.md)<ElementType> | |
|
||||||
| value | string | |
|
| value | string | |
|
||||||
| fillOptions | { signal?: AbortSignal; } | _(Optional)_ |
|
| options | Readonly<[ActionOptions](./puppeteer.actionoptions.md)> | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
|
@ -8,15 +8,19 @@ sidebar_label: Locator.hover
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Locator {
|
class Locator {
|
||||||
abstract hover(hoverOptions?: {signal?: AbortSignal}): Promise<void>;
|
abstract hover<ElementType extends Element>(
|
||||||
|
this: Locator<ElementType>,
|
||||||
|
options?: Readonly<ActionOptions>
|
||||||
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| ------------ | ------------------------- | ------------ |
|
| --------- | ------------------------------------------------------------- | ------------ |
|
||||||
| hoverOptions | { signal?: AbortSignal; } | _(Optional)_ |
|
| this | [Locator](./puppeteer.locator.md)<ElementType> | |
|
||||||
|
| options | Readonly<[ActionOptions](./puppeteer.actionoptions.md)> | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
|
@ -9,23 +9,29 @@ Locators describe a strategy of locating elements and performing an action on th
|
|||||||
#### Signature:
|
#### Signature:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export declare abstract class Locator extends EventEmitter
|
export declare abstract class Locator<T> extends EventEmitter
|
||||||
```
|
```
|
||||||
|
|
||||||
**Extends:** [EventEmitter](./puppeteer.eventemitter.md)
|
**Extends:** [EventEmitter](./puppeteer.eventemitter.md)
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Modifiers | Type | Description |
|
||||||
|
| -------- | --------------------- | ---- | ------------------------------------------------------------ |
|
||||||
|
| \_ | <code>optional</code> | T | Used for nominally typing [Locator](./puppeteer.locator.md). |
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
|
||||||
| Method | Modifiers | Description |
|
| Method | Modifiers | Description |
|
||||||
| ------------------------------------------------------------------------------------------------ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------------------------------------------------------------------------ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| [click(clickOptions)](./puppeteer.locator.click.md) | | |
|
| [click(this, options)](./puppeteer.locator.click.md) | | |
|
||||||
| [fill(value, fillOptions)](./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(hoverOptions)](./puppeteer.locator.hover.md) | | |
|
| [hover(this, options)](./puppeteer.locator.hover.md) | | |
|
||||||
| [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) | | |
|
||||||
| [race(locators)](./puppeteer.locator.race.md) | <code>static</code> | Creates a race between multiple locators but ensures that only a single one acts. |
|
| [race(locators)](./puppeteer.locator.race.md) | <code>static</code> | Creates a race between multiple locators but ensures that only a single one acts. |
|
||||||
| [scroll(scrollOptions)](./puppeteer.locator.scroll.md) | | |
|
| [scroll(this, options)](./puppeteer.locator.scroll.md) | | |
|
||||||
| [setEnsureElementIsInTheViewport(value)](./puppeteer.locator.setensureelementisintheviewport.md) | | |
|
| [setEnsureElementIsInTheViewport(value)](./puppeteer.locator.setensureelementisintheviewport.md) | | |
|
||||||
| [setTimeout(timeout)](./puppeteer.locator.settimeout.md) | | |
|
| [setTimeout(timeout)](./puppeteer.locator.settimeout.md) | | |
|
||||||
| [setVisibility(visibility)](./puppeteer.locator.setvisibility.md) | | |
|
| [setVisibility(visibility)](./puppeteer.locator.setvisibility.md) | | |
|
||||||
|
@ -10,16 +10,18 @@ Creates a race between multiple locators but ensures that only a single one acts
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Locator {
|
class Locator {
|
||||||
static race(locators: Locator[]): Locator;
|
static race<Locators extends Array<Locator<unknown>>>(
|
||||||
|
locators: Locators
|
||||||
|
): Locator<UnionLocatorOf<Locators>>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ------------------------------------- | ----------- |
|
| --------- | -------- | ----------- |
|
||||||
| locators | [Locator](./puppeteer.locator.md)\[\] | |
|
| locators | Locators | |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
[Locator](./puppeteer.locator.md)
|
[Locator](./puppeteer.locator.md)<UnionLocatorOf<Locators>>
|
||||||
|
@ -8,19 +8,19 @@ sidebar_label: Locator.scroll
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Locator {
|
class Locator {
|
||||||
abstract scroll(scrollOptions?: {
|
abstract scroll<ElementType extends Element>(
|
||||||
scrollTop?: number;
|
this: Locator<ElementType>,
|
||||||
scrollLeft?: number;
|
options?: Readonly<LocatorScrollOptions>
|
||||||
signal?: AbortSignal;
|
): Promise<void>;
|
||||||
}): Promise<void>;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| ------------- | ------------------------------------------------------------------ | ------------ |
|
| --------- | --------------------------------------------------------------------------- | ------------ |
|
||||||
| scrollOptions | { scrollTop?: number; scrollLeft?: number; signal?: AbortSignal; } | _(Optional)_ |
|
| this | [Locator](./puppeteer.locator.md)<ElementType> | |
|
||||||
|
| options | Readonly<[LocatorScrollOptions](./puppeteer.locatorscrolloptions.md)> | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
|
13
docs/api/puppeteer.locatorclickoptions.md
Normal file
13
docs/api/puppeteer.locatorclickoptions.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: LocatorClickOptions
|
||||||
|
---
|
||||||
|
|
||||||
|
# LocatorClickOptions type
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type LocatorClickOptions = ClickOptions & ActionOptions;
|
||||||
|
```
|
||||||
|
|
||||||
|
**References:** [ClickOptions](./puppeteer.clickoptions.md), [ActionOptions](./puppeteer.actionoptions.md)
|
20
docs/api/puppeteer.locatorscrolloptions.md
Normal file
20
docs/api/puppeteer.locatorscrolloptions.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: LocatorScrollOptions
|
||||||
|
---
|
||||||
|
|
||||||
|
# LocatorScrollOptions interface
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface LocatorScrollOptions extends ActionOptions
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extends:** [ActionOptions](./puppeteer.actionoptions.md)
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Modifiers | Type | Description | Default |
|
||||||
|
| ---------- | --------------------- | ------ | ----------- | ------- |
|
||||||
|
| scrollLeft | <code>optional</code> | number | | |
|
||||||
|
| scrollTop | <code>optional</code> | number | | |
|
@ -10,19 +10,21 @@ Creates a locator for the provided `selector`. See [Locator](./puppeteer.locator
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Page {
|
class Page {
|
||||||
locator(selector: string): Locator;
|
locator<Selector extends string>(
|
||||||
|
selector: Selector
|
||||||
|
): Locator<NodeFor<Selector>>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ------ | ----------- |
|
| --------- | -------- | ----------- |
|
||||||
| selector | string | |
|
| selector | Selector | |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
[Locator](./puppeteer.locator.md)
|
[Locator](./puppeteer.locator.md)<[NodeFor](./puppeteer.nodefor.md)<Selector>>
|
||||||
|
|
||||||
## Remarks
|
## Remarks
|
||||||
|
|
||||||
|
15
docs/api/puppeteer.predicate.md
Normal file
15
docs/api/puppeteer.predicate.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: Predicate
|
||||||
|
---
|
||||||
|
|
||||||
|
# Predicate type
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type Predicate<From, To extends From = From> =
|
||||||
|
| ((value: From) => value is To)
|
||||||
|
| ((value: From) => Awaitable<boolean>);
|
||||||
|
```
|
||||||
|
|
||||||
|
**References:** [Awaitable](./puppeteer.awaitable.md)
|
@ -424,7 +424,9 @@ export class Frame {
|
|||||||
* Locators API is experimental and we will not follow semver for breaking
|
* Locators API is experimental and we will not follow semver for breaking
|
||||||
* change in the Locators API.
|
* change in the Locators API.
|
||||||
*/
|
*/
|
||||||
locator(selector: string): Locator {
|
locator<Selector extends string>(
|
||||||
|
selector: Selector
|
||||||
|
): Locator<NodeFor<Selector>> {
|
||||||
return Locator.create(this, selector);
|
return Locator.create(this, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,13 +16,20 @@
|
|||||||
|
|
||||||
import {TimeoutError} from '../common/Errors.js';
|
import {TimeoutError} from '../common/Errors.js';
|
||||||
import {EventEmitter} from '../common/EventEmitter.js';
|
import {EventEmitter} from '../common/EventEmitter.js';
|
||||||
|
import {Awaitable, HandleFor, NodeFor} from '../common/types.js';
|
||||||
import {debugError} from '../common/util.js';
|
import {debugError} from '../common/util.js';
|
||||||
import {isErrorLike} from '../util/ErrorLike.js';
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
|
|
||||||
import {ElementHandle, BoundingBox, ClickOptions} from './ElementHandle.js';
|
import {BoundingBox, ClickOptions, ElementHandle} from './ElementHandle.js';
|
||||||
import type {Frame} from './Frame.js';
|
import type {Frame} from './Frame.js';
|
||||||
import type {Page} from './Page.js';
|
import type {Page} from './Page.js';
|
||||||
|
|
||||||
|
interface LocatorContext<T> {
|
||||||
|
conditions?: Set<ActionCondition<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LOCATOR_CONTEXTS = new WeakMap<Locator<unknown>, LocatorContext<never>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -74,19 +81,38 @@ const CONDITION_TIMEOUT = 1_000;
|
|||||||
const WAIT_FOR_FUNCTION_DELAY = 100;
|
const WAIT_FOR_FUNCTION_DELAY = 100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @internal
|
||||||
*/
|
*/
|
||||||
export type ActionCondition = (
|
export type ActionCondition<T> = (
|
||||||
element: ElementHandle,
|
element: HandleFor<T>,
|
||||||
signal: AbortSignal
|
signal: AbortSignal
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type Predicate<From, To extends From = From> =
|
||||||
|
| ((value: From) => value is To)
|
||||||
|
| ((value: From) => Awaitable<boolean>);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface ActionOptions {
|
export interface ActionOptions {
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
conditions: ActionCondition[];
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type LocatorClickOptions = ClickOptions & ActionOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface LocatorScrollOptions extends ActionOptions {
|
||||||
|
scrollTop?: number;
|
||||||
|
scrollLeft?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,6 +134,8 @@ export interface LocatorEventObject {
|
|||||||
[LocatorEmittedEvents.Action]: never;
|
[LocatorEmittedEvents.Action]: never;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UnionLocatorOf<T> = T extends Array<Locator<infer S>> ? S : never;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locators describe a strategy of locating elements and performing an action on
|
* Locators describe a strategy of locating elements and performing an action on
|
||||||
* them. If the action fails because the element is not ready for the action,
|
* them. If the action fails because the element is not ready for the action,
|
||||||
@ -116,12 +144,20 @@ export interface LocatorEventObject {
|
|||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export abstract class Locator extends EventEmitter {
|
export abstract class Locator<T> extends EventEmitter {
|
||||||
|
/**
|
||||||
|
* Used for nominally typing {@link Locator}.
|
||||||
|
*/
|
||||||
|
declare _?: T;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
static create(pageOrFrame: Page | Frame, selector: string): Locator {
|
static create<Selector extends string>(
|
||||||
return new LocatorImpl(pageOrFrame, selector).setTimeout(
|
pageOrFrame: Page | Frame,
|
||||||
|
selector: Selector
|
||||||
|
): Locator<NodeFor<Selector>> {
|
||||||
|
return new NodeLocator<NodeFor<Selector>>(pageOrFrame, selector).setTimeout(
|
||||||
'getDefaultTimeout' in pageOrFrame
|
'getDefaultTimeout' in pageOrFrame
|
||||||
? pageOrFrame.getDefaultTimeout()
|
? pageOrFrame.getDefaultTimeout()
|
||||||
: pageOrFrame.page().getDefaultTimeout()
|
: pageOrFrame.page().getDefaultTimeout()
|
||||||
@ -132,8 +168,23 @@ export abstract class Locator extends EventEmitter {
|
|||||||
* Creates a race between multiple locators but ensures that only a single one
|
* Creates a race between multiple locators but ensures that only a single one
|
||||||
* acts.
|
* acts.
|
||||||
*/
|
*/
|
||||||
static race(locators: Locator[]): Locator {
|
static race<Locators extends Array<Locator<unknown>>>(
|
||||||
return new RaceLocatorImpl(locators);
|
locators: Locators
|
||||||
|
): Locator<UnionLocatorOf<Locators>> {
|
||||||
|
return new RaceLocator(
|
||||||
|
locators as Array<Locator<UnionLocatorOf<Locators>>>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an expectation that is evaluated against located values.
|
||||||
|
*
|
||||||
|
* If the expectations do not match, then the locator will retry.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
expect<S extends T>(predicate: Predicate<T, S>): Locator<S> {
|
||||||
|
return new ExpectedLocator(this, predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
override on<K extends keyof LocatorEventObject>(
|
override on<K extends keyof LocatorEventObject>(
|
||||||
@ -167,10 +218,9 @@ export abstract class Locator extends EventEmitter {
|
|||||||
|
|
||||||
abstract setWaitForStableBoundingBox(value: boolean): this;
|
abstract setWaitForStableBoundingBox(value: boolean): this;
|
||||||
|
|
||||||
abstract click(
|
abstract click<ElementType extends Element>(
|
||||||
clickOptions?: ClickOptions & {
|
this: Locator<ElementType>,
|
||||||
signal?: AbortSignal;
|
options?: Readonly<LocatorClickOptions>
|
||||||
}
|
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -179,24 +229,27 @@ export abstract class Locator extends EventEmitter {
|
|||||||
* method is chosen based on the type. contenteditable, selector, inputs are
|
* method is chosen based on the type. contenteditable, selector, inputs are
|
||||||
* supported.
|
* supported.
|
||||||
*/
|
*/
|
||||||
abstract fill(
|
abstract fill<ElementType extends Element>(
|
||||||
|
this: Locator<ElementType>,
|
||||||
value: string,
|
value: string,
|
||||||
fillOptions?: {signal?: AbortSignal}
|
options?: Readonly<ActionOptions>
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
abstract hover(hoverOptions?: {signal?: AbortSignal}): Promise<void>;
|
abstract hover<ElementType extends Element>(
|
||||||
|
this: Locator<ElementType>,
|
||||||
|
options?: Readonly<ActionOptions>
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
abstract scroll(scrollOptions?: {
|
abstract scroll<ElementType extends Element>(
|
||||||
scrollTop?: number;
|
this: Locator<ElementType>,
|
||||||
scrollLeft?: number;
|
options?: Readonly<LocatorScrollOptions>
|
||||||
signal?: AbortSignal;
|
): Promise<void>;
|
||||||
}): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class LocatorImpl extends Locator {
|
export class NodeLocator<T extends Node> extends Locator<T> {
|
||||||
#pageOrFrame: Page | Frame;
|
#pageOrFrame: Page | Frame;
|
||||||
#selector: string;
|
#selector: string;
|
||||||
#visibility: VisibilityOption = 'visible';
|
#visibility: VisibilityOption = 'visible';
|
||||||
@ -302,8 +355,8 @@ export class LocatorImpl extends Locator {
|
|||||||
/**
|
/**
|
||||||
* Checks if the element is in the viewport and auto-scrolls it if it is not.
|
* Checks if the element is in the viewport and auto-scrolls it if it is not.
|
||||||
*/
|
*/
|
||||||
#ensureElementIsInTheViewportIfNeeded = async (
|
#ensureElementIsInTheViewportIfNeeded = async <ElementType extends Element>(
|
||||||
element: ElementHandle,
|
element: HandleFor<ElementType>,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (!this.#ensureElementIsInTheViewport) {
|
if (!this.#ensureElementIsInTheViewport) {
|
||||||
@ -332,8 +385,8 @@ export class LocatorImpl extends Locator {
|
|||||||
* than 'hidden' or 'collapse' and non-empty bounding box. visibility ===
|
* than 'hidden' or 'collapse' and non-empty bounding box. visibility ===
|
||||||
* 'hidden' means the opposite of that.
|
* 'hidden' means the opposite of that.
|
||||||
*/
|
*/
|
||||||
#waitForVisibilityIfNeeded = async (
|
#waitForVisibilityIfNeeded = async <ElementType extends Element>(
|
||||||
element: ElementHandle,
|
element: HandleFor<ElementType>,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (this.#visibility === null) {
|
if (this.#visibility === null) {
|
||||||
@ -350,11 +403,11 @@ export class LocatorImpl extends Locator {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the element is a button, textarea, input or select, wait till the
|
* If the element has a "disabled" property, wait for the element to be
|
||||||
* element becomes enabled.
|
* enabled.
|
||||||
*/
|
*/
|
||||||
#waitForEnabledIfNeeded = async (
|
#waitForEnabledIfNeeded = async <ElementType extends Element>(
|
||||||
element: ElementHandle,
|
element: HandleFor<ElementType>,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (!this.#waitForEnabled) {
|
if (!this.#waitForEnabled) {
|
||||||
@ -362,8 +415,8 @@ export class LocatorImpl extends Locator {
|
|||||||
}
|
}
|
||||||
await this.#pageOrFrame.waitForFunction(
|
await this.#pageOrFrame.waitForFunction(
|
||||||
el => {
|
el => {
|
||||||
if (['button', 'textarea', 'input', 'select'].includes(el.tagName)) {
|
if ('disabled' in el && typeof el.disabled === 'boolean') {
|
||||||
return !(el as HTMLInputElement).disabled;
|
return !el.disabled;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@ -379,8 +432,8 @@ export class LocatorImpl extends Locator {
|
|||||||
* Compares the bounding box of the element for two consecutive animation
|
* Compares the bounding box of the element for two consecutive animation
|
||||||
* frames and waits till they are the same.
|
* frames and waits till they are the same.
|
||||||
*/
|
*/
|
||||||
#waitForStableBoundingBoxIfNeeded = async (
|
#waitForStableBoundingBoxIfNeeded = async <ElementType extends Element>(
|
||||||
element: ElementHandle,
|
element: HandleFor<ElementType>,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (!this.#waitForStableBoundingBox) {
|
if (!this.#waitForStableBoundingBox) {
|
||||||
@ -423,21 +476,26 @@ export class LocatorImpl extends Locator {
|
|||||||
}, signal);
|
}, signal);
|
||||||
};
|
};
|
||||||
|
|
||||||
async #run(
|
#run(
|
||||||
action: (el: ElementHandle) => Promise<void>,
|
action: (el: HandleFor<T>) => Promise<void>,
|
||||||
options?: ActionOptions
|
signal?: AbortSignal,
|
||||||
|
conditions: Array<ActionCondition<T>> = []
|
||||||
) {
|
) {
|
||||||
await this.#waitForFunction(
|
const globalConditions = [
|
||||||
|
...(LOCATOR_CONTEXTS.get(this)?.conditions?.values() ?? []),
|
||||||
|
] as Array<ActionCondition<T>>;
|
||||||
|
const allConditions = conditions.concat(globalConditions);
|
||||||
|
return this.#waitForFunction(
|
||||||
async signal => {
|
async signal => {
|
||||||
// 1. Select the element without visibility checks.
|
// 1. Select the element without visibility checks.
|
||||||
const element = await this.#pageOrFrame.waitForSelector(
|
const element = (await this.#pageOrFrame.waitForSelector(
|
||||||
this.#selector,
|
this.#selector,
|
||||||
{
|
{
|
||||||
visible: false,
|
visible: false,
|
||||||
timeout: this.#timeout,
|
timeout: this.#timeout,
|
||||||
signal,
|
signal,
|
||||||
}
|
}
|
||||||
);
|
)) as HandleFor<T> | null;
|
||||||
// Retry if no element is found.
|
// Retry if no element is found.
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return false;
|
return false;
|
||||||
@ -446,9 +504,9 @@ export class LocatorImpl extends Locator {
|
|||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
// 2. Perform action specific checks.
|
// 2. Perform action specific checks.
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
options?.conditions.map(check => {
|
allConditions.map(check => {
|
||||||
return check(element, signal);
|
return check(element, signal);
|
||||||
}) || []
|
})
|
||||||
);
|
);
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
// 3. Perform the action
|
// 3. Perform the action
|
||||||
@ -459,29 +517,26 @@ export class LocatorImpl extends Locator {
|
|||||||
void element.dispose().catch(debugError);
|
void element.dispose().catch(debugError);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
options?.signal,
|
signal,
|
||||||
this.#timeout
|
this.#timeout
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async click(
|
async click<ElementType extends Element>(
|
||||||
clickOptions?: ClickOptions & {
|
this: NodeLocator<ElementType>,
|
||||||
signal?: AbortSignal;
|
options?: Readonly<LocatorClickOptions>
|
||||||
}
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return await this.#run(
|
return await this.#run(
|
||||||
async element => {
|
async element => {
|
||||||
await element.click(clickOptions);
|
await element.click(options);
|
||||||
},
|
},
|
||||||
{
|
options?.signal,
|
||||||
signal: clickOptions?.signal,
|
[
|
||||||
conditions: [
|
|
||||||
this.#ensureElementIsInTheViewportIfNeeded,
|
this.#ensureElementIsInTheViewportIfNeeded,
|
||||||
this.#waitForVisibilityIfNeeded,
|
this.#waitForVisibilityIfNeeded,
|
||||||
this.#waitForEnabledIfNeeded,
|
this.#waitForEnabledIfNeeded,
|
||||||
this.#waitForStableBoundingBoxIfNeeded,
|
this.#waitForStableBoundingBoxIfNeeded,
|
||||||
],
|
]
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,13 +546,14 @@ export class LocatorImpl extends Locator {
|
|||||||
* method is chosen based on the type. contenteditable, selector, inputs are
|
* method is chosen based on the type. contenteditable, selector, inputs are
|
||||||
* supported.
|
* supported.
|
||||||
*/
|
*/
|
||||||
async fill(
|
fill<ElementType extends Element>(
|
||||||
|
this: NodeLocator<ElementType>,
|
||||||
value: string,
|
value: string,
|
||||||
fillOptions?: {signal?: AbortSignal}
|
options?: Readonly<ActionOptions>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return await this.#run(
|
return this.#run(
|
||||||
async element => {
|
async element => {
|
||||||
const input = element as ElementHandle<HTMLElement>;
|
const input = element as unknown as ElementHandle<HTMLElement>;
|
||||||
const inputType = await input.evaluate(el => {
|
const inputType = await input.evaluate(el => {
|
||||||
if (el instanceof HTMLSelectElement) {
|
if (el instanceof HTMLSelectElement) {
|
||||||
return 'select';
|
return 'select';
|
||||||
@ -583,40 +639,38 @@ export class LocatorImpl extends Locator {
|
|||||||
throw new Error(`Element cannot be filled out.`);
|
throw new Error(`Element cannot be filled out.`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
options?.signal,
|
||||||
signal: fillOptions?.signal,
|
[
|
||||||
conditions: [
|
|
||||||
this.#ensureElementIsInTheViewportIfNeeded,
|
this.#ensureElementIsInTheViewportIfNeeded,
|
||||||
this.#waitForVisibilityIfNeeded,
|
this.#waitForVisibilityIfNeeded,
|
||||||
this.#waitForEnabledIfNeeded,
|
this.#waitForEnabledIfNeeded,
|
||||||
this.#waitForStableBoundingBoxIfNeeded,
|
this.#waitForStableBoundingBoxIfNeeded,
|
||||||
],
|
]
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async hover(hoverOptions?: {signal?: AbortSignal}): Promise<void> {
|
hover<ElementType extends Element>(
|
||||||
return await this.#run(
|
this: NodeLocator<ElementType>,
|
||||||
|
options?: Readonly<ActionOptions>
|
||||||
|
): Promise<void> {
|
||||||
|
return this.#run(
|
||||||
async element => {
|
async element => {
|
||||||
await element.hover();
|
await element.hover();
|
||||||
},
|
},
|
||||||
{
|
options?.signal,
|
||||||
signal: hoverOptions?.signal,
|
[
|
||||||
conditions: [
|
|
||||||
this.#ensureElementIsInTheViewportIfNeeded,
|
this.#ensureElementIsInTheViewportIfNeeded,
|
||||||
this.#waitForVisibilityIfNeeded,
|
this.#waitForVisibilityIfNeeded,
|
||||||
this.#waitForStableBoundingBoxIfNeeded,
|
this.#waitForStableBoundingBoxIfNeeded,
|
||||||
],
|
]
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async scroll(scrollOptions?: {
|
scroll<ElementType extends Element>(
|
||||||
scrollTop?: number;
|
this: NodeLocator<ElementType>,
|
||||||
scrollLeft?: number;
|
options?: Readonly<LocatorScrollOptions>
|
||||||
signal?: AbortSignal;
|
): Promise<void> {
|
||||||
}): Promise<void> {
|
return this.#run(
|
||||||
return await this.#run(
|
|
||||||
async element => {
|
async element => {
|
||||||
await element.evaluate(
|
await element.evaluate(
|
||||||
(el, scrollTop, scrollLeft) => {
|
(el, scrollTop, scrollLeft) => {
|
||||||
@ -627,29 +681,110 @@ export class LocatorImpl extends Locator {
|
|||||||
el.scrollLeft = scrollLeft;
|
el.scrollLeft = scrollLeft;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollOptions?.scrollTop,
|
options?.scrollTop,
|
||||||
scrollOptions?.scrollLeft
|
options?.scrollLeft
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
{
|
options?.signal,
|
||||||
signal: scrollOptions?.signal,
|
[
|
||||||
conditions: [
|
|
||||||
this.#ensureElementIsInTheViewportIfNeeded,
|
this.#ensureElementIsInTheViewportIfNeeded,
|
||||||
this.#waitForVisibilityIfNeeded,
|
this.#waitForVisibilityIfNeeded,
|
||||||
this.#waitForStableBoundingBoxIfNeeded,
|
this.#waitForStableBoundingBoxIfNeeded,
|
||||||
],
|
]
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ExpectedLocator<From, To extends From> extends Locator<To> {
|
||||||
|
#base: Locator<From>;
|
||||||
|
#predicate: Predicate<From, To>;
|
||||||
|
|
||||||
|
constructor(base: Locator<From>, predicate: Predicate<From, To>) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.#base = base;
|
||||||
|
this.#predicate = predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
override setVisibility(visibility: VisibilityOption): this {
|
||||||
|
this.#base.setVisibility(visibility);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
override setTimeout(timeout: number): this {
|
||||||
|
this.#base.setTimeout(timeout);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
override setEnsureElementIsInTheViewport(value: boolean): this {
|
||||||
|
this.#base.setEnsureElementIsInTheViewport(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
override setWaitForEnabled(value: boolean): this {
|
||||||
|
this.#base.setWaitForEnabled(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
override setWaitForStableBoundingBox(value: boolean): this {
|
||||||
|
this.#base.setWaitForStableBoundingBox(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#condition: ActionCondition<From> = async (handle, signal) => {
|
||||||
|
// TODO(jrandolf): We should remove this once JSHandle has waitForFunction.
|
||||||
|
await (handle as ElementHandle<Node>).frame.waitForFunction(
|
||||||
|
this.#predicate,
|
||||||
|
{signal},
|
||||||
|
handle
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
#insertFilterCondition<
|
||||||
|
FromElement extends Node,
|
||||||
|
ToElement extends FromElement,
|
||||||
|
>(this: ExpectedLocator<FromElement, ToElement>): void {
|
||||||
|
const context = (LOCATOR_CONTEXTS.get(this.#base) ??
|
||||||
|
{}) as LocatorContext<FromElement>;
|
||||||
|
context.conditions ??= new Set();
|
||||||
|
context.conditions.add(this.#condition);
|
||||||
|
LOCATOR_CONTEXTS.set(this.#base, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
override click<FromElement extends Element, ToElement extends FromElement>(
|
||||||
|
this: ExpectedLocator<FromElement, ToElement>,
|
||||||
|
options?: Readonly<LocatorClickOptions>
|
||||||
|
): Promise<void> {
|
||||||
|
this.#insertFilterCondition();
|
||||||
|
return this.#base.click(options);
|
||||||
|
}
|
||||||
|
override fill<FromElement extends Element, ToElement extends FromElement>(
|
||||||
|
this: ExpectedLocator<FromElement, ToElement>,
|
||||||
|
value: string,
|
||||||
|
options?: Readonly<ActionOptions>
|
||||||
|
): Promise<void> {
|
||||||
|
this.#insertFilterCondition();
|
||||||
|
return this.#base.fill(value, options);
|
||||||
|
}
|
||||||
|
override hover<FromElement extends Element, ToElement extends FromElement>(
|
||||||
|
this: ExpectedLocator<FromElement, ToElement>,
|
||||||
|
options?: Readonly<ActionOptions>
|
||||||
|
): Promise<void> {
|
||||||
|
this.#insertFilterCondition();
|
||||||
|
return this.#base.hover(options);
|
||||||
|
}
|
||||||
|
override scroll<FromElement extends Element, ToElement extends FromElement>(
|
||||||
|
this: ExpectedLocator<FromElement, ToElement>,
|
||||||
|
options?: Readonly<LocatorScrollOptions>
|
||||||
|
): Promise<void> {
|
||||||
|
this.#insertFilterCondition();
|
||||||
|
return this.#base.scroll(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
class RaceLocatorImpl extends Locator {
|
class RaceLocator<T> extends Locator<T> {
|
||||||
#locators: Locator[];
|
#locators: Array<Locator<T>>;
|
||||||
|
|
||||||
constructor(locators: Locator[]) {
|
constructor(locators: Array<Locator<T>>) {
|
||||||
super();
|
super();
|
||||||
this.#locators = locators;
|
this.#locators = locators;
|
||||||
}
|
}
|
||||||
@ -689,22 +824,20 @@ class RaceLocatorImpl extends Locator {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #runRace(
|
async #run(
|
||||||
action: (el: Locator, abortSignal: AbortSignal) => Promise<void>,
|
action: (locator: Locator<T>, signal: AbortSignal) => Promise<void>,
|
||||||
options: {
|
signal?: AbortSignal
|
||||||
signal?: AbortSignal;
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
const abortControllers = new WeakMap<Locator, AbortController>();
|
const abortControllers = new WeakMap<Locator<T>, AbortController>();
|
||||||
|
|
||||||
// Abort all locators if the user-provided signal aborts.
|
// Abort all locators if the user-provided signal aborts.
|
||||||
options.signal?.addEventListener('abort', () => {
|
signal?.addEventListener('abort', () => {
|
||||||
for (const locator of this.#locators) {
|
for (const locator of this.#locators) {
|
||||||
abortControllers.get(locator)?.abort();
|
abortControllers.get(locator)?.abort();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleLocatorAction = (locator: Locator): (() => void) => {
|
const handleLocatorAction = (locator: Locator<T>): (() => void) => {
|
||||||
return () => {
|
return () => {
|
||||||
// When one locator is ready to act, we will abort other locators.
|
// When one locator is ready to act, we will abort other locators.
|
||||||
for (const other of this.#locators) {
|
for (const other of this.#locators) {
|
||||||
@ -716,7 +849,7 @@ class RaceLocatorImpl extends Locator {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const createAbortController = (locator: Locator): AbortController => {
|
const createAbortController = (locator: Locator<T>): AbortController => {
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
abortControllers.set(locator, abortController);
|
abortControllers.set(locator, abortController);
|
||||||
return abortController;
|
return abortController;
|
||||||
@ -731,7 +864,7 @@ class RaceLocatorImpl extends Locator {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
options.signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
|
|
||||||
const rejected = results.filter(
|
const rejected = results.filter(
|
||||||
(result): result is PromiseRejectedResult => {
|
(result): result is PromiseRejectedResult => {
|
||||||
@ -754,70 +887,52 @@ class RaceLocatorImpl extends Locator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override async click(
|
async click<ElementType extends Element>(
|
||||||
clickOptions?: ClickOptions & {
|
this: RaceLocator<ElementType>,
|
||||||
signal?: AbortSignal;
|
options?: Readonly<LocatorClickOptions>
|
||||||
}
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return await this.#runRace(
|
return await this.#run(
|
||||||
(locator, abortSignal) => {
|
(locator, signal) => {
|
||||||
return locator.click({
|
return locator.click({...options, signal});
|
||||||
...clickOptions,
|
|
||||||
signal: abortSignal,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
{
|
options?.signal
|
||||||
signal: clickOptions?.signal,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async fill(
|
async fill<ElementType extends Element>(
|
||||||
|
this: RaceLocator<ElementType>,
|
||||||
value: string,
|
value: string,
|
||||||
fillOptions?: {signal?: AbortSignal}
|
options?: Readonly<ActionOptions>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return await this.#runRace(
|
return await this.#run(
|
||||||
(locator, abortSignal) => {
|
(locator, signal) => {
|
||||||
return locator.fill(value, {
|
return locator.fill(value, {...options, signal});
|
||||||
...fillOptions,
|
|
||||||
signal: abortSignal,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
{
|
options?.signal
|
||||||
signal: fillOptions?.signal,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async hover(hoverOptions?: {signal?: AbortSignal}): Promise<void> {
|
async hover<ElementType extends Element>(
|
||||||
return await this.#runRace(
|
this: RaceLocator<ElementType>,
|
||||||
(locator, abortSignal) => {
|
options?: Readonly<ActionOptions>
|
||||||
return locator.hover({
|
): Promise<void> {
|
||||||
...hoverOptions,
|
return await this.#run(
|
||||||
signal: abortSignal,
|
(locator, signal) => {
|
||||||
});
|
return locator.hover({...options, signal});
|
||||||
},
|
},
|
||||||
{
|
options?.signal
|
||||||
signal: hoverOptions?.signal,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async scroll(scrollOptions?: {
|
async scroll<ElementType extends Element>(
|
||||||
scrollTop?: number;
|
this: RaceLocator<ElementType>,
|
||||||
scrollLeft?: number;
|
options?: Readonly<LocatorScrollOptions>
|
||||||
signal?: AbortSignal;
|
): Promise<void> {
|
||||||
}): Promise<void> {
|
return await this.#run(
|
||||||
return await this.#runRace(
|
(locator, signal) => {
|
||||||
(locator, abortSignal) => {
|
return locator.scroll({...options, signal});
|
||||||
return locator.scroll({
|
|
||||||
...scrollOptions,
|
|
||||||
signal: abortSignal,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
{
|
options?.signal
|
||||||
signal: scrollOptions?.signal,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -831,7 +831,9 @@ export class Page extends EventEmitter {
|
|||||||
* Locators API is experimental and we will not follow semver for breaking
|
* Locators API is experimental and we will not follow semver for breaking
|
||||||
* change in the Locators API.
|
* change in the Locators API.
|
||||||
*/
|
*/
|
||||||
locator(selector: string): Locator {
|
locator<Selector extends string>(
|
||||||
|
selector: Selector
|
||||||
|
): Locator<NodeFor<Selector>> {
|
||||||
return Locator.create(this, selector);
|
return Locator.create(this, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -840,7 +842,7 @@ export class Page extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
locatorRace(locators: Locator[]): Locator {
|
locatorRace(locators: Array<Locator<Node>>): Locator<Node> {
|
||||||
return Locator.race(locators);
|
return Locator.race(locators);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2399,6 +2399,12 @@
|
|||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[locator.spec] Locator Locator.prototype.expect should resolve as soon as the predicate matches",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
|
"expectations": ["TIMEOUT"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[mouse.spec] Mouse should reset properly",
|
"testIdPattern": "[mouse.spec] Mouse should reset properly",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
@ -538,4 +538,59 @@ describe('Locator', function () {
|
|||||||
await expect(result).resolves.toEqual(undefined);
|
await expect(result).resolves.toEqual(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Locator.prototype.expect', () => {
|
||||||
|
it('should not resolve if the predicate does not match', async () => {
|
||||||
|
const clock = sinon.useFakeTimers({
|
||||||
|
shouldClearNativeTimers: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const {page} = await getTestState();
|
||||||
|
page.setDefaultTimeout(5000);
|
||||||
|
await page.setContent(`<div>test</div>`);
|
||||||
|
const result = page
|
||||||
|
.locator('::-p-text(test)')
|
||||||
|
.expect((element): Promise<boolean> => {
|
||||||
|
return Promise.resolve(
|
||||||
|
element.getAttribute('clickable') === 'true'
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.hover();
|
||||||
|
clock.tick(5100);
|
||||||
|
await expect(result).rejects.toEqual(
|
||||||
|
new TimeoutError('waitForFunction timed out. The timeout is 5000ms.')
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
clock.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve as soon as the predicate matches', async () => {
|
||||||
|
const clock = sinon.useFakeTimers({
|
||||||
|
shouldClearNativeTimers: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const {page} = await getTestState();
|
||||||
|
page.setDefaultTimeout(5000);
|
||||||
|
await page.setContent(`<div>test</div>`);
|
||||||
|
const result = page
|
||||||
|
.locator('::-p-text(test)')
|
||||||
|
.expect(async element => {
|
||||||
|
return element.getAttribute('clickable') === 'true';
|
||||||
|
})
|
||||||
|
.expect(element => {
|
||||||
|
return element.getAttribute('clickable') === 'true';
|
||||||
|
})
|
||||||
|
.hover();
|
||||||
|
clock.tick(2000);
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.querySelector('div')?.setAttribute('clickable', 'true');
|
||||||
|
});
|
||||||
|
clock.tick(2000);
|
||||||
|
await expect(result).resolves.toEqual(undefined);
|
||||||
|
} finally {
|
||||||
|
clock.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user