mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-02 08:07:34 +03:00
feat(locator): implement element locators (#7808)
This commit is contained in:
parent
bfbba5a979
commit
827fb80465
@ -304,8 +304,8 @@ element_handle.dispatch_event("#source", "dragstart", {"dataTransfer": data_tran
|
||||
```
|
||||
|
||||
```csharp
|
||||
var handle = await page.EvaluateHandleAsync("() => new DataTransfer()");
|
||||
await handle.AsElement().DispatchEventAsync("dragstart", new Dictionary<string, object>
|
||||
var dataTransfer = await page.EvaluateHandleAsync("() => new DataTransfer()");
|
||||
await elementHandle.DispatchEventAsync("dragstart", new Dictionary<string, object>
|
||||
{
|
||||
{ "dataTransfer", dataTransfer }
|
||||
});
|
||||
|
@ -935,6 +935,16 @@ Returns whether the element is [visible](./actionability.md#visible). [`option:
|
||||
|
||||
### option: Frame.isVisible.timeout = %%-input-timeout-%%
|
||||
|
||||
## method: Frame.locator
|
||||
- returns: <[Locator]>
|
||||
|
||||
The method returns an element locator that can be used to perform actions in the frame.
|
||||
Locator is resolved to the element immediately before performing an action, so a series of actions on the same locator can in fact be performed on different DOM elements. That would happen if the DOM structure between those actions has changed.
|
||||
|
||||
Note that locator always implies visibility, so it will always be locating visible elements.
|
||||
|
||||
### param: Frame.locator.selector = %%-find-selector-%%
|
||||
|
||||
## method: Frame.name
|
||||
- returns: <[string]>
|
||||
|
||||
|
849
docs/src/api/class-locator.md
Normal file
849
docs/src/api/class-locator.md
Normal file
@ -0,0 +1,849 @@
|
||||
# class: Locator
|
||||
|
||||
Locator represents a view to the element(s) on the page. It captures the logic sufficient to retrieve the element at any given moment. Locator can be created with the [`method: Page.locator`] method.
|
||||
|
||||
The difference between the Locator and [ElementHandle] is that the latter points to a particular element, while Locator only captures the logic of how to retrieve an element at any given moment.
|
||||
|
||||
In the example below, handle points to a particular DOM element on page. If that element changes text or is used by React to render an entirely different component, handle is still pointing to that very DOM element.
|
||||
|
||||
```js
|
||||
const handle = await page.$('text=Submit');
|
||||
// ...
|
||||
await handle.hover();
|
||||
await handle.click();
|
||||
```
|
||||
|
||||
```java
|
||||
ElementHandle handle = page.querySelector("text=Submit");
|
||||
handle.hover();
|
||||
handle.click();
|
||||
```
|
||||
|
||||
```python async
|
||||
handle = await page.query_selector("text=Submit")
|
||||
await handle.hover()
|
||||
await handle.click()
|
||||
```
|
||||
|
||||
```python sync
|
||||
handle = page.query_selector("text=Submit")
|
||||
handle.hover()
|
||||
handle.click()
|
||||
```
|
||||
|
||||
```csharp
|
||||
var handle = await page.QuerySelectorAsync("text=Submit");
|
||||
await handle.HoverAsync();
|
||||
await handle.ClickAsync();
|
||||
```
|
||||
|
||||
With the locator, every time the `element` is used, corresponding DOM element is located in the page using given selector. So in the snippet below, underlying DOM element is going to be located twice, using the given selector.
|
||||
|
||||
```js
|
||||
const element = page.locator('text=Submit');
|
||||
// ...
|
||||
await element.hover();
|
||||
await element.click();
|
||||
```
|
||||
|
||||
```java
|
||||
Locator element = page.locator("text=Submit");
|
||||
element.hover();
|
||||
element.click();
|
||||
```
|
||||
|
||||
```python async
|
||||
element = page.locator("text=Submit")
|
||||
await element.hover()
|
||||
await element.click()
|
||||
```
|
||||
|
||||
```python sync
|
||||
element = page.locator("text=Submit")
|
||||
element.hover()
|
||||
element.click()
|
||||
```
|
||||
|
||||
```csharp
|
||||
var element = page.Finder("text=Submit");
|
||||
await element.HoverAsync();
|
||||
await element.ClickAsync();
|
||||
```
|
||||
|
||||
## async method: Locator.all
|
||||
- returns: <[Array]<[ElementHandle]>>
|
||||
|
||||
Resolves given locator to all matching DOM elements.
|
||||
|
||||
## async method: Locator.boundingBox
|
||||
- returns: <[null]|[Object]>
|
||||
- `x` <[float]> the x coordinate of the element in pixels.
|
||||
- `y` <[float]> the y coordinate of the element in pixels.
|
||||
- `width` <[float]> the width of the element in pixels.
|
||||
- `height` <[float]> the height of the element in pixels.
|
||||
|
||||
This method returns the bounding box of the element, or `null` if the element is not visible. The bounding box is
|
||||
calculated relative to the main frame viewport - which is usually the same as the browser window.
|
||||
|
||||
Scrolling affects the returned bonding box, similarly to
|
||||
[Element.getBoundingClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). That
|
||||
means `x` and/or `y` may be negative.
|
||||
|
||||
Elements from child frames return the bounding box relative to the main frame, unlike the
|
||||
[Element.getBoundingClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
|
||||
|
||||
Assuming the page is static, it is safe to use bounding box coordinates to perform input. For example, the following
|
||||
snippet should click the center of the element.
|
||||
|
||||
```js
|
||||
const box = await element.boundingBox();
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
|
||||
```
|
||||
|
||||
```java
|
||||
BoundingBox box = element.boundingBox();
|
||||
page.mouse().click(box.x + box.width / 2, box.y + box.height / 2);
|
||||
```
|
||||
|
||||
```python async
|
||||
box = await element.bounding_box()
|
||||
await page.mouse.click(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
|
||||
```
|
||||
|
||||
```python sync
|
||||
box = element.bounding_box()
|
||||
page.mouse.click(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
|
||||
```
|
||||
|
||||
```csharp
|
||||
var box = await element.BoundingBoxAsync();
|
||||
await page.Mouse.ClickAsync(box.X + box.Width / 2, box.Y + box.Height / 2);
|
||||
```
|
||||
|
||||
### option: Locator.boundingBox.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.check
|
||||
|
||||
This method checks the element by performing the following steps:
|
||||
1. Ensure that element is a checkbox or a radio input. If not, this method throws. If the element is already
|
||||
checked, this method returns immediately.
|
||||
1. Wait for [actionability](./actionability.md) checks on the element, unless [`option: force`] option is set.
|
||||
1. Scroll the element into view if needed.
|
||||
1. Use [`property: Page.mouse`] to click in the center of the element.
|
||||
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
|
||||
1. Ensure that the element is now checked. If not, this method throws.
|
||||
|
||||
If the element is detached from the DOM at any moment during the action, this method throws.
|
||||
|
||||
When all steps combined have not finished during the specified [`option: timeout`], this method throws a
|
||||
[TimeoutError]. Passing zero timeout disables this.
|
||||
|
||||
### option: Locator.check.position = %%-input-position-%%
|
||||
### option: Locator.check.force = %%-input-force-%%
|
||||
### option: Locator.check.noWaitAfter = %%-input-no-wait-after-%%
|
||||
### option: Locator.check.timeout = %%-input-timeout-%%
|
||||
### option: Locator.check.trial = %%-input-trial-%%
|
||||
|
||||
## async method: Locator.click
|
||||
|
||||
This method clicks the element by performing the following steps:
|
||||
1. Wait for [actionability](./actionability.md) checks on the element, unless [`option: force`] option is set.
|
||||
1. Scroll the element into view if needed.
|
||||
1. Use [`property: Page.mouse`] to click in the center of the element, or the specified [`option: position`].
|
||||
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
|
||||
|
||||
If the element is detached from the DOM at any moment during the action, this method throws.
|
||||
|
||||
When all steps combined have not finished during the specified [`option: timeout`], this method throws a
|
||||
[TimeoutError]. Passing zero timeout disables this.
|
||||
|
||||
### option: Locator.click.button = %%-input-button-%%
|
||||
### option: Locator.click.clickCount = %%-input-click-count-%%
|
||||
### option: Locator.click.delay = %%-input-down-up-delay-%%
|
||||
### option: Locator.click.position = %%-input-position-%%
|
||||
### option: Locator.click.modifiers = %%-input-modifiers-%%
|
||||
### option: Locator.click.force = %%-input-force-%%
|
||||
### option: Locator.click.noWaitAfter = %%-input-no-wait-after-%%
|
||||
### option: Locator.click.timeout = %%-input-timeout-%%
|
||||
### option: Locator.click.trial = %%-input-trial-%%
|
||||
|
||||
## async method: Locator.dblclick
|
||||
* langs:
|
||||
- alias-csharp: DblClickAsync
|
||||
|
||||
This method double clicks the element by performing the following steps:
|
||||
1. Wait for [actionability](./actionability.md) checks on the element, unless [`option: force`] option is set.
|
||||
1. Scroll the element into view if needed.
|
||||
1. Use [`property: Page.mouse`] to double click in the center of the element, or the specified [`option: position`].
|
||||
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set. Note that
|
||||
if the first click of the `dblclick()` triggers a navigation event, this method will throw.
|
||||
|
||||
If the element is detached from the DOM at any moment during the action, this method throws.
|
||||
|
||||
When all steps combined have not finished during the specified [`option: timeout`], this method throws a
|
||||
[TimeoutError]. Passing zero timeout disables this.
|
||||
|
||||
:::note
|
||||
`element.dblclick()` dispatches two `click` events and a single `dblclick` event.
|
||||
:::
|
||||
|
||||
### option: Locator.dblclick.button = %%-input-button-%%
|
||||
### option: Locator.dblclick.delay = %%-input-down-up-delay-%%
|
||||
### option: Locator.dblclick.position = %%-input-position-%%
|
||||
### option: Locator.dblclick.modifiers = %%-input-modifiers-%%
|
||||
### option: Locator.dblclick.force = %%-input-force-%%
|
||||
### option: Locator.dblclick.noWaitAfter = %%-input-no-wait-after-%%
|
||||
### option: Locator.dblclick.timeout = %%-input-timeout-%%
|
||||
### option: Locator.dblclick.trial = %%-input-trial-%%
|
||||
|
||||
## async method: Locator.dispatchEvent
|
||||
|
||||
The snippet below dispatches the `click` event on the element. Regardless of the visibility state of the element, `click`
|
||||
is dispatched. This is equivalent to calling
|
||||
[element.click()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click).
|
||||
|
||||
```js
|
||||
await element.dispatchEvent('click');
|
||||
```
|
||||
|
||||
```java
|
||||
element.dispatchEvent("click");
|
||||
```
|
||||
|
||||
```python async
|
||||
await element.dispatch_event("click")
|
||||
```
|
||||
|
||||
```python sync
|
||||
element.dispatch_event("click")
|
||||
```
|
||||
|
||||
```csharp
|
||||
await element.DispatchEventAsync("click");
|
||||
```
|
||||
|
||||
Under the hood, it creates an instance of an event based on the given [`param: type`], initializes it with
|
||||
[`param: eventInit`] properties and dispatches it on the element. Events are `composed`, `cancelable` and bubble by
|
||||
default.
|
||||
|
||||
Since [`param: eventInit`] is event-specific, please refer to the events documentation for the lists of initial
|
||||
properties:
|
||||
* [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent)
|
||||
* [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent)
|
||||
* [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent)
|
||||
* [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent)
|
||||
* [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent)
|
||||
* [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent)
|
||||
* [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event)
|
||||
|
||||
You can also specify `JSHandle` as the property value if you want live objects to be passed into the event:
|
||||
|
||||
```js
|
||||
// Note you can only create DataTransfer in Chromium and Firefox
|
||||
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
|
||||
await element.dispatchEvent('dragstart', { dataTransfer });
|
||||
```
|
||||
|
||||
```java
|
||||
// Note you can only create DataTransfer in Chromium and Firefox
|
||||
JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()");
|
||||
Map<String, Object> arg = new HashMap<>();
|
||||
arg.put("dataTransfer", dataTransfer);
|
||||
element.dispatchEvent("dragstart", arg);
|
||||
```
|
||||
|
||||
```python async
|
||||
# note you can only create data_transfer in chromium and firefox
|
||||
data_transfer = await page.evaluate_handle("new DataTransfer()")
|
||||
await element.dispatch_event("#source", "dragstart", {"dataTransfer": data_transfer})
|
||||
```
|
||||
|
||||
```python sync
|
||||
# note you can only create data_transfer in chromium and firefox
|
||||
data_transfer = page.evaluate_handle("new DataTransfer()")
|
||||
element.dispatch_event("#source", "dragstart", {"dataTransfer": data_transfer})
|
||||
```
|
||||
|
||||
```csharp
|
||||
var dataTransfer = await page.EvaluateHandleAsync("() => new DataTransfer()");
|
||||
await element.DispatchEventAsync("dragstart", new Dictionary<string, object>
|
||||
{
|
||||
{ "dataTransfer", dataTransfer }
|
||||
});
|
||||
```
|
||||
|
||||
### param: Locator.dispatchEvent.type
|
||||
- `type` <[string]>
|
||||
|
||||
DOM event type: `"click"`, `"dragstart"`, etc.
|
||||
|
||||
### param: Locator.dispatchEvent.eventInit
|
||||
- `eventInit` <[EvaluationArgument]>
|
||||
|
||||
Optional event-specific initialization properties.
|
||||
|
||||
### option: Locator.dispatchEvent.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.evaluate
|
||||
- returns: <[Serializable]>
|
||||
|
||||
Returns the return value of [`param: expression`].
|
||||
|
||||
This method passes this handle as the first argument to [`param: expression`].
|
||||
|
||||
If [`param: expression`] returns a [Promise], then `handle.evaluate` would wait for the promise to resolve and return
|
||||
its value.
|
||||
|
||||
Examples:
|
||||
|
||||
```js
|
||||
const tweets = await page.locator('.tweet .retweets');
|
||||
expect(await tweets.evaluate(node => node.innerText)).toBe('10 retweets');
|
||||
```
|
||||
|
||||
```java
|
||||
Locator tweets = page.locator(".tweet .retweets");
|
||||
assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
|
||||
```
|
||||
|
||||
```python async
|
||||
tweets = await page.locator(".tweet .retweets")
|
||||
assert await tweets.evaluate("node => node.innerText") == "10 retweets"
|
||||
```
|
||||
|
||||
```python sync
|
||||
tweets = page.locator(".tweet .retweets")
|
||||
assert tweets.evaluate("node => node.innerText") == "10 retweets"
|
||||
```
|
||||
|
||||
```csharp
|
||||
var tweets = page.Finder(".tweet .retweets");
|
||||
Assert.Equals("10 retweets", await tweets.EvaluateAsync("node => node.innerText"));
|
||||
```
|
||||
|
||||
### param: Locator.evaluate.expression = %%-evaluate-expression-%%
|
||||
|
||||
### param: Locator.evaluate.arg
|
||||
- `arg` <[EvaluationArgument]>
|
||||
|
||||
Optional argument to pass to [`param: expression`].
|
||||
|
||||
### option: Locator.evaluate.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.evaluateAll
|
||||
- returns: <[Serializable]>
|
||||
|
||||
The method finds all elements matching the specified locator and passes an array of matched elements as
|
||||
a first argument to [`param: expression`]. Returns the result of [`param: expression`] invocation.
|
||||
|
||||
If [`param: expression`] returns a [Promise], then [`Locator.evaluateAll`] would wait for the promise
|
||||
to resolve and return its value.
|
||||
|
||||
Examples:
|
||||
|
||||
```js
|
||||
const elements = page.locator('div');
|
||||
const divCounts = await elements.evaluateAll((divs, min) => divs.length >= min, 10);
|
||||
```
|
||||
|
||||
```java
|
||||
Locator elements = page.locator("div");
|
||||
boolean divCounts = (boolean) elements.evaluateAll("(divs, min) => divs.length >= min", 10);
|
||||
```
|
||||
|
||||
```python async
|
||||
elements = page.locator("div")
|
||||
div_counts = await elements("(divs, min) => divs.length >= min", 10)
|
||||
```
|
||||
|
||||
```python sync
|
||||
elements = page.locator("div")
|
||||
div_counts = elements("(divs, min) => divs.length >= min", 10)
|
||||
```
|
||||
|
||||
```csharp
|
||||
var elements = page.Locator("div");
|
||||
var divsCount = await elements.EvaluateAll<bool>("(divs, min) => divs.length >= min", 10);
|
||||
```
|
||||
|
||||
### param: Locator.evaluateAll.expression = %%-evaluate-expression-%%
|
||||
|
||||
### param: Locator.evaluateAll.arg
|
||||
- `arg` <[EvaluationArgument]>
|
||||
|
||||
Optional argument to pass to [`param: expression`].
|
||||
|
||||
|
||||
## async method: Locator.evaluateHandle
|
||||
- returns: <[JSHandle]>
|
||||
|
||||
Returns the return value of [`param: expression`] as a [JSHandle].
|
||||
|
||||
This method passes this handle as the first argument to [`param: expression`].
|
||||
|
||||
The only difference between [`method: Locator.evaluate`] and [`method: Locator.evaluateHandle`] is that [`method: Locator.evaluateHandle`] returns [JSHandle].
|
||||
|
||||
If the function passed to the [`method: Locator.evaluateHandle`] returns a [Promise], then [`method: Locator.evaluateHandle`] would wait
|
||||
for the promise to resolve and return its value.
|
||||
|
||||
See [`method: Page.evaluateHandle`] for more details.
|
||||
|
||||
### param: Locator.evaluateHandle.expression = %%-evaluate-expression-%%
|
||||
|
||||
### param: Locator.evaluateHandle.arg
|
||||
- `arg` <[EvaluationArgument]>
|
||||
|
||||
Optional argument to pass to [`param: expression`].
|
||||
|
||||
### option: Locator.evaluateHandle.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.fill
|
||||
|
||||
This method waits for [actionability](./actionability.md) checks, focuses the element, fills it and triggers an `input` event after filling. Note that you can pass an empty string to clear the input field.
|
||||
|
||||
If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error. However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be filled instead.
|
||||
|
||||
To send fine-grained keyboard events, use [`method: Locator.type`].
|
||||
|
||||
### param: Locator.fill.value
|
||||
- `value` <[string]>
|
||||
|
||||
Value to set for the `<input>`, `<textarea>` or `[contenteditable]` element.
|
||||
|
||||
### option: Locator.fill.force = %%-input-force-%%
|
||||
### option: Locator.fill.noWaitAfter = %%-input-no-wait-after-%%
|
||||
### option: Locator.fill.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.first
|
||||
- returns: <[ElementHandle]>
|
||||
|
||||
Resolves given locator to the first VISIBLE matching DOM element. If no elements matching
|
||||
the query are visible, waits for them up to a given timeout.
|
||||
|
||||
### option: Locator.first.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.focus
|
||||
|
||||
Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element.
|
||||
|
||||
### option: Locator.focus.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.getAttribute
|
||||
- returns: <[null]|[string]>
|
||||
|
||||
Returns element attribute value.
|
||||
|
||||
### param: Locator.getAttribute.name
|
||||
- `name` <[string]>
|
||||
|
||||
Attribute name to get the value for.
|
||||
|
||||
### option: Locator.getAttribute.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.hover
|
||||
|
||||
This method hovers over the element by performing the following steps:
|
||||
1. Wait for [actionability](./actionability.md) checks on the element, unless [`option: force`] option is set.
|
||||
1. Scroll the element into view if needed.
|
||||
1. Use [`property: Page.mouse`] to hover over the center of the element, or the specified [`option: position`].
|
||||
1. Wait for initiated navigations to either succeed or fail, unless `noWaitAfter` option is set.
|
||||
|
||||
If the element is detached from the DOM at any moment during the action, this method throws.
|
||||
|
||||
When all steps combined have not finished during the specified [`option: timeout`], this method throws a
|
||||
[TimeoutError]. Passing zero timeout disables this.
|
||||
|
||||
### option: Locator.hover.position = %%-input-position-%%
|
||||
### option: Locator.hover.modifiers = %%-input-modifiers-%%
|
||||
### option: Locator.hover.force = %%-input-force-%%
|
||||
### option: Locator.hover.timeout = %%-input-timeout-%%
|
||||
### option: Locator.hover.trial = %%-input-trial-%%
|
||||
|
||||
## async method: Locator.innerHTML
|
||||
- returns: <[string]>
|
||||
|
||||
Returns the `element.innerHTML`.
|
||||
|
||||
### option: Locator.innerHTML.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.innerText
|
||||
- returns: <[string]>
|
||||
|
||||
Returns the `element.innerText`.
|
||||
|
||||
### option: Locator.innerText.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.inputValue
|
||||
- returns: <[string]>
|
||||
|
||||
Returns `input.value` for `<input>` or `<textarea>` element. Throws for non-input elements.
|
||||
|
||||
### option: Locator.inputValue.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.isChecked
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is checked. Throws if the element is not a checkbox or radio input.
|
||||
|
||||
### option: Locator.isChecked.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.isDisabled
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is disabled, the opposite of [enabled](./actionability.md#enabled).
|
||||
|
||||
### option: Locator.isDisabled.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.isEditable
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is [editable](./actionability.md#editable).
|
||||
|
||||
### option: Locator.isEditable.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.isEnabled
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is [enabled](./actionability.md#enabled).
|
||||
|
||||
### option: Locator.isEnabled.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.isHidden
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is hidden, the opposite of [visible](./actionability.md#visible).
|
||||
|
||||
### option: Locator.isHidden.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.isVisible
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is [visible](./actionability.md#visible).
|
||||
|
||||
### option: Locator.isVisible.timeout = %%-input-timeout-%%
|
||||
|
||||
## method: Locator.locator
|
||||
- returns: <[Locator]>
|
||||
|
||||
The method finds an element matching the specified selector in the `Locator`'s subtree. See
|
||||
[Working with selectors](./selectors.md) for more details.
|
||||
|
||||
### param: Locator.locator.selector = %%-find-selector-%%
|
||||
|
||||
## async method: Locator.press
|
||||
|
||||
Focuses the element, and then uses [`method: Keyboard.down`] and [`method: Keyboard.up`].
|
||||
|
||||
[`param: key`] can specify the intended
|
||||
[keyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) value or a single character to
|
||||
generate the text for. A superset of the [`param: key`] values can be found
|
||||
[here](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values). Examples of the keys are:
|
||||
|
||||
`F1` - `F12`, `Digit0`- `Digit9`, `KeyA`- `KeyZ`, `Backquote`, `Minus`, `Equal`, `Backslash`, `Backspace`, `Tab`,
|
||||
`Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`, etc.
|
||||
|
||||
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
|
||||
|
||||
Holding down `Shift` will type the text that corresponds to the [`param: key`] in the upper case.
|
||||
|
||||
If [`param: key`] is a single character, it is case-sensitive, so the values `a` and `A` will generate different
|
||||
respective texts.
|
||||
|
||||
Shortcuts such as `key: "Control+o"` or `key: "Control+Shift+T"` are supported as well. When specified with the
|
||||
modifier, modifier is pressed and being held while the subsequent key is being pressed.
|
||||
|
||||
### param: Locator.press.key
|
||||
- `key` <[string]>
|
||||
|
||||
Name of the key to press or a character to generate, such as `ArrowLeft` or `a`.
|
||||
|
||||
### option: Locator.press.delay
|
||||
- `delay` <[float]>
|
||||
|
||||
Time to wait between `keydown` and `keyup` in milliseconds. Defaults to 0.
|
||||
|
||||
### option: Locator.press.noWaitAfter = %%-input-no-wait-after-%%
|
||||
|
||||
### option: Locator.press.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.screenshot
|
||||
- returns: <[Buffer]>
|
||||
|
||||
Returns the buffer with the captured screenshot.
|
||||
|
||||
This method waits for the [actionability](./actionability.md) checks, then scrolls element into view before taking a
|
||||
screenshot. If the element is detached from DOM, the method throws an error.
|
||||
|
||||
### option: Locator.screenshot.path
|
||||
- `path` <[path]>
|
||||
|
||||
The file path to save the image to. The screenshot type will be inferred from file extension. If [`option: path`] is a
|
||||
relative path, then it is resolved relative to the current working directory. If no path is provided, the image won't be
|
||||
saved to the disk.
|
||||
|
||||
### option: Locator.screenshot.type = %%-screenshot-type-%%
|
||||
|
||||
### option: Locator.screenshot.quality
|
||||
- `quality` <[int]>
|
||||
|
||||
The quality of the image, between 0-100. Not applicable to `png` images.
|
||||
|
||||
### option: Locator.screenshot.omitBackground
|
||||
- `omitBackground` <[boolean]>
|
||||
|
||||
Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images.
|
||||
Defaults to `false`.
|
||||
|
||||
### option: Locator.screenshot.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.scrollIntoViewIfNeeded
|
||||
|
||||
This method waits for [actionability](./actionability.md) checks, then tries to scroll element into view, unless it is
|
||||
completely visible as defined by
|
||||
[IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)'s `ratio`.
|
||||
|
||||
### option: Locator.scrollIntoViewIfNeeded.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.selectOption
|
||||
- returns: <[Array]<[string]>>
|
||||
|
||||
This method waits for [actionability](./actionability.md) checks, waits until all specified options are present in the `<select>` element and selects these options.
|
||||
|
||||
If the target element is not a `<select>` element, this method throws an error. However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be used instead.
|
||||
|
||||
Returns the array of option values that have been successfully selected.
|
||||
|
||||
Triggers a `change` and `input` event once all the provided options have been selected.
|
||||
|
||||
```js
|
||||
// single selection matching the value
|
||||
element.selectOption('blue');
|
||||
|
||||
// single selection matching the label
|
||||
element.selectOption({ label: 'Blue' });
|
||||
|
||||
// multiple selection
|
||||
element.selectOption(['red', 'green', 'blue']);
|
||||
```
|
||||
|
||||
```java
|
||||
// single selection matching the value
|
||||
element.selectOption("blue");
|
||||
// single selection matching the label
|
||||
element.selectOption(new SelectOption().setLabel("Blue"));
|
||||
// multiple selection
|
||||
element.selectOption(new String[] {"red", "green", "blue"});
|
||||
```
|
||||
|
||||
```python async
|
||||
# single selection matching the value
|
||||
await element.select_option("blue")
|
||||
# single selection matching the label
|
||||
await element.select_option(label="blue")
|
||||
# multiple selection
|
||||
await element.select_option(value=["red", "green", "blue"])
|
||||
```
|
||||
|
||||
```python sync
|
||||
# single selection matching the value
|
||||
element.select_option("blue")
|
||||
# single selection matching both the label
|
||||
element.select_option(label="blue")
|
||||
# multiple selection
|
||||
element.select_option(value=["red", "green", "blue"])
|
||||
```
|
||||
|
||||
```python sync
|
||||
# single selection matching the value
|
||||
element.select_option("blue")
|
||||
# single selection matching both the value and the label
|
||||
element.select_option(label="blue")
|
||||
# multiple selection
|
||||
element.select_option("red", "green", "blue")
|
||||
# multiple selection for blue, red and second option
|
||||
element.select_option(value="blue", { index: 2 }, "red")
|
||||
```
|
||||
|
||||
```csharp
|
||||
// single selection matching the value
|
||||
await element.SelectOptionAsync(new[] { "blue" });
|
||||
// single selection matching the label
|
||||
await element.SelectOptionAsync(new[] { new SelectOptionValue() { Label = "blue" } });
|
||||
// multiple selection
|
||||
await element.SelectOptionAsync(new[] { "red", "green", "blue" });
|
||||
// multiple selection for blue, red and second option
|
||||
await element.SelectOptionAsync(new[] {
|
||||
new SelectOptionValue() { Label = "blue" },
|
||||
new SelectOptionValue() { Index = 2 },
|
||||
new SelectOptionValue() { Value = "red" }});
|
||||
```
|
||||
|
||||
### param: Locator.selectOption.values = %%-select-options-values-%%
|
||||
### option: Locator.selectOption.force = %%-input-force-%%
|
||||
### option: Locator.selectOption.noWaitAfter = %%-input-no-wait-after-%%
|
||||
### option: Locator.selectOption.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.selectText
|
||||
|
||||
This method waits for [actionability](./actionability.md) checks, then focuses the element and selects all its text
|
||||
content.
|
||||
|
||||
### option: Locator.selectText.force = %%-input-force-%%
|
||||
### option: Locator.selectText.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.setInputFiles
|
||||
|
||||
This method expects `element` to point to an
|
||||
[input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
|
||||
|
||||
Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they
|
||||
are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
|
||||
### param: Locator.setInputFiles.files = %%-input-files-%%
|
||||
### option: Locator.setInputFiles.noWaitAfter = %%-input-no-wait-after-%%
|
||||
### option: Locator.setInputFiles.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.tap
|
||||
|
||||
This method taps the element by performing the following steps:
|
||||
1. Wait for [actionability](./actionability.md) checks on the element, unless [`option: force`] option is set.
|
||||
1. Scroll the element into view if needed.
|
||||
1. Use [`property: Page.touchscreen`] to tap the center of the element, or the specified [`option: position`].
|
||||
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
|
||||
|
||||
If the element is detached from the DOM at any moment during the action, this method throws.
|
||||
|
||||
When all steps combined have not finished during the specified [`option: timeout`], this method throws a
|
||||
[TimeoutError]. Passing zero timeout disables this.
|
||||
|
||||
:::note
|
||||
`element.tap()` requires that the `hasTouch` option of the browser context be set to true.
|
||||
:::
|
||||
|
||||
### option: Locator.tap.position = %%-input-position-%%
|
||||
### option: Locator.tap.modifiers = %%-input-modifiers-%%
|
||||
### option: Locator.tap.force = %%-input-force-%%
|
||||
### option: Locator.tap.noWaitAfter = %%-input-no-wait-after-%%
|
||||
### option: Locator.tap.timeout = %%-input-timeout-%%
|
||||
### option: Locator.tap.trial = %%-input-trial-%%
|
||||
|
||||
## async method: Locator.textContent
|
||||
- returns: <[null]|[string]>
|
||||
|
||||
Returns the `node.textContent`.
|
||||
|
||||
### option: Locator.textContent.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.type
|
||||
|
||||
Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text.
|
||||
|
||||
To press a special key, like `Control` or `ArrowDown`, use [`method: Locator.press`].
|
||||
|
||||
```js
|
||||
await element.type('Hello'); // Types instantly
|
||||
await element.type('World', {delay: 100}); // Types slower, like a user
|
||||
```
|
||||
|
||||
```java
|
||||
element.type("Hello"); // Types instantly
|
||||
element.type("World", new Locator.TypeOptions().setDelay(100)); // Types slower, like a user
|
||||
```
|
||||
|
||||
```python async
|
||||
await element.type("hello") # types instantly
|
||||
await element.type("world", delay=100) # types slower, like a user
|
||||
```
|
||||
|
||||
```python sync
|
||||
element.type("hello") # types instantly
|
||||
element.type("world", delay=100) # types slower, like a user
|
||||
```
|
||||
|
||||
```csharp
|
||||
await element.TypeAsync("Hello"); // Types instantly
|
||||
await element.TypeAsync("World", delay: 100); // Types slower, like a user
|
||||
```
|
||||
|
||||
An example of typing into a text field and then submitting the form:
|
||||
|
||||
```js
|
||||
const element = page.locator('input');
|
||||
await element.type('some text');
|
||||
await element.press('Enter');
|
||||
```
|
||||
|
||||
```java
|
||||
Locator element = page.locator("input");
|
||||
element.type("some text");
|
||||
element.press("Enter");
|
||||
```
|
||||
|
||||
```python async
|
||||
element = page.locator("input")
|
||||
await element.type("some text")
|
||||
await element.press("Enter")
|
||||
```
|
||||
|
||||
```python sync
|
||||
element = page.locator("input")
|
||||
element.type("some text")
|
||||
element.press("Enter")
|
||||
```
|
||||
|
||||
```csharp
|
||||
var element = page.Finder("input");
|
||||
await element.TypeAsync("some text");
|
||||
await element.PressAsync("Enter");
|
||||
```
|
||||
|
||||
### param: Locator.type.text
|
||||
- `text` <[string]>
|
||||
|
||||
A text to type into a focused element.
|
||||
|
||||
### option: Locator.type.delay
|
||||
- `delay` <[float]>
|
||||
|
||||
Time to wait between key presses in milliseconds. Defaults to 0.
|
||||
|
||||
### option: Locator.type.noWaitAfter = %%-input-no-wait-after-%%
|
||||
### option: Locator.type.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.uncheck
|
||||
|
||||
This method checks the element by performing the following steps:
|
||||
1. Ensure that element is a checkbox or a radio input. If not, this method throws. If the element is already
|
||||
unchecked, this method returns immediately.
|
||||
1. Wait for [actionability](./actionability.md) checks on the element, unless [`option: force`] option is set.
|
||||
1. Scroll the element into view if needed.
|
||||
1. Use [`property: Page.mouse`] to click in the center of the element.
|
||||
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
|
||||
1. Ensure that the element is now unchecked. If not, this method throws.
|
||||
|
||||
If the element is detached from the DOM at any moment during the action, this method throws.
|
||||
|
||||
When all steps combined have not finished during the specified [`option: timeout`], this method throws a
|
||||
[TimeoutError]. Passing zero timeout disables this.
|
||||
|
||||
### option: Locator.uncheck.position = %%-input-position-%%
|
||||
### option: Locator.uncheck.force = %%-input-force-%%
|
||||
### option: Locator.uncheck.noWaitAfter = %%-input-no-wait-after-%%
|
||||
### option: Locator.uncheck.timeout = %%-input-timeout-%%
|
||||
### option: Locator.uncheck.trial = %%-input-trial-%%
|
||||
|
||||
## async method: Locator.waitFor
|
||||
- returns: <[null]|[ElementHandle]<[HTMLElement]|[SVGElement]>>
|
||||
|
||||
Returns when element specified by selector satisfies [`option: state`] option. Returns `null` if waiting for `hidden` or `detached`.
|
||||
|
||||
Wait for the element to satisfy [`option: state`] option (either appear/disappear from dom, or become
|
||||
visible/hidden). If at the moment of calling the method it already satisfies the condition, the method
|
||||
will return immediately. If the selector doesn't satisfy the condition for the [`option: timeout`] milliseconds, the
|
||||
function will throw.
|
||||
|
||||
This method works across navigations.
|
||||
|
||||
### option: Locator.waitFor.state = %%-wait-for-selector-state-%%
|
||||
### option: Locator.waitFor.timeout = %%-input-timeout-%%
|
||||
|
@ -2046,6 +2046,18 @@ Returns whether the element is [visible](./actionability.md#visible). [`option:
|
||||
## property: Page.keyboard
|
||||
- type: <[Keyboard]>
|
||||
|
||||
## method: Page.locator
|
||||
- returns: <[Locator]>
|
||||
|
||||
The method returns an element locator that can be used to perform actions on the page.
|
||||
Locator is resolved to the element immediately before performing an action, so a series of actions on the same locator can in fact be performed on different DOM elements. That would happen if the DOM structure between those actions has changed.
|
||||
|
||||
Note that locator always implies visibility, so it will always be locating visible elements.
|
||||
|
||||
Shortcut for main frame's [`method: Frame.locator`].
|
||||
|
||||
### param: Page.locator.selector = %%-find-selector-%%
|
||||
|
||||
## method: Page.mainFrame
|
||||
- returns: <[Frame]>
|
||||
|
||||
@ -3675,7 +3687,6 @@ class FrameExamples
|
||||
### param: Page.waitForSelector.selector = %%-query-selector-%%
|
||||
|
||||
### option: Page.waitForSelector.state = %%-wait-for-selector-state-%%
|
||||
|
||||
### option: Page.waitForSelector.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Page.waitForTimeout
|
||||
|
@ -25,3 +25,6 @@
|
||||
### param: Worker.evaluateHandle.expression = %%-js-worker-evaluate-workerfunction-%%
|
||||
### param: ElectronApplication.evaluate.expression = %%-js-electron-evaluate-workerfunction-%%
|
||||
### param: ElectronApplication.evaluateHandle.expression = %%-js-electron-evaluate-workerfunction-%%
|
||||
### param: Locator.evaluate.expression = %%-js-evaluate-pagefunction-%%
|
||||
### param: Locator.evaluateAll.expression = %%-js-evaluate-pagefunction-%%
|
||||
### param: Locator.evaluateHandle.expression = %%-js-evaluate-pagefunction-%%
|
||||
|
@ -102,6 +102,11 @@ When set, this method only performs the [actionability](./actionability.md) chec
|
||||
|
||||
A selector to query for. See [working with selectors](./selectors.md) for more details.
|
||||
|
||||
## find-selector
|
||||
- `selector` <[string]>
|
||||
|
||||
A selector to use when resolving DOM element. See [working with selectors](./selectors.md) for more details.
|
||||
|
||||
## wait-for-selector-state
|
||||
- `state` <[WaitForSelectorState]<"attached"|"detached"|"visible"|"hidden">>
|
||||
|
||||
|
@ -25,6 +25,7 @@ export { Coverage } from './coverage';
|
||||
export { Dialog } from './dialog';
|
||||
export { Download } from './download';
|
||||
export { Electron, ElectronApplication } from './electron';
|
||||
export { Locator } from './locator';
|
||||
export { ElementHandle } from './elementHandle';
|
||||
export { FileChooser } from './fileChooser';
|
||||
export type { Logger } from './types';
|
||||
|
@ -18,6 +18,7 @@
|
||||
import { assert } from '../utils/utils';
|
||||
import * as channels from '../protocol/channels';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { Locator } from './locator';
|
||||
import { ElementHandle, convertSelectOptionValues, convertInputFiles } from './elementHandle';
|
||||
import { assertMaxArguments, JSHandle, serializeArgument, parseResult } from './jsHandle';
|
||||
import fs from 'fs';
|
||||
@ -316,6 +317,10 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
|
||||
});
|
||||
}
|
||||
|
||||
locator(selector: string): Locator {
|
||||
return new Locator(this, selector);
|
||||
}
|
||||
|
||||
async focus(selector: string, options: channels.FrameFocusOptions = {}) {
|
||||
return this._wrapApiCall(async (channel: channels.FrameChannel) => {
|
||||
await channel.focus({ selector, ...options });
|
||||
|
198
src/client/locator.ts
Normal file
198
src/client/locator.ts
Normal file
@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 * as structs from '../../types/structs';
|
||||
import * as api from '../../types/types';
|
||||
import * as channels from '../protocol/channels';
|
||||
import * as util from 'util';
|
||||
import { monotonicTime } from '../utils/utils';
|
||||
import { ElementHandle } from './elementHandle';
|
||||
import { Frame } from './frame';
|
||||
import { FilePayload, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types';
|
||||
|
||||
export class Locator implements api.Locator {
|
||||
private _frame: Frame;
|
||||
private _selector: string;
|
||||
|
||||
constructor(frame: Frame, selector: string) {
|
||||
this._frame = frame;
|
||||
this._selector = selector;
|
||||
}
|
||||
|
||||
private async _withFirst<R, O extends TimeoutOptions>(task: (handle: ElementHandle<SVGElement | HTMLElement>, options?: O) => Promise<R>, options?: O): Promise<R> {
|
||||
if (!options)
|
||||
options = {} as any;
|
||||
const timeout = this._frame.page()._timeoutSettings.timeout(options!);
|
||||
const deadline = timeout ? monotonicTime() + timeout : 0;
|
||||
const first = await this.first(options);
|
||||
try {
|
||||
return await task(first, { ...options!, timeout: deadline ? deadline - monotonicTime() : 0 });
|
||||
} finally {
|
||||
first.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
async all(): Promise<api.ElementHandle<SVGElement | HTMLElement>[]> {
|
||||
return this._frame.$$(this._selector);
|
||||
}
|
||||
|
||||
async boundingBox(options?: TimeoutOptions): Promise<Rect | null> {
|
||||
return this._withFirst(h => h.boundingBox(), options);
|
||||
}
|
||||
|
||||
async check(options: channels.ElementHandleCheckOptions = {}) {
|
||||
return this._frame.check(this._selector, options);
|
||||
}
|
||||
|
||||
async click(options: channels.ElementHandleClickOptions = {}): Promise<void> {
|
||||
return this._frame.click(this._selector, options);
|
||||
}
|
||||
|
||||
async dblclick(options: channels.ElementHandleDblclickOptions = {}): Promise<void> {
|
||||
return this._frame.dblclick(this._selector, options);
|
||||
}
|
||||
|
||||
async dispatchEvent(type: string, eventInit: Object = {}, options?: TimeoutOptions) {
|
||||
return this._frame.dispatchEvent(this._selector, type, eventInit, options);
|
||||
}
|
||||
|
||||
async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn<SVGElement | HTMLElement, Arg, R>, arg?: Arg, options?: TimeoutOptions): Promise<R> {
|
||||
return this._withFirst(h => h.evaluate(pageFunction as any, arg), options);
|
||||
}
|
||||
|
||||
async evaluateAll<R, Arg>(pageFunction: structs.PageFunctionOn<(SVGElement | HTMLElement)[], Arg, R>, arg?: Arg): Promise<R> {
|
||||
return this._frame.$$eval(this._selector, pageFunction as any, arg);
|
||||
}
|
||||
|
||||
async evaluateHandle<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg, options?: TimeoutOptions): Promise<structs.SmartHandle<R>> {
|
||||
return this._withFirst(h => h.evaluateHandle(pageFunction as any, arg), options);
|
||||
}
|
||||
|
||||
async fill(value: string, options: channels.ElementHandleFillOptions = {}): Promise<void> {
|
||||
return this._frame.fill(this._selector, value, options);
|
||||
}
|
||||
|
||||
locator(selector: string): Locator {
|
||||
return new Locator(this._frame, this._selector + ' >> ' + selector);
|
||||
}
|
||||
|
||||
async first(options?: TimeoutOptions): Promise<ElementHandle<SVGElement | HTMLElement>> {
|
||||
const result = await this._frame.waitForSelector(this._selector, options);
|
||||
return result!;
|
||||
}
|
||||
|
||||
async focus(options?: TimeoutOptions): Promise<void> {
|
||||
return this._frame.focus(this._selector, options);
|
||||
}
|
||||
|
||||
async getAttribute(name: string, options?: TimeoutOptions): Promise<string | null> {
|
||||
return this._frame.getAttribute(this._selector, name, options);
|
||||
}
|
||||
|
||||
async hover(options: channels.ElementHandleHoverOptions = {}): Promise<void> {
|
||||
return this._frame.hover(this._selector, options);
|
||||
}
|
||||
|
||||
async innerHTML(options?: TimeoutOptions): Promise<string> {
|
||||
return this._frame.innerHTML(this._selector, options);
|
||||
}
|
||||
|
||||
async innerText(options?: TimeoutOptions): Promise<string> {
|
||||
return this._frame.innerText(this._selector, options);
|
||||
}
|
||||
|
||||
async inputValue(options?: TimeoutOptions): Promise<string> {
|
||||
return this._frame.inputValue(this._selector, options);
|
||||
}
|
||||
|
||||
async isChecked(options?: TimeoutOptions): Promise<boolean> {
|
||||
return this._frame.isChecked(this._selector, options);
|
||||
}
|
||||
|
||||
async isDisabled(options?: TimeoutOptions): Promise<boolean> {
|
||||
return this._frame.isDisabled(this._selector, options);
|
||||
}
|
||||
|
||||
async isEditable(options?: TimeoutOptions): Promise<boolean> {
|
||||
return this._frame.isEditable(this._selector, options);
|
||||
}
|
||||
|
||||
async isEnabled(options?: TimeoutOptions): Promise<boolean> {
|
||||
return this._frame.isEnabled(this._selector, options);
|
||||
}
|
||||
|
||||
async isHidden(options?: TimeoutOptions): Promise<boolean> {
|
||||
return this._frame.isHidden(this._selector);
|
||||
}
|
||||
|
||||
async isVisible(options?: TimeoutOptions): Promise<boolean> {
|
||||
return this._frame.isVisible(this._selector);
|
||||
}
|
||||
|
||||
async press(key: string, options: channels.ElementHandlePressOptions = {}): Promise<void> {
|
||||
return this._frame.press(this._selector, key, options);
|
||||
}
|
||||
|
||||
async screenshot(options: channels.ElementHandleScreenshotOptions & { path?: string } = {}): Promise<Buffer> {
|
||||
return this._withFirst((h, o) => h.screenshot(o), options);
|
||||
}
|
||||
|
||||
async scrollIntoViewIfNeeded(options: channels.ElementHandleScrollIntoViewIfNeededOptions = {}) {
|
||||
return this._withFirst((h, o) => h.scrollIntoViewIfNeeded(o), options);
|
||||
}
|
||||
|
||||
async selectOption(values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options: SelectOptionOptions = {}): Promise<string[]> {
|
||||
return this._frame.selectOption(this._selector, values, options);
|
||||
}
|
||||
|
||||
async selectText(options: channels.ElementHandleSelectTextOptions = {}): Promise<void> {
|
||||
return this._withFirst((h, o) => h.selectText(o), options);
|
||||
}
|
||||
|
||||
async setInputFiles(files: string | FilePayload | string[] | FilePayload[], options: channels.ElementHandleSetInputFilesOptions = {}) {
|
||||
return this._frame.setInputFiles(this._selector, files, options);
|
||||
}
|
||||
|
||||
async tap(options: channels.ElementHandleTapOptions = {}): Promise<void> {
|
||||
return this._frame.tap(this._selector, options);
|
||||
}
|
||||
|
||||
async textContent(options?: TimeoutOptions): Promise<string | null> {
|
||||
return this._frame.textContent(this._selector, options);
|
||||
}
|
||||
|
||||
async type(text: string, options: channels.ElementHandleTypeOptions = {}): Promise<void> {
|
||||
return this._frame.type(this._selector, text, options);
|
||||
}
|
||||
|
||||
async uncheck(options: channels.ElementHandleUncheckOptions = {}) {
|
||||
return this._frame.uncheck(this._selector, options);
|
||||
}
|
||||
|
||||
waitFor(options: channels.FrameWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise<ElementHandle<SVGElement | HTMLElement>>;
|
||||
waitFor(options?: channels.FrameWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null>;
|
||||
async waitFor(options?: channels.FrameWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
|
||||
return this._frame.waitForSelector(this._selector, options);
|
||||
}
|
||||
|
||||
[(util.inspect as any).custom]() {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Locator@${this._selector}`;
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import { ConsoleMessage } from './consoleMessage';
|
||||
import { Dialog } from './dialog';
|
||||
import { Download } from './download';
|
||||
import { ElementHandle, determineScreenshotType } from './elementHandle';
|
||||
import { Locator } from './locator';
|
||||
import { Worker } from './worker';
|
||||
import { Frame, verifyLoadState, WaitForNavigationOptions } from './frame';
|
||||
import { Keyboard, Mouse, Touchscreen } from './input';
|
||||
@ -521,6 +522,10 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
|
||||
return this._mainFrame.fill(selector, value, options);
|
||||
}
|
||||
|
||||
locator(selector: string): Locator {
|
||||
return this.mainFrame().locator(selector);
|
||||
}
|
||||
|
||||
async focus(selector: string, options?: channels.FrameFocusOptions) {
|
||||
return this._mainFrame.focus(selector, options);
|
||||
}
|
||||
|
70
tests/page/locator-click.spec.ts
Normal file
70
tests/page/locator-click.spec.ts
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { test as it, expect } from './pageTest';
|
||||
|
||||
it('should work', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
const button = page.locator('button');
|
||||
await button.click();
|
||||
expect(await page.evaluate(() => window['result'])).toBe('Clicked');
|
||||
});
|
||||
|
||||
it('should work with Node removed', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.evaluate(() => delete window['Node']);
|
||||
const button = page.locator('button');
|
||||
await button.click();
|
||||
expect(await page.evaluate(() => window['result'])).toBe('Clicked');
|
||||
});
|
||||
|
||||
it('should work for TextNodes', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild);
|
||||
await buttonTextNode.click();
|
||||
expect(await page.evaluate(() => window['result'])).toBe('Clicked');
|
||||
});
|
||||
|
||||
it('should throw for recursively hidden nodes with force', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
const button = page.locator('button');
|
||||
await page.evaluate(button => button.parentElement.style.display = 'none', await button.first());
|
||||
const error = await button.click({ force: true }).catch(err => err);
|
||||
expect(error.message).toContain('Element is not visible');
|
||||
});
|
||||
|
||||
it('should throw for <br> elements with force', async ({ page, server }) => {
|
||||
await page.setContent('hello<br>goodbye');
|
||||
const br = page.locator('br');
|
||||
const error = await br.click({ force: true }).catch(err => err);
|
||||
expect(error.message).toContain('Element is outside of the viewport');
|
||||
});
|
||||
|
||||
it('should double click the button', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.evaluate(() => {
|
||||
window['double'] = false;
|
||||
const button = document.querySelector('button');
|
||||
button.addEventListener('dblclick', event => {
|
||||
window['double'] = true;
|
||||
});
|
||||
});
|
||||
const button = page.locator('button');
|
||||
await button.dblclick();
|
||||
expect(await page.evaluate('double')).toBe(true);
|
||||
expect(await page.evaluate('result')).toBe('Clicked');
|
||||
});
|
198
tests/page/locator-convenience.spec.ts
Normal file
198
tests/page/locator-convenience.spec.ts
Normal file
@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { test as it, expect } from './pageTest';
|
||||
|
||||
it('should have a nice preview', async ({ page, server }) => {
|
||||
await page.goto(`${server.PREFIX}/dom.html`);
|
||||
const outer = page.locator('#outer');
|
||||
const inner = outer.locator('#inner');
|
||||
const check = page.locator('#check');
|
||||
const text = await inner.evaluateHandle(e => e.firstChild);
|
||||
await page.evaluate(() => 1); // Give them a chance to calculate the preview.
|
||||
expect(String(outer)).toBe('Locator@#outer');
|
||||
expect(String(inner)).toBe('Locator@#outer >> #inner');
|
||||
expect(String(text)).toBe('JSHandle@#text=Text,↵more text');
|
||||
expect(String(check)).toBe('Locator@#check');
|
||||
});
|
||||
|
||||
it('getAttribute should work', async ({ page, server }) => {
|
||||
await page.goto(`${server.PREFIX}/dom.html`);
|
||||
const locator = page.locator('#outer');
|
||||
expect(await locator.getAttribute('name')).toBe('value');
|
||||
expect(await locator.getAttribute('foo')).toBe(null);
|
||||
expect(await page.getAttribute('#outer', 'name')).toBe('value');
|
||||
expect(await page.getAttribute('#outer', 'foo')).toBe(null);
|
||||
});
|
||||
|
||||
it('inputValue should work', async ({ page, server }) => {
|
||||
await page.goto(`${server.PREFIX}/dom.html`);
|
||||
|
||||
await page.fill('#textarea', 'text value');
|
||||
expect(await page.inputValue('#textarea')).toBe('text value');
|
||||
|
||||
await page.fill('#input', 'input value');
|
||||
expect(await page.inputValue('#input')).toBe('input value');
|
||||
const handle = page.locator('#input');
|
||||
expect(await handle.inputValue()).toBe('input value');
|
||||
|
||||
expect(await page.inputValue('#inner').catch(e => e.message)).toContain('Node is not an HTMLInputElement or HTMLTextAreaElement');
|
||||
const handle2 = page.locator('#inner');
|
||||
expect(await handle2.inputValue().catch(e => e.message)).toContain('Node is not an HTMLInputElement or HTMLTextAreaElement');
|
||||
});
|
||||
|
||||
it('innerHTML should work', async ({ page, server }) => {
|
||||
await page.goto(`${server.PREFIX}/dom.html`);
|
||||
const handle = page.locator('#outer');
|
||||
expect(await handle.innerHTML()).toBe('<div id="inner">Text,\nmore text</div>');
|
||||
expect(await page.innerHTML('#outer')).toBe('<div id="inner">Text,\nmore text</div>');
|
||||
});
|
||||
|
||||
it('innerText should work', async ({ page, server }) => {
|
||||
await page.goto(`${server.PREFIX}/dom.html`);
|
||||
const handle = page.locator('#inner');
|
||||
expect(await handle.innerText()).toBe('Text, more text');
|
||||
expect(await page.innerText('#inner')).toBe('Text, more text');
|
||||
});
|
||||
|
||||
it('innerText should throw', async ({ page, server }) => {
|
||||
await page.setContent(`<svg>text</svg>`);
|
||||
const error1 = await page.innerText('svg').catch(e => e);
|
||||
expect(error1.message).toContain('Not an HTMLElement');
|
||||
const handle = page.locator('svg');
|
||||
const error2 = await handle.innerText().catch(e => e);
|
||||
expect(error2.message).toContain('Not an HTMLElement');
|
||||
});
|
||||
|
||||
it('textContent should work', async ({ page, server }) => {
|
||||
await page.goto(`${server.PREFIX}/dom.html`);
|
||||
const handle = page.locator('#inner');
|
||||
expect(await handle.textContent()).toBe('Text,\nmore text');
|
||||
expect(await page.textContent('#inner')).toBe('Text,\nmore text');
|
||||
});
|
||||
|
||||
it('textContent should be atomic', async ({ playwright, page }) => {
|
||||
const createDummySelector = () => ({
|
||||
query(root, selector) {
|
||||
const result = root.querySelector(selector);
|
||||
if (result)
|
||||
Promise.resolve().then(() => result.textContent = 'modified');
|
||||
return result;
|
||||
},
|
||||
queryAll(root: HTMLElement, selector: string) {
|
||||
const result = Array.from(root.querySelectorAll(selector));
|
||||
for (const e of result)
|
||||
Promise.resolve().then(() => e.textContent = 'modified');
|
||||
return result;
|
||||
}
|
||||
});
|
||||
await playwright.selectors.register('textContent', createDummySelector);
|
||||
await page.setContent(`<div>Hello</div>`);
|
||||
const tc = await page.textContent('textContent=div');
|
||||
expect(tc).toBe('Hello');
|
||||
expect(await page.evaluate(() => document.querySelector('div').textContent)).toBe('modified');
|
||||
});
|
||||
|
||||
it('innerText should be atomic', async ({ playwright, page }) => {
|
||||
const createDummySelector = () => ({
|
||||
query(root: HTMLElement, selector: string) {
|
||||
const result = root.querySelector(selector);
|
||||
if (result)
|
||||
Promise.resolve().then(() => result.textContent = 'modified');
|
||||
return result;
|
||||
},
|
||||
queryAll(root: HTMLElement, selector: string) {
|
||||
const result = Array.from(root.querySelectorAll(selector));
|
||||
for (const e of result)
|
||||
Promise.resolve().then(() => e.textContent = 'modified');
|
||||
return result;
|
||||
}
|
||||
});
|
||||
await playwright.selectors.register('innerText', createDummySelector);
|
||||
await page.setContent(`<div>Hello</div>`);
|
||||
const tc = await page.innerText('innerText=div');
|
||||
expect(tc).toBe('Hello');
|
||||
expect(await page.evaluate(() => document.querySelector('div').innerText)).toBe('modified');
|
||||
});
|
||||
|
||||
it('isVisible and isHidden should work', async ({ page }) => {
|
||||
await page.setContent(`<div>Hi</div><span></span>`);
|
||||
|
||||
const div = page.locator('div');
|
||||
expect(await div.isVisible()).toBe(true);
|
||||
expect(await div.isHidden()).toBe(false);
|
||||
expect(await page.isVisible('div')).toBe(true);
|
||||
expect(await page.isHidden('div')).toBe(false);
|
||||
|
||||
const span = page.locator('span');
|
||||
expect(await span.isVisible()).toBe(false);
|
||||
expect(await span.isHidden()).toBe(true);
|
||||
expect(await page.isVisible('span')).toBe(false);
|
||||
expect(await page.isHidden('span')).toBe(true);
|
||||
|
||||
expect(await page.isVisible('no-such-element')).toBe(false);
|
||||
expect(await page.isHidden('no-such-element')).toBe(true);
|
||||
});
|
||||
|
||||
it('isEnabled and isDisabled should work', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<button disabled>button1</button>
|
||||
<button>button2</button>
|
||||
<div>div</div>
|
||||
`);
|
||||
const div = page.locator('div');
|
||||
expect(await div.isEnabled()).toBe(true);
|
||||
expect(await div.isDisabled()).toBe(false);
|
||||
expect(await page.isEnabled('div')).toBe(true);
|
||||
expect(await page.isDisabled('div')).toBe(false);
|
||||
const button1 = page.locator(':text("button1")');
|
||||
expect(await button1.isEnabled()).toBe(false);
|
||||
expect(await button1.isDisabled()).toBe(true);
|
||||
expect(await page.isEnabled(':text("button1")')).toBe(false);
|
||||
expect(await page.isDisabled(':text("button1")')).toBe(true);
|
||||
const button2 = page.locator(':text("button2")');
|
||||
expect(await button2.isEnabled()).toBe(true);
|
||||
expect(await button2.isDisabled()).toBe(false);
|
||||
expect(await page.isEnabled(':text("button2")')).toBe(true);
|
||||
expect(await page.isDisabled(':text("button2")')).toBe(false);
|
||||
});
|
||||
|
||||
it('isEditable should work', async ({ page }) => {
|
||||
await page.setContent(`<input id=input1 disabled><textarea></textarea><input id=input2>`);
|
||||
await page.$eval('textarea', t => t.readOnly = true);
|
||||
const input1 = page.locator('#input1');
|
||||
expect(await input1.isEditable()).toBe(false);
|
||||
expect(await page.isEditable('#input1')).toBe(false);
|
||||
const input2 = page.locator('#input2');
|
||||
expect(await input2.isEditable()).toBe(true);
|
||||
expect(await page.isEditable('#input2')).toBe(true);
|
||||
const textarea = page.locator('textarea');
|
||||
expect(await textarea.isEditable()).toBe(false);
|
||||
expect(await page.isEditable('textarea')).toBe(false);
|
||||
});
|
||||
|
||||
it('isChecked should work', async ({page}) => {
|
||||
await page.setContent(`<input type='checkbox' checked><div>Not a checkbox</div>`);
|
||||
const element = page.locator('input');
|
||||
expect(await element.isChecked()).toBe(true);
|
||||
expect(await page.isChecked('input')).toBe(true);
|
||||
await element.evaluate(input => (input as HTMLInputElement).checked = false);
|
||||
expect(await element.isChecked()).toBe(false);
|
||||
expect(await page.isChecked('input')).toBe(false);
|
||||
const error = await page.isChecked('div').catch(e => e);
|
||||
expect(error.message).toContain('Not a checkbox or radio button');
|
||||
});
|
56
tests/page/locator-evaluate.spec.ts
Normal file
56
tests/page/locator-evaluate.spec.ts
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { test as it, expect } from './pageTest';
|
||||
|
||||
it('should work', async ({page, server}) => {
|
||||
await page.setContent('<html><body><div class="tweet"><div class="like">100</div><div class="retweets">10</div></div></body></html>');
|
||||
const tweet = page.locator('.tweet .like');
|
||||
const content = await tweet.evaluate(node => (node as HTMLElement).innerText);
|
||||
expect(content).toBe('100');
|
||||
});
|
||||
|
||||
it('should retrieve content from subtree', async ({page, server}) => {
|
||||
const htmlContent = '<div class="a">not-a-child-div</div><div id="myId"><div class="a">a-child-div</div></div>';
|
||||
await page.setContent(htmlContent);
|
||||
const elementHandle = page.locator('#myId .a');
|
||||
const content = await elementHandle.evaluate(node => (node as HTMLElement).innerText);
|
||||
expect(content).toBe('a-child-div');
|
||||
});
|
||||
|
||||
it('should work for all', async ({page, server}) => {
|
||||
await page.setContent('<html><body><div class="tweet"><div class="like">100</div><div class="like">10</div></div></body></html>');
|
||||
const tweet = page.locator('.tweet .like');
|
||||
const content = await tweet.evaluateAll(nodes => nodes.map(n => (n as HTMLElement).innerText));
|
||||
expect(content).toEqual(['100', '10']);
|
||||
});
|
||||
|
||||
it('should retrieve content from subtree for all', async ({page, server}) => {
|
||||
const htmlContent = '<div class="a">not-a-child-div</div><div id="myId"><div class="a">a1-child-div</div><div class="a">a2-child-div</div></div>';
|
||||
await page.setContent(htmlContent);
|
||||
const element = page.locator('#myId .a');
|
||||
const content = await element.evaluateAll(nodes => nodes.map(n => (n as HTMLElement).innerText));
|
||||
expect(content).toEqual(['a1-child-div', 'a2-child-div']);
|
||||
});
|
||||
|
||||
it('should not throw in case of missing selector for all', async ({page, server}) => {
|
||||
const htmlContent = '<div class="a">not-a-child-div</div><div id="myId"></div>';
|
||||
await page.setContent(htmlContent);
|
||||
const element = page.locator('#myId .a');
|
||||
const nodesLength = await element.evaluateAll(nodes => nodes.length);
|
||||
expect(nodesLength).toBe(0);
|
||||
});
|
106
tests/page/locator-misc-1.spec.ts
Normal file
106
tests/page/locator-misc-1.spec.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { contextTest as it, expect } from '../config/browserTest';
|
||||
import path from 'path';
|
||||
|
||||
it('should hover', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/scrollable.html');
|
||||
const button = page.locator('#button-6');
|
||||
await button.hover();
|
||||
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
|
||||
});
|
||||
|
||||
it('should hover when Node is removed', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/scrollable.html');
|
||||
await page.evaluate(() => delete window['Node']);
|
||||
const button = page.locator('#button-6');
|
||||
await button.hover();
|
||||
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
|
||||
});
|
||||
|
||||
it('should fill input', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
const handle = page.locator('input');
|
||||
await handle.fill('some value');
|
||||
expect(await page.evaluate(() => window['result'])).toBe('some value');
|
||||
});
|
||||
|
||||
it('should fill input when Node is removed', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
await page.evaluate(() => delete window['Node']);
|
||||
const handle = page.locator('input');
|
||||
await handle.fill('some value');
|
||||
expect(await page.evaluate(() => window['result'])).toBe('some value');
|
||||
});
|
||||
|
||||
it('should check the box', async ({ page }) => {
|
||||
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
|
||||
const input = page.locator('input');
|
||||
await input.check();
|
||||
expect(await page.evaluate('checkbox.checked')).toBe(true);
|
||||
});
|
||||
|
||||
it('should uncheck the box', async ({ page }) => {
|
||||
await page.setContent(`<input id='checkbox' type='checkbox' checked></input>`);
|
||||
const input = page.locator('input');
|
||||
await input.uncheck();
|
||||
expect(await page.evaluate('checkbox.checked')).toBe(false);
|
||||
});
|
||||
|
||||
it('should select single option', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/select.html');
|
||||
const select = page.locator('select');
|
||||
await select.selectOption('blue');
|
||||
expect(await page.evaluate(() => window['result'].onInput)).toEqual(['blue']);
|
||||
expect(await page.evaluate(() => window['result'].onChange)).toEqual(['blue']);
|
||||
});
|
||||
|
||||
it('should focus a button', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
const button = page.locator('button');
|
||||
expect(await button.evaluate(button => document.activeElement === button)).toBe(false);
|
||||
await button.focus();
|
||||
expect(await button.evaluate(button => document.activeElement === button)).toBe(true);
|
||||
});
|
||||
|
||||
it('should dispatch click event via ElementHandles', async ({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
const button = page.locator('button');
|
||||
await button.dispatchEvent('click');
|
||||
expect(await page.evaluate(() => window['result'])).toBe('Clicked');
|
||||
});
|
||||
|
||||
it('should upload the file', async ({page, server, asset}) => {
|
||||
await page.goto(server.PREFIX + '/input/fileupload.html');
|
||||
const filePath = path.relative(process.cwd(), asset('file-to-upload.txt'));
|
||||
const input = page.locator('input');
|
||||
await input.setInputFiles(filePath);
|
||||
expect(await page.evaluate(e => (e as HTMLInputElement).files[0].name, await input.first())).toBe('file-to-upload.txt');
|
||||
});
|
||||
|
||||
it.describe('tap group', () => {
|
||||
it.use({ hasTouch: true });
|
||||
it('should send all of the correct events', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<div id="a" style="background: lightblue; width: 50px; height: 50px">a</div>
|
||||
<div id="b" style="background: pink; width: 50px; height: 50px">b</div>
|
||||
`);
|
||||
await page.locator('#a').tap();
|
||||
await page.locator('#b').tap();
|
||||
});
|
||||
});
|
105
tests/page/locator-misc-2.spec.ts
Normal file
105
tests/page/locator-misc-2.spec.ts
Normal file
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { test as it, expect } from './pageTest';
|
||||
|
||||
it('should press', async ({ page }) => {
|
||||
await page.setContent(`<input type='text' />`);
|
||||
await page.locator('input').press('h');
|
||||
expect(await page.$eval('input', input => input.value)).toBe('h');
|
||||
});
|
||||
|
||||
it('should scroll into view', async ({ page, server, isAndroid }) => {
|
||||
it.fixme(isAndroid);
|
||||
|
||||
await page.goto(server.PREFIX + '/offscreenbuttons.html');
|
||||
for (let i = 0; i < 11; ++i) {
|
||||
const button = page.locator('#btn' + i);
|
||||
const before = await button.evaluate(button => {
|
||||
return button.getBoundingClientRect().right - window.innerWidth;
|
||||
});
|
||||
expect(before).toBe(10 * i);
|
||||
await button.scrollIntoViewIfNeeded();
|
||||
const after = await button.evaluate(button => {
|
||||
return button.getBoundingClientRect().right - window.innerWidth;
|
||||
});
|
||||
expect(after <= 0).toBe(true);
|
||||
await page.evaluate(() => window.scrollTo(0, 0));
|
||||
}
|
||||
});
|
||||
|
||||
it('should select textarea', async ({ page, server, browserName }) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
const textarea = page.locator('textarea');
|
||||
await textarea.evaluate(textarea => (textarea as HTMLTextAreaElement).value = 'some value');
|
||||
await textarea.selectText();
|
||||
if (browserName === 'firefox') {
|
||||
expect(await textarea.evaluate(el => (el as HTMLTextAreaElement).selectionStart)).toBe(0);
|
||||
expect(await textarea.evaluate(el => (el as HTMLTextAreaElement).selectionEnd)).toBe(10);
|
||||
} else {
|
||||
expect(await page.evaluate(() => window.getSelection().toString())).toBe('some value');
|
||||
}
|
||||
});
|
||||
|
||||
it('should type', async ({ page }) => {
|
||||
await page.setContent(`<input type='text' />`);
|
||||
await page.locator('input').type('hello');
|
||||
expect(await page.$eval('input', input => input.value)).toBe('hello');
|
||||
});
|
||||
|
||||
it('should wait for visible', async ({ page }) => {
|
||||
async function giveItAChanceToResolve() {
|
||||
for (let i = 0; i < 5; i++)
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
}
|
||||
|
||||
await page.setContent(`<div id='div' style='display:none'>content</div>`);
|
||||
const div = page.locator('div');
|
||||
let done = false;
|
||||
const promise = div.waitFor({ state: 'visible' }).then(() => done = true);
|
||||
await giveItAChanceToResolve();
|
||||
expect(done).toBe(false);
|
||||
await page.evaluate(() => (window as any).div.style.display = 'block');
|
||||
await promise;
|
||||
});
|
||||
|
||||
it('should wait for already visible', async ({ page }) => {
|
||||
await page.setContent(`<div>content</div>`);
|
||||
const div = page.locator('div');
|
||||
await div.waitFor({ state: 'visible' });
|
||||
});
|
||||
|
||||
it('should take screenshot', async ({ page, server, browserName, headless, isAndroid }) => {
|
||||
it.skip(browserName === 'firefox' && !headless);
|
||||
it.skip(isAndroid, 'Different dpr. Remove after using 1x scale for screenshots.');
|
||||
await page.setViewportSize({width: 500, height: 500});
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
await page.evaluate(() => window.scrollBy(50, 100));
|
||||
const element = page.locator('.box:nth-of-type(3)');
|
||||
const screenshot = await element.screenshot();
|
||||
expect(screenshot).toMatchSnapshot('screenshot-element-bounding-box.png');
|
||||
});
|
||||
|
||||
it('should return bounding box', async ({ page, server, browserName, headless }) => {
|
||||
it.fail(browserName === 'firefox' && !headless);
|
||||
|
||||
await page.setViewportSize({ width: 500, height: 500 });
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
const element = page.locator('.box:nth-of-type(13)');
|
||||
const box = await element.boundingBox();
|
||||
expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 });
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 474 B |
62
tests/page/locator-query-selector.spec.ts
Normal file
62
tests/page/locator-query-selector.spec.ts
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { test as it, expect } from './pageTest';
|
||||
|
||||
it('should query existing element', async ({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/playground.html');
|
||||
await page.setContent('<html><body><div class="second"><div class="inner">A</div></div></body></html>');
|
||||
const html = page.locator('html');
|
||||
const second = html.locator('.second');
|
||||
const inner = second.locator('.inner');
|
||||
const content = await page.evaluate(e => e.textContent, await inner.first());
|
||||
expect(content).toBe('A');
|
||||
});
|
||||
|
||||
it('should query existing elements', async ({page, server}) => {
|
||||
await page.setContent('<html><body><div>A</div><br/><div>B</div></body></html>');
|
||||
const html = page.locator('html');
|
||||
const elements = await html.locator('div').all();
|
||||
expect(elements.length).toBe(2);
|
||||
const promises = elements.map(element => page.evaluate(e => e.textContent, element));
|
||||
expect(await Promise.all(promises)).toEqual(['A', 'B']);
|
||||
});
|
||||
|
||||
it('should return empty array for non-existing elements', async ({page, server}) => {
|
||||
await page.setContent('<html><body><span>A</span><br/><span>B</span></body></html>');
|
||||
const html = page.locator('html');
|
||||
const elements = await html.locator('div').all();
|
||||
expect(elements.length).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('xpath should query existing element', async ({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/playground.html');
|
||||
await page.setContent('<html><body><div class="second"><div class="inner">A</div></div></body></html>');
|
||||
const html = page.locator('html');
|
||||
const second = html.locator(`xpath=./body/div[contains(@class, 'second')]`);
|
||||
const inner = second.locator(`xpath=./div[contains(@class, 'inner')]`);
|
||||
const content = await page.evaluate(e => e.textContent, await inner.first());
|
||||
expect(content).toBe('A');
|
||||
});
|
||||
|
||||
it('xpath should return null for non-existing element', async ({page, server}) => {
|
||||
await page.setContent('<html><body><div class="second"><div class="inner">B</div></div></body></html>');
|
||||
const html = page.locator('html');
|
||||
const second = await html.locator(`xpath=/div[contains(@class, 'third')]`).all();
|
||||
expect(second).toEqual([]);
|
||||
});
|
2
types/structs.d.ts
vendored
2
types/structs.d.ts
vendored
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JSHandle, ElementHandle, Frame, Page, BrowserContext } from './types';
|
||||
import { JSHandle, ElementHandle, Frame, Page, BrowserContext, Locator } from './types';
|
||||
|
||||
/**
|
||||
* Can be converted to JSON
|
||||
|
1139
types/types.d.ts
vendored
1139
types/types.d.ts
vendored
File diff suppressed because it is too large
Load Diff
11
utils/generate_types/overrides.d.ts
vendored
11
utils/generate_types/overrides.d.ts
vendored
@ -140,6 +140,17 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
|
||||
waitForSelector(selector: string, options: ElementHandleWaitForSelectorOptions): Promise<null|ElementHandle<SVGElement | HTMLElement>>;
|
||||
}
|
||||
|
||||
export interface Locator {
|
||||
first(options?: {
|
||||
timeout?: number;
|
||||
}): Promise<null|ElementHandle<SVGElement | HTMLElement>>;
|
||||
all(): Promise<null|ElementHandle<SVGElement | HTMLElement>[]>;
|
||||
evaluate<R, Arg>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, Arg, R>, arg: Arg): Promise<R>;
|
||||
evaluate<R>(pageFunction: PageFunctionOn<SVGElement | HTMLElement, void, R>): Promise<R>;
|
||||
evaluateAll<R, Arg>(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], Arg, R>, arg: Arg): Promise<R>;
|
||||
evaluateAll<R>(pageFunction: PageFunctionOn<(SVGElement | HTMLElement)[], void, R>): Promise<R>;
|
||||
}
|
||||
|
||||
export interface BrowserType<Unused = {}> {
|
||||
connectOverCDP(endpointURL: string, options?: ConnectOverCDPOptions): Promise<Browser>;
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user