Skip to content

Commit

Permalink
Add: Locator.all/1
Browse files Browse the repository at this point in the history
Relatedly, refactor `Locator.count/1`, used by `all/1`, to make use of the new
`Frame.query_count/2`.

Also, extract some `Locator` module docs which will soon find their way into
guides.
  • Loading branch information
coreyti committed Aug 8, 2024
1 parent 15f5dc7 commit 7c8c3e6
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 57 deletions.
10 changes: 10 additions & 0 deletions lib/playwright/frame.ex
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,16 @@ defmodule Playwright.Frame do
Channel.post(session, {:guid, frame.guid}, :press, Map.merge(%{selector: selector, key: key}, options))
end

# 20240807: Official implementations define (something similar to) this as
# a pseudo-private function/method. As such, our implementation is hidden
# from documentation for the time being. Only called from `Locator.count/1`.
# ---
@doc false
@spec query_count(t(), binary()) :: number()
def query_count(%Frame{session: session} = frame, selector) do
Channel.post(session, {:guid, frame.guid}, :query_count, %{selector: selector})
end

@doc """
Returns the `Playwright.ElementHandle` pointing to the frame element.
Expand Down
93 changes: 36 additions & 57 deletions lib/playwright/locator.ex
Original file line number Diff line number Diff line change
@@ -1,58 +1,16 @@
defmodule Playwright.Locator do
@moduledoc """
`Playwright.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 `Playwright.Locator.new/2` function.
Locators are the central piece of Playwright's auto-waiting and retry-ability.
In a nutshell, locators represent a way to find element(s) on the page at any
moment. A locator may be created with the `Page.locator/2` function.
See also `Playwright.Page.locator/2`.
Instances of `Playwright.Locator` may be created via the following means:
## Example
locator = Playwright.Locator.new(page, "a#exists")
Playwright.Locator.click(locator)
The difference between the `Playwright.Locator` and `Playwright.ElementHandle`
is that the latter points to a particular element, while `Playwright.Locator` captures
the logic of how to retrieve that element.
## ElementHandle Example
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. This can lead
to unexpected behaviors.
handle = Page.query_selector(page, "text=Submit")
ElementHandle.hover(handle)
ElementHandle.click(handle)
## Locator Example
With the locator, every time the element is used, up-to-date DOM element is
located in the page using the selector. So in the snippet below, underlying
DOM element is going to be located twice.
locator = Playwright.Locator.new(page, "a#exists")
:ok = Playwright.Locator.hover(locator)
:ok = Playwright.Locator.click(locator)
## Strictness
Locators are strict. This means that all operations on locators that imply
some target DOM element will throw if more than one element matches given
selector.
alias Playwright.Locator
locator = Locator.new(page, "button")
- `Playwright.Locator.new/2`
- `Playwright.Frame.locator/2`
- `Playwright.Page.locator/2`
# Throws if there are several buttons in DOM:
Locator.click(locator)
# Works because we explicitly tell locator to pick the first element:
Locator.first(locator) |> Locator.click()
# Works because count knows what to do with multiple matches:
Locator.count(locator)
[Learn more about locators](guides-locators.html).
"""

alias Playwright.{ElementHandle, Frame, Locator, Page}
Expand Down Expand Up @@ -98,11 +56,11 @@ defmodule Playwright.Locator do
@type serializable :: any()

@doc """
Returns a `%Playwright.Locator{}`.
Returns a `Playwright.Locator`.
## Arguments
| key/name | type | | description |
| key/name | type | | description |
| ---------- | ------ | ---------------------- | ----------- |
| `frame` | param | `Frame.t() | Page.t()` | |
| `selector` | param | `binary()` | A Playwright selector. |
Expand All @@ -124,8 +82,29 @@ defmodule Playwright.Locator do
}
end

# @spec all(Locator.t()) :: [Locator.t()]
# def all(locator)
@doc """
When the locator points to a list of elements, returns a list of locators,
each addressing their respective elements.
> ### NOTE {: .warning}
>
> `Playwright.Locator.all/1` does not wait for elements to match the locator,
> and instead immediately returns whatever is present in the page. When the
> list of elements changes dynamically, `Playwright.Locator.all/1` will
> produce unpredictable and flaky results. When the list of elements is
> stable, but loaded dynamically, wait for the full list to finish loading
> before calling `Playwright.Locator.all/1``.
## Example
...
"""
@spec all(Locator.t()) :: [Locator.t()]
def all(locator) do
Enum.map(1..count(locator), fn n ->
Locator.nth(locator, n - 1)
end)
end

@doc """
Returns an list of `node.innerText` values for all matching nodes.
Expand Down Expand Up @@ -284,9 +263,9 @@ defmodule Playwright.Locator do
- `number()`
"""
@spec count(t()) :: number()
@spec count(Locator.t()) :: number()
def count(%Locator{} = locator) do
evaluate_all(locator, "ee => ee.length")
Frame.query_count(locator.frame, locator.selector)
end

@doc """
Expand Down Expand Up @@ -629,7 +608,7 @@ defmodule Playwright.Locator do
## Arguments
| key/name | type | | description |
| key/name | type | | description |
| ---------- | ------ | ---------- | ----------- |
| `name` | param | `binary()` | Name of the attribute to retrieve. |
| `:timeout` | option | `number()` | Maximum time in milliseconds. Pass `0` to disable timeout. The default value can be changed by using the `Playwright.BrowserContext.set_default_timeout/2` or `Playwright.Page.set_default_timeout/2` functions. `(default: 30 seconds)` |
Expand Down
13 changes: 13 additions & 0 deletions test/api/locator_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ defmodule Playwright.LocatorTest do
alias Playwright.{ElementHandle, Locator, Page}
alias Playwright.SDK.Channel.Error

describe "Locator.all/1" do
test "returns a list of Locators, addressing each respective element", %{page: page} do
Page.set_content(page, "<div><p>A</p><p>B</p><p>C</p></div>")

result =
Page.locator(page, "p")
|> Locator.all()
|> Enum.map(fn locator -> Locator.text_content(locator) end)

assert ["A", "B", "C"] = result
end
end

describe "Locator.all_inner_texts/1" do
test "...", %{page: page} do
Page.set_content(page, "<div>A</div><div>B</div><div>C</div>")
Expand Down

0 comments on commit 7c8c3e6

Please sign in to comment.