mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
refactor: move locators to separate files (#10591)
This commit is contained in:
parent
4a3b8b2d9e
commit
0715ad8281
@ -181,4 +181,5 @@ sidebar_label: API
|
|||||||
| [PuppeteerNodeLaunchOptions](./puppeteer.puppeteernodelaunchoptions.md) | Utility type exposed to enable users to define options that can be passed to <code>puppeteer.launch</code> without having to list the set of all types. |
|
| [PuppeteerNodeLaunchOptions](./puppeteer.puppeteernodelaunchoptions.md) | Utility type exposed to enable users to define options that can be passed to <code>puppeteer.launch</code> without having to list the set of all types. |
|
||||||
| [ResourceType](./puppeteer.resourcetype.md) | Resource types for HTTPRequests as perceived by the rendering engine. |
|
| [ResourceType](./puppeteer.resourcetype.md) | Resource types for HTTPRequests as perceived by the rendering engine. |
|
||||||
| [TargetFilterCallback](./puppeteer.targetfiltercallback.md) | |
|
| [TargetFilterCallback](./puppeteer.targetfiltercallback.md) | |
|
||||||
|
| [UnionLocatorOf](./puppeteer.unionlocatorof.md) | |
|
||||||
| [VisibilityOption](./puppeteer.visibilityoption.md) | |
|
| [VisibilityOption](./puppeteer.visibilityoption.md) | |
|
||||||
|
@ -24,4 +24,4 @@ class Locator {
|
|||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
[Locator](./puppeteer.locator.md)<UnionLocatorOf<Locators>>
|
[Locator](./puppeteer.locator.md)<[UnionLocatorOf](./puppeteer.unionlocatorof.md)<Locators>>
|
||||||
|
13
docs/api/puppeteer.unionlocatorof.md
Normal file
13
docs/api/puppeteer.unionlocatorof.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: UnionLocatorOf
|
||||||
|
---
|
||||||
|
|
||||||
|
# UnionLocatorOf type
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type UnionLocatorOf<T> = T extends Array<Locator<infer S>> ? S : never;
|
||||||
|
```
|
||||||
|
|
||||||
|
**References:** [Locator](./puppeteer.locator.md)
|
@ -39,7 +39,7 @@ import {TaskManager} from '../common/WaitTask.js';
|
|||||||
|
|
||||||
import {KeyboardTypeOptions} from './Input.js';
|
import {KeyboardTypeOptions} from './Input.js';
|
||||||
import {JSHandle} from './JSHandle.js';
|
import {JSHandle} from './JSHandle.js';
|
||||||
import {Locator} from './Locator.js';
|
import {Locator, NodeLocator} from './locators/locators.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -427,7 +427,7 @@ export class Frame {
|
|||||||
locator<Selector extends string>(
|
locator<Selector extends string>(
|
||||||
selector: Selector
|
selector: Selector
|
||||||
): Locator<NodeFor<Selector>> {
|
): Locator<NodeFor<Selector>> {
|
||||||
return Locator.create(this, selector);
|
return NodeLocator.create(this, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,9 +71,9 @@ import type {
|
|||||||
FrameAddStyleTagOptions,
|
FrameAddStyleTagOptions,
|
||||||
FrameWaitForFunctionOptions,
|
FrameWaitForFunctionOptions,
|
||||||
} from './Frame.js';
|
} from './Frame.js';
|
||||||
import {Keyboard, Mouse, Touchscreen, KeyboardTypeOptions} from './Input.js';
|
import {Keyboard, KeyboardTypeOptions, Mouse, Touchscreen} from './Input.js';
|
||||||
import type {JSHandle} from './JSHandle.js';
|
import type {JSHandle} from './JSHandle.js';
|
||||||
import {Locator} from './Locator.js';
|
import {Locator, NodeLocator} from './locators/locators.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -834,7 +834,7 @@ export class Page extends EventEmitter {
|
|||||||
locator<Selector extends string>(
|
locator<Selector extends string>(
|
||||||
selector: Selector
|
selector: Selector
|
||||||
): Locator<NodeFor<Selector>> {
|
): Locator<NodeFor<Selector>> {
|
||||||
return Locator.create(this, selector);
|
return NodeLocator.create(this, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,4 +23,4 @@ export * from './Input.js';
|
|||||||
export * from './Frame.js';
|
export * from './Frame.js';
|
||||||
export * from './HTTPResponse.js';
|
export * from './HTTPResponse.js';
|
||||||
export * from './HTTPRequest.js';
|
export * from './HTTPRequest.js';
|
||||||
export * from './Locator.js';
|
export * from './locators/locators.js';
|
||||||
|
106
packages/puppeteer-core/src/api/locators/ExpectedLocator.ts
Normal file
106
packages/puppeteer-core/src/api/locators/ExpectedLocator.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import {Awaitable} from '../../common/common.js';
|
||||||
|
import {ElementHandle} from '../ElementHandle.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActionOptions,
|
||||||
|
LOCATOR_CONTEXTS,
|
||||||
|
Locator,
|
||||||
|
LocatorClickOptions,
|
||||||
|
LocatorContext,
|
||||||
|
LocatorScrollOptions,
|
||||||
|
VisibilityOption,
|
||||||
|
type ActionCondition,
|
||||||
|
} from './locators.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type Predicate<From, To extends From = From> =
|
||||||
|
| ((value: From) => value is To)
|
||||||
|
| ((value: From) => Awaitable<boolean>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export 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);
|
||||||
|
}
|
||||||
|
}
|
201
packages/puppeteer-core/src/api/locators/Locator.ts
Normal file
201
packages/puppeteer-core/src/api/locators/Locator.ts
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import {EventEmitter} from '../../common/EventEmitter.js';
|
||||||
|
import {ClickOptions} from '../ElementHandle.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ExpectedLocator,
|
||||||
|
Predicate,
|
||||||
|
RaceLocator,
|
||||||
|
UnionLocatorOf,
|
||||||
|
type ActionCondition,
|
||||||
|
} from './locators.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export interface LocatorContext<T> {
|
||||||
|
conditions?: Set<ActionCondition<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export const LOCATOR_CONTEXTS = new WeakMap<
|
||||||
|
Locator<unknown>,
|
||||||
|
LocatorContext<never>
|
||||||
|
>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type VisibilityOption = 'hidden' | 'visible' | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface LocatorOptions {
|
||||||
|
/**
|
||||||
|
* Whether to wait for the element to be `visible` or `hidden`. `null` to
|
||||||
|
* disable visibility checks.
|
||||||
|
*/
|
||||||
|
visibility: VisibilityOption;
|
||||||
|
/**
|
||||||
|
* Total timeout for the entire locator operation.
|
||||||
|
*
|
||||||
|
* Pass `0` to disable timeout.
|
||||||
|
*
|
||||||
|
* @defaultValue `Page.getDefaultTimeout()`
|
||||||
|
*/
|
||||||
|
timeout: number;
|
||||||
|
/**
|
||||||
|
* Whether to scroll the element into viewport if not in the viewprot already.
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
|
ensureElementIsInTheViewport: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to wait for input elements to become enabled before the action.
|
||||||
|
* Applicable to `click` and `fill` actions.
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
|
waitForEnabled: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to wait for the element's bounding box to be same between two
|
||||||
|
* animation frames.
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
|
waitForStableBoundingBox: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface ActionOptions {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type LocatorClickOptions = ClickOptions & ActionOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface LocatorScrollOptions extends ActionOptions {
|
||||||
|
scrollTop?: number;
|
||||||
|
scrollLeft?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All the events that a locator instance may emit.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export enum LocatorEmittedEvents {
|
||||||
|
/**
|
||||||
|
* Emitted every time before the locator performs an action on the located element(s).
|
||||||
|
*/
|
||||||
|
Action = 'action',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface LocatorEventObject {
|
||||||
|
[LocatorEmittedEvents.Action]: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
* the whole operation is retried. Various preconditions for a successful action
|
||||||
|
* are checked automatically.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export abstract class Locator<T> extends EventEmitter {
|
||||||
|
/**
|
||||||
|
* Used for nominally typing {@link Locator}.
|
||||||
|
*/
|
||||||
|
declare _?: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a race between multiple locators but ensures that only a single one
|
||||||
|
* acts.
|
||||||
|
*/
|
||||||
|
static race<Locators extends Array<Locator<unknown>>>(
|
||||||
|
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>(
|
||||||
|
eventName: K,
|
||||||
|
handler: (event: LocatorEventObject[K]) => void
|
||||||
|
): this {
|
||||||
|
return super.on(eventName, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
override once<K extends keyof LocatorEventObject>(
|
||||||
|
eventName: K,
|
||||||
|
handler: (event: LocatorEventObject[K]) => void
|
||||||
|
): this {
|
||||||
|
return super.once(eventName, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
override off<K extends keyof LocatorEventObject>(
|
||||||
|
eventName: K,
|
||||||
|
handler: (event: LocatorEventObject[K]) => void
|
||||||
|
): this {
|
||||||
|
return super.off(eventName, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract setVisibility(visibility: VisibilityOption): this;
|
||||||
|
|
||||||
|
abstract setTimeout(timeout: number): this;
|
||||||
|
|
||||||
|
abstract setEnsureElementIsInTheViewport(value: boolean): this;
|
||||||
|
|
||||||
|
abstract setWaitForEnabled(value: boolean): this;
|
||||||
|
|
||||||
|
abstract setWaitForStableBoundingBox(value: boolean): this;
|
||||||
|
|
||||||
|
abstract click<ElementType extends Element>(
|
||||||
|
this: Locator<ElementType>,
|
||||||
|
options?: Readonly<LocatorClickOptions>
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
abstract fill<ElementType extends Element>(
|
||||||
|
this: Locator<ElementType>,
|
||||||
|
value: string,
|
||||||
|
options?: Readonly<ActionOptions>
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
abstract hover<ElementType extends Element>(
|
||||||
|
this: Locator<ElementType>,
|
||||||
|
options?: Readonly<ActionOptions>
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
abstract scroll<ElementType extends Element>(
|
||||||
|
this: Locator<ElementType>,
|
||||||
|
options?: Readonly<LocatorScrollOptions>
|
||||||
|
): Promise<void>;
|
||||||
|
}
|
@ -1,75 +1,20 @@
|
|||||||
/**
|
import {TimeoutError} from '../../common/Errors.js';
|
||||||
* Copyright 2023 Google Inc. All rights reserved.
|
import {HandleFor, NodeFor} from '../../common/types.js';
|
||||||
*
|
import {debugError} from '../../common/util.js';
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
import {isErrorLike} from '../../util/ErrorLike.js';
|
||||||
* you may not use this file except in compliance with the License.
|
import {BoundingBox, ElementHandle} from '../ElementHandle.js';
|
||||||
* You may obtain a copy of the License at
|
import type {Frame} from '../Frame.js';
|
||||||
*
|
import type {Page} from '../Page.js';
|
||||||
* 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 {TimeoutError} from '../common/Errors.js';
|
import {
|
||||||
import {EventEmitter} from '../common/EventEmitter.js';
|
ActionOptions,
|
||||||
import {Awaitable, HandleFor, NodeFor} from '../common/types.js';
|
LOCATOR_CONTEXTS,
|
||||||
import {debugError} from '../common/util.js';
|
Locator,
|
||||||
import {isErrorLike} from '../util/ErrorLike.js';
|
LocatorClickOptions,
|
||||||
|
LocatorEmittedEvents,
|
||||||
import {BoundingBox, ClickOptions, ElementHandle} from './ElementHandle.js';
|
LocatorScrollOptions,
|
||||||
import type {Frame} from './Frame.js';
|
VisibilityOption,
|
||||||
import type {Page} from './Page.js';
|
} from './locators.js';
|
||||||
|
|
||||||
interface LocatorContext<T> {
|
|
||||||
conditions?: Set<ActionCondition<T>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LOCATOR_CONTEXTS = new WeakMap<Locator<unknown>, LocatorContext<never>>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export type VisibilityOption = 'hidden' | 'visible' | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface LocatorOptions {
|
|
||||||
/**
|
|
||||||
* Whether to wait for the element to be `visible` or `hidden`. `null` to
|
|
||||||
* disable visibility checks.
|
|
||||||
*/
|
|
||||||
visibility: VisibilityOption;
|
|
||||||
/**
|
|
||||||
* Total timeout for the entire locator operation.
|
|
||||||
*
|
|
||||||
* Pass `0` to disable timeout.
|
|
||||||
*
|
|
||||||
* @defaultValue `Page.getDefaultTimeout()`
|
|
||||||
*/
|
|
||||||
timeout: number;
|
|
||||||
/**
|
|
||||||
* Whether to scroll the element into viewport if not in the viewprot already.
|
|
||||||
* @defaultValue `true`
|
|
||||||
*/
|
|
||||||
ensureElementIsInTheViewport: boolean;
|
|
||||||
/**
|
|
||||||
* Whether to wait for input elements to become enabled before the action.
|
|
||||||
* Applicable to `click` and `fill` actions.
|
|
||||||
* @defaultValue `true`
|
|
||||||
*/
|
|
||||||
waitForEnabled: boolean;
|
|
||||||
/**
|
|
||||||
* Whether to wait for the element's bounding box to be same between two
|
|
||||||
* animation frames.
|
|
||||||
* @defaultValue `true`
|
|
||||||
*/
|
|
||||||
waitForStableBoundingBox: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timeout for individual operations inside the locator. On errors the
|
* Timeout for individual operations inside the locator. On errors the
|
||||||
@ -77,7 +22,7 @@ export interface LocatorOptions {
|
|||||||
* exceeded. This timeout should be generally much lower as locating an
|
* exceeded. This timeout should be generally much lower as locating an
|
||||||
* element means multiple asynchronious operations.
|
* element means multiple asynchronious operations.
|
||||||
*/
|
*/
|
||||||
const CONDITION_TIMEOUT = 1_000;
|
const CONDITION_TIMEOUT = 1000;
|
||||||
const WAIT_FOR_FUNCTION_DELAY = 100;
|
const WAIT_FOR_FUNCTION_DELAY = 100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,67 +34,9 @@ export type ActionCondition<T> = (
|
|||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @internal
|
||||||
*/
|
*/
|
||||||
export type Predicate<From, To extends From = From> =
|
export class NodeLocator<T extends Node> extends Locator<T> {
|
||||||
| ((value: From) => value is To)
|
|
||||||
| ((value: From) => Awaitable<boolean>);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface ActionOptions {
|
|
||||||
signal?: AbortSignal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export type LocatorClickOptions = ClickOptions & ActionOptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface LocatorScrollOptions extends ActionOptions {
|
|
||||||
scrollTop?: number;
|
|
||||||
scrollLeft?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All the events that a locator instance may emit.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export enum LocatorEmittedEvents {
|
|
||||||
/**
|
|
||||||
* Emitted every time before the locator performs an action on the located element(s).
|
|
||||||
*/
|
|
||||||
Action = 'action',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface LocatorEventObject {
|
|
||||||
[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
|
|
||||||
* them. If the action fails because the element is not ready for the action,
|
|
||||||
* the whole operation is retried. Various preconditions for a successful action
|
|
||||||
* are checked automatically.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export abstract class Locator<T> extends EventEmitter {
|
|
||||||
/**
|
|
||||||
* Used for nominally typing {@link Locator}.
|
|
||||||
*/
|
|
||||||
declare _?: T;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -164,96 +51,10 @@ export abstract class Locator<T> extends EventEmitter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a race between multiple locators but ensures that only a single one
|
|
||||||
* acts.
|
|
||||||
*/
|
|
||||||
static race<Locators extends Array<Locator<unknown>>>(
|
|
||||||
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>(
|
|
||||||
eventName: K,
|
|
||||||
handler: (event: LocatorEventObject[K]) => void
|
|
||||||
): this {
|
|
||||||
return super.on(eventName, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
override once<K extends keyof LocatorEventObject>(
|
|
||||||
eventName: K,
|
|
||||||
handler: (event: LocatorEventObject[K]) => void
|
|
||||||
): this {
|
|
||||||
return super.once(eventName, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
override off<K extends keyof LocatorEventObject>(
|
|
||||||
eventName: K,
|
|
||||||
handler: (event: LocatorEventObject[K]) => void
|
|
||||||
): this {
|
|
||||||
return super.off(eventName, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract setVisibility(visibility: VisibilityOption): this;
|
|
||||||
|
|
||||||
abstract setTimeout(timeout: number): this;
|
|
||||||
|
|
||||||
abstract setEnsureElementIsInTheViewport(value: boolean): this;
|
|
||||||
|
|
||||||
abstract setWaitForEnabled(value: boolean): this;
|
|
||||||
|
|
||||||
abstract setWaitForStableBoundingBox(value: boolean): this;
|
|
||||||
|
|
||||||
abstract click<ElementType extends Element>(
|
|
||||||
this: Locator<ElementType>,
|
|
||||||
options?: Readonly<LocatorClickOptions>
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
abstract fill<ElementType extends Element>(
|
|
||||||
this: Locator<ElementType>,
|
|
||||||
value: string,
|
|
||||||
options?: Readonly<ActionOptions>
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
abstract hover<ElementType extends Element>(
|
|
||||||
this: Locator<ElementType>,
|
|
||||||
options?: Readonly<ActionOptions>
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
abstract scroll<ElementType extends Element>(
|
|
||||||
this: Locator<ElementType>,
|
|
||||||
options?: Readonly<LocatorScrollOptions>
|
|
||||||
): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
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';
|
||||||
#timeout = 30_000;
|
#timeout = 30000;
|
||||||
#ensureElementIsInTheViewport = true;
|
#ensureElementIsInTheViewport = true;
|
||||||
#waitForEnabled = true;
|
#waitForEnabled = true;
|
||||||
#waitForStableBoundingBox = true;
|
#waitForStableBoundingBox = true;
|
||||||
@ -694,245 +495,3 @@ export class NodeLocator<T extends Node> extends Locator<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
class RaceLocator<T> extends Locator<T> {
|
|
||||||
#locators: Array<Locator<T>>;
|
|
||||||
|
|
||||||
constructor(locators: Array<Locator<T>>) {
|
|
||||||
super();
|
|
||||||
this.#locators = locators;
|
|
||||||
}
|
|
||||||
|
|
||||||
override setVisibility(visibility: VisibilityOption): this {
|
|
||||||
for (const locator of this.#locators) {
|
|
||||||
locator.setVisibility(visibility);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
override setTimeout(timeout: number): this {
|
|
||||||
for (const locator of this.#locators) {
|
|
||||||
locator.setTimeout(timeout);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
override setEnsureElementIsInTheViewport(value: boolean): this {
|
|
||||||
for (const locator of this.#locators) {
|
|
||||||
locator.setEnsureElementIsInTheViewport(value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
override setWaitForEnabled(value: boolean): this {
|
|
||||||
for (const locator of this.#locators) {
|
|
||||||
locator.setWaitForEnabled(value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
override setWaitForStableBoundingBox(value: boolean): this {
|
|
||||||
for (const locator of this.#locators) {
|
|
||||||
locator.setWaitForStableBoundingBox(value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
async #run(
|
|
||||||
action: (locator: Locator<T>, signal: AbortSignal) => Promise<void>,
|
|
||||||
signal?: AbortSignal
|
|
||||||
) {
|
|
||||||
const abortControllers = new WeakMap<Locator<T>, AbortController>();
|
|
||||||
|
|
||||||
// Abort all locators if the user-provided signal aborts.
|
|
||||||
signal?.addEventListener('abort', () => {
|
|
||||||
for (const locator of this.#locators) {
|
|
||||||
abortControllers.get(locator)?.abort();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleLocatorAction = (locator: Locator<T>): (() => void) => {
|
|
||||||
return () => {
|
|
||||||
// When one locator is ready to act, we will abort other locators.
|
|
||||||
for (const other of this.#locators) {
|
|
||||||
if (other !== locator) {
|
|
||||||
abortControllers.get(other)?.abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.emit(LocatorEmittedEvents.Action);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createAbortController = (locator: Locator<T>): AbortController => {
|
|
||||||
const abortController = new AbortController();
|
|
||||||
abortControllers.set(locator, abortController);
|
|
||||||
return abortController;
|
|
||||||
};
|
|
||||||
|
|
||||||
const results = await Promise.allSettled(
|
|
||||||
this.#locators.map(locator => {
|
|
||||||
return action(
|
|
||||||
locator.on(LocatorEmittedEvents.Action, handleLocatorAction(locator)),
|
|
||||||
createAbortController(locator).signal
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
signal?.throwIfAborted();
|
|
||||||
|
|
||||||
const rejected = results.filter(
|
|
||||||
(result): result is PromiseRejectedResult => {
|
|
||||||
return result.status === 'rejected';
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// If some locators are fulfilled, do not throw.
|
|
||||||
if (rejected.length !== results.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const result of rejected) {
|
|
||||||
const reason = result.reason;
|
|
||||||
// AbortError is be an expected result of a race.
|
|
||||||
if (isErrorLike(reason) && reason.name === 'AbortError') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw reason;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async click<ElementType extends Element>(
|
|
||||||
this: RaceLocator<ElementType>,
|
|
||||||
options?: Readonly<LocatorClickOptions>
|
|
||||||
): Promise<void> {
|
|
||||||
return await this.#run(
|
|
||||||
(locator, signal) => {
|
|
||||||
return locator.click({...options, signal});
|
|
||||||
},
|
|
||||||
options?.signal
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fill<ElementType extends Element>(
|
|
||||||
this: RaceLocator<ElementType>,
|
|
||||||
value: string,
|
|
||||||
options?: Readonly<ActionOptions>
|
|
||||||
): Promise<void> {
|
|
||||||
return await this.#run(
|
|
||||||
(locator, signal) => {
|
|
||||||
return locator.fill(value, {...options, signal});
|
|
||||||
},
|
|
||||||
options?.signal
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async hover<ElementType extends Element>(
|
|
||||||
this: RaceLocator<ElementType>,
|
|
||||||
options?: Readonly<ActionOptions>
|
|
||||||
): Promise<void> {
|
|
||||||
return await this.#run(
|
|
||||||
(locator, signal) => {
|
|
||||||
return locator.hover({...options, signal});
|
|
||||||
},
|
|
||||||
options?.signal
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async scroll<ElementType extends Element>(
|
|
||||||
this: RaceLocator<ElementType>,
|
|
||||||
options?: Readonly<LocatorScrollOptions>
|
|
||||||
): Promise<void> {
|
|
||||||
return await this.#run(
|
|
||||||
(locator, signal) => {
|
|
||||||
return locator.scroll({...options, signal});
|
|
||||||
},
|
|
||||||
options?.signal
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
174
packages/puppeteer-core/src/api/locators/RaceLocator.ts
Normal file
174
packages/puppeteer-core/src/api/locators/RaceLocator.ts
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import {isErrorLike} from '../../util/ErrorLike.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Locator,
|
||||||
|
VisibilityOption,
|
||||||
|
LocatorEmittedEvents,
|
||||||
|
LocatorClickOptions,
|
||||||
|
ActionOptions,
|
||||||
|
LocatorScrollOptions,
|
||||||
|
} from './locators.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type UnionLocatorOf<T> = T extends Array<Locator<infer S>> ? S : never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export class RaceLocator<T> extends Locator<T> {
|
||||||
|
#locators: Array<Locator<T>>;
|
||||||
|
|
||||||
|
constructor(locators: Array<Locator<T>>) {
|
||||||
|
super();
|
||||||
|
this.#locators = locators;
|
||||||
|
}
|
||||||
|
|
||||||
|
override setVisibility(visibility: VisibilityOption): this {
|
||||||
|
for (const locator of this.#locators) {
|
||||||
|
locator.setVisibility(visibility);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
override setTimeout(timeout: number): this {
|
||||||
|
for (const locator of this.#locators) {
|
||||||
|
locator.setTimeout(timeout);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
override setEnsureElementIsInTheViewport(value: boolean): this {
|
||||||
|
for (const locator of this.#locators) {
|
||||||
|
locator.setEnsureElementIsInTheViewport(value);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
override setWaitForEnabled(value: boolean): this {
|
||||||
|
for (const locator of this.#locators) {
|
||||||
|
locator.setWaitForEnabled(value);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
override setWaitForStableBoundingBox(value: boolean): this {
|
||||||
|
for (const locator of this.#locators) {
|
||||||
|
locator.setWaitForStableBoundingBox(value);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #run(
|
||||||
|
action: (locator: Locator<T>, signal: AbortSignal) => Promise<void>,
|
||||||
|
signal?: AbortSignal
|
||||||
|
) {
|
||||||
|
const abortControllers = new WeakMap<Locator<T>, AbortController>();
|
||||||
|
|
||||||
|
// Abort all locators if the user-provided signal aborts.
|
||||||
|
signal?.addEventListener('abort', () => {
|
||||||
|
for (const locator of this.#locators) {
|
||||||
|
abortControllers.get(locator)?.abort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLocatorAction = (locator: Locator<T>): (() => void) => {
|
||||||
|
return () => {
|
||||||
|
// When one locator is ready to act, we will abort other locators.
|
||||||
|
for (const other of this.#locators) {
|
||||||
|
if (other !== locator) {
|
||||||
|
abortControllers.get(other)?.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit(LocatorEmittedEvents.Action);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAbortController = (locator: Locator<T>): AbortController => {
|
||||||
|
const abortController = new AbortController();
|
||||||
|
abortControllers.set(locator, abortController);
|
||||||
|
return abortController;
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
this.#locators.map(locator => {
|
||||||
|
return action(
|
||||||
|
locator.on(LocatorEmittedEvents.Action, handleLocatorAction(locator)),
|
||||||
|
createAbortController(locator).signal
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
signal?.throwIfAborted();
|
||||||
|
|
||||||
|
const rejected = results.filter(
|
||||||
|
(result): result is PromiseRejectedResult => {
|
||||||
|
return result.status === 'rejected';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If some locators are fulfilled, do not throw.
|
||||||
|
if (rejected.length !== results.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const result of rejected) {
|
||||||
|
const reason = result.reason;
|
||||||
|
// AbortError is be an expected result of a race.
|
||||||
|
if (isErrorLike(reason) && reason.name === 'AbortError') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async click<ElementType extends Element>(
|
||||||
|
this: RaceLocator<ElementType>,
|
||||||
|
options?: Readonly<LocatorClickOptions>
|
||||||
|
): Promise<void> {
|
||||||
|
return await this.#run(
|
||||||
|
(locator, signal) => {
|
||||||
|
return locator.click({...options, signal});
|
||||||
|
},
|
||||||
|
options?.signal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fill<ElementType extends Element>(
|
||||||
|
this: RaceLocator<ElementType>,
|
||||||
|
value: string,
|
||||||
|
options?: Readonly<ActionOptions>
|
||||||
|
): Promise<void> {
|
||||||
|
return await this.#run(
|
||||||
|
(locator, signal) => {
|
||||||
|
return locator.fill(value, {...options, signal});
|
||||||
|
},
|
||||||
|
options?.signal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async hover<ElementType extends Element>(
|
||||||
|
this: RaceLocator<ElementType>,
|
||||||
|
options?: Readonly<ActionOptions>
|
||||||
|
): Promise<void> {
|
||||||
|
return await this.#run(
|
||||||
|
(locator, signal) => {
|
||||||
|
return locator.hover({...options, signal});
|
||||||
|
},
|
||||||
|
options?.signal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async scroll<ElementType extends Element>(
|
||||||
|
this: RaceLocator<ElementType>,
|
||||||
|
options?: Readonly<LocatorScrollOptions>
|
||||||
|
): Promise<void> {
|
||||||
|
return await this.#run(
|
||||||
|
(locator, signal) => {
|
||||||
|
return locator.scroll({...options, signal});
|
||||||
|
},
|
||||||
|
options?.signal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
4
packages/puppeteer-core/src/api/locators/locators.ts
Normal file
4
packages/puppeteer-core/src/api/locators/locators.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './Locator.js';
|
||||||
|
export * from './NodeLocator.js';
|
||||||
|
export * from './ExpectedLocator.js';
|
||||||
|
export * from './RaceLocator.js';
|
@ -19,7 +19,7 @@ import {TimeoutError} from 'puppeteer-core';
|
|||||||
import {
|
import {
|
||||||
Locator,
|
Locator,
|
||||||
LocatorEmittedEvents,
|
LocatorEmittedEvents,
|
||||||
} from 'puppeteer-core/internal/api/Locator.js';
|
} from 'puppeteer-core/internal/api/locators/locators.js';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import {getTestState, setupTestBrowserHooks} from './mocha-utils.js';
|
import {getTestState, setupTestBrowserHooks} from './mocha-utils.js';
|
||||||
|
Loading…
Reference in New Issue
Block a user