playwright-ruby-client 1.16.beta1 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/docs/api/{fetch_request.md → api_request_context.md} +2 -2
  3. data/documentation/docs/api/browser_context.md +1 -1
  4. data/documentation/docs/api/element_handle.md +2 -3
  5. data/documentation/docs/api/frame.md +30 -0
  6. data/documentation/docs/api/frame_locator.md +70 -0
  7. data/documentation/docs/api/locator.md +17 -2
  8. data/documentation/docs/api/page.md +35 -4
  9. data/documentation/docs/api/request.md +3 -3
  10. data/documentation/docs/api/selectors.md +1 -1
  11. data/documentation/docs/api/tracing.md +2 -2
  12. data/documentation/docs/include/api_coverage.md +25 -7
  13. data/lib/playwright/channel.rb +1 -1
  14. data/lib/playwright/channel_owners/api_request_context.rb +4 -0
  15. data/lib/playwright/channel_owners/artifact.rb +1 -6
  16. data/lib/playwright/channel_owners/browser.rb +6 -8
  17. data/lib/playwright/channel_owners/browser_context.rb +5 -3
  18. data/lib/playwright/channel_owners/browser_type.rb +0 -1
  19. data/lib/playwright/channel_owners/fetch_request.rb +5 -1
  20. data/lib/playwright/channel_owners/frame.rb +10 -6
  21. data/lib/playwright/channel_owners/page.rb +18 -10
  22. data/lib/playwright/channel_owners/request.rb +9 -21
  23. data/lib/playwright/channel_owners/response.rb +0 -4
  24. data/lib/playwright/connection.rb +9 -0
  25. data/lib/playwright/frame_locator_impl.rb +49 -0
  26. data/lib/playwright/locator_impl.rb +13 -5
  27. data/lib/playwright/route_handler.rb +13 -1
  28. data/lib/playwright/tracing_impl.rb +6 -9
  29. data/lib/playwright/version.rb +2 -2
  30. data/lib/playwright/video.rb +3 -0
  31. data/lib/playwright.rb +2 -1
  32. data/lib/playwright_api/api_request_context.rb +149 -0
  33. data/lib/playwright_api/browser_context.rb +6 -1
  34. data/lib/playwright_api/element_handle.rb +2 -3
  35. data/lib/playwright_api/frame.rb +26 -1
  36. data/lib/playwright_api/frame_locator.rb +51 -0
  37. data/lib/playwright_api/locator.rb +12 -2
  38. data/lib/playwright_api/page.rb +35 -9
  39. data/lib/playwright_api/playwright.rb +5 -0
  40. data/lib/playwright_api/selectors.rb +2 -2
  41. data/lib/playwright_api/tracing.rb +4 -4
  42. metadata +12 -8
  43. data/lib/playwright_api/fetch_request.rb +0 -77
@@ -84,39 +84,27 @@ module Playwright
84
84
 
85
85
  # @return [RawHeaders|nil]
86
86
  private def actual_headers
87
- @actual_headers ||= response&.send(:raw_request_headers)
87
+ @actual_headers ||= raw_request_headers
88
+ end
89
+
90
+ private def raw_request_headers
91
+ RawHeaders.new(@channel.send_message_to_server('rawRequestHeaders'))
88
92
  end
89
93
 
90
94
  def all_headers
91
- if actual_headers
92
- actual_headers.headers
93
- else
94
- @provisional_headers.headers
95
- end
95
+ actual_headers.headers
96
96
  end
97
97
 
98
98
  def headers_array
99
- if actual_headers
100
- actual_headers.headers_array
101
- else
102
- @provisional_headers.headers_array
103
- end
99
+ actual_headers.headers_array
104
100
  end
105
101
 
106
102
  def header_value(name)
107
- if actual_headers
108
- actual_headers.get(name)
109
- else
110
- @provisional_headers.get(name)
111
- end
103
+ actual_headers.get(name)
112
104
  end
113
105
 
114
106
  def header_values(name)
115
- if actual_headers
116
- actual_headers.get_all(name)
117
- else
118
- @provisional_headers.get_all(name)
119
- end
107
+ actual_headers.get_all(name)
120
108
  end
121
109
 
122
110
  def sizes
@@ -48,10 +48,6 @@ module Playwright
48
48
  @actual_headers ||= raw_response_headers
49
49
  end
50
50
 
51
- private def raw_request_headers
52
- RawHeaders.new(@channel.send_message_to_server('rawRequestHeaders'))
53
- end
54
-
55
51
  private def raw_response_headers
56
52
  RawHeaders.new(@channel.send_message_to_server('rawResponseHeaders'))
57
53
  end
@@ -21,6 +21,15 @@ module Playwright
21
21
  @waiting_for_object = {} # Hash[ guid => Promise<ChannelOwner> ]
22
22
  @callbacks = {} # Hash [ guid => Promise<ChannelOwner> ]
23
23
  @root_object = RootChannelOwner.new(self)
24
+ @remote = false
25
+ end
26
+
27
+ def mark_as_remote
28
+ @remote = true
29
+ end
30
+
31
+ def remote?
32
+ @remote
24
33
  end
25
34
 
26
35
  def async_run
@@ -0,0 +1,49 @@
1
+ module Playwright
2
+ define_api_implementation :FrameLocatorImpl do
3
+ def initialize(frame:, timeout_settings:, frame_selector:)
4
+ @frame = frame
5
+ @timeout_settings = timeout_settings
6
+ @frame_selector = frame_selector
7
+ end
8
+
9
+ def locator(selector)
10
+ LocatorImpl.new(
11
+ frame: @frame,
12
+ timeout_settings: @timeout_settings,
13
+ selector: "#{@frame_selector} >> control=enter-frame >> #{selector}",
14
+ )
15
+ end
16
+
17
+ def frame_locator(selector)
18
+ FrameLocatorImpl.new(
19
+ frame: @frame,
20
+ timeout_settings: @timeout_settings,
21
+ frame_selector: "#{@frame_selector} >> control=enter-frame >> #{selector}",
22
+ )
23
+ end
24
+
25
+ def first
26
+ FrameLocatorImpl.new(
27
+ frame: @frame,
28
+ timeout_settings: @timeout_settings,
29
+ frame_selector: "#{@frame_selector} >> nth=0",
30
+ )
31
+ end
32
+
33
+ def last
34
+ FrameLocatorImpl.new(
35
+ frame: @frame,
36
+ timeout_settings: @timeout_settings,
37
+ frame_selector: "#{@frame_selector} >> nth=-1",
38
+ )
39
+ end
40
+
41
+ def nth(index)
42
+ FrameLocatorImpl.new(
43
+ frame: @frame,
44
+ timeout_settings: @timeout_settings,
45
+ frame_selector: "#{@frame_selector} >> nth=#{index}",
46
+ )
47
+ end
48
+ end
49
+ end
@@ -11,17 +11,17 @@ module Playwright
11
11
  end
12
12
 
13
13
  private def with_element(timeout: nil, &block)
14
+ timeout_or_default = @timeout_settings.timeout(timeout)
14
15
  start_time = Time.now
15
16
 
16
- handle = @frame.wait_for_selector(@selector, strict: true, state: 'attached', timeout: timeout)
17
+ handle = @frame.wait_for_selector(@selector, strict: true, state: 'attached', timeout: timeout_or_default)
17
18
  unless handle
18
19
  raise "Could not resolve #{@selector} to DOM Element"
19
20
  end
20
21
 
21
- call_options = {}
22
- if timeout
23
- call_options[:timeout] = (timeout - (Time.now - start_time) * 1000).to_i
24
- end
22
+ call_options = {
23
+ timeout: (timeout_or_default - (Time.now - start_time) * 1000).to_i,
24
+ }
25
25
 
26
26
  begin
27
27
  block.call(handle, call_options)
@@ -130,6 +130,14 @@ module Playwright
130
130
  )
131
131
  end
132
132
 
133
+ def frame_locator(selector)
134
+ FrameLocatorImpl.new(
135
+ frame: @frame,
136
+ timeout_settings: @timeout_settings,
137
+ frame_selector: "#{@selector} >> #{selector}",
138
+ )
139
+ end
140
+
133
141
  def element_handle(timeout: nil)
134
142
  @frame.wait_for_selector(@selector, strict: true, state: 'attached', timeout: timeout)
135
143
  end
@@ -6,17 +6,25 @@ module Playwright
6
6
  end
7
7
 
8
8
  def handle
9
- return false if @count <= 0
9
+ return false if expired?
10
10
 
11
11
  @count = @count - 1
12
12
  true
13
13
  end
14
+
15
+ def expired?
16
+ @count <= 0
17
+ end
14
18
  end
15
19
 
16
20
  class StubCounter
17
21
  def handle
18
22
  true
19
23
  end
24
+
25
+ def expired?
26
+ false
27
+ end
20
28
  end
21
29
 
22
30
  # @param url [String]
@@ -46,6 +54,10 @@ module Playwright
46
54
  end
47
55
  end
48
56
 
57
+ def expired?
58
+ @counter.expired?
59
+ end
60
+
49
61
  def same_value?(url:, handler: nil)
50
62
  if handler
51
63
  @url_value == url && @handler == handler
@@ -5,18 +5,18 @@ module Playwright
5
5
  @context = context
6
6
  end
7
7
 
8
- def start(name: nil, screenshots: nil, snapshots: nil)
8
+ def start(name: nil, title: nil, screenshots: nil, snapshots: nil)
9
9
  params = {
10
10
  name: name,
11
11
  screenshots: screenshots,
12
12
  snapshots: snapshots,
13
13
  }.compact
14
14
  @channel.send_message_to_server('tracingStart', params)
15
- @channel.send_message_to_server('tracingStartChunk')
15
+ @channel.send_message_to_server('tracingStartChunk', { title: title }.compact)
16
16
  end
17
17
 
18
- def start_chunk
19
- @channel.send_message_to_server('tracingStartChunk')
18
+ def start_chunk(title: nil)
19
+ @channel.send_message_to_server('tracingStartChunk', { title: title }.compact)
20
20
  end
21
21
 
22
22
  def stop_chunk(path: nil)
@@ -29,13 +29,10 @@ module Playwright
29
29
  end
30
30
 
31
31
  private def do_stop_chunk(path:)
32
- resp = @channel.send_message_to_server('tracingStopChunk', save: !path.nil?)
33
- artifact = ChannelOwners::Artifact.from_nullable(resp)
32
+ result = @channel.send_message_to_server_result('tracingStopChunk', save: !path.nil?, skipCompress: false)
33
+ artifact = ChannelOwners::Artifact.from_nullable(result['artifact'])
34
34
  return unless artifact
35
35
 
36
- if @context.browser.send(:remote?)
37
- artifact.update_as_remote
38
- end
39
36
  artifact.save_as(path)
40
37
  artifact.delete
41
38
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '1.16.beta1'
5
- COMPATIBLE_PLAYWRIGHT_VERSION = '1.16.0'
4
+ VERSION = '1.17.0'
5
+ COMPATIBLE_PLAYWRIGHT_VERSION = '1.17.0'
6
6
  end
@@ -22,6 +22,9 @@ module Playwright
22
22
  end
23
23
 
24
24
  def path
25
+ if @page.send(:remote_connection?)
26
+ raise 'Path is not available when using browserType.connect(). Use save_as() to save a local copy.'
27
+ end
25
28
  wait_for_artifact_and do |artifact|
26
29
  artifact.absolute_path
27
30
  end
data/lib/playwright.rb CHANGED
@@ -135,13 +135,14 @@ module Playwright
135
135
 
136
136
  transport = WebSocketTransport.new(ws_endpoint: ws_endpoint)
137
137
  connection = Connection.new(transport)
138
+ connection.mark_as_remote
138
139
  connection.async_run
139
140
 
140
141
  execution =
141
142
  begin
142
143
  playwright = connection.initialize_playwright
143
144
  browser = playwright.send(:pre_launched_browser)
144
- browser.send(:update_as_remote)
145
+ browser.should_close_connection_on_close!
145
146
  Execution.new(connection, PlaywrightApi.wrap(playwright), PlaywrightApi.wrap(browser))
146
147
  rescue
147
148
  connection.stop
@@ -0,0 +1,149 @@
1
+ module Playwright
2
+ # This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare
3
+ # environment or the service to your e2e test. When used on `Page` or a `BrowserContext`, this API will automatically use
4
+ # the cookies from the corresponding `BrowserContext`. This means that if you log in using this API, your e2e test will be
5
+ # logged in and vice versa.
6
+ class APIRequestContext < PlaywrightApi
7
+
8
+ # Sends HTTP(S) [DELETE](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) request and returns its
9
+ # response. The method will populate request cookies from the context and update context cookies from the response. The
10
+ # method will automatically follow redirects.
11
+ def delete(
12
+ url,
13
+ data: nil,
14
+ failOnStatusCode: nil,
15
+ form: nil,
16
+ headers: nil,
17
+ ignoreHTTPSErrors: nil,
18
+ multipart: nil,
19
+ params: nil,
20
+ timeout: nil)
21
+ raise NotImplementedError.new('delete is not implemented yet.')
22
+ end
23
+
24
+ # All responses returned by [`method: APIRequestContext.get`] and similar methods are stored in the memory, so that you
25
+ # can later call [`method: APIResponse.body`]. This method discards all stored responses, and makes
26
+ # [`method: APIResponse.body`] throw "Response disposed" error.
27
+ def dispose
28
+ raise NotImplementedError.new('dispose is not implemented yet.')
29
+ end
30
+
31
+ # Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
32
+ # context cookies from the response. The method will automatically follow redirects.
33
+ def fetch(
34
+ urlOrRequest,
35
+ data: nil,
36
+ failOnStatusCode: nil,
37
+ form: nil,
38
+ headers: nil,
39
+ ignoreHTTPSErrors: nil,
40
+ method: nil,
41
+ multipart: nil,
42
+ params: nil,
43
+ timeout: nil)
44
+ raise NotImplementedError.new('fetch is not implemented yet.')
45
+ end
46
+
47
+ # Sends HTTP(S) [GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) request and returns its response. The
48
+ # method will populate request cookies from the context and update context cookies from the response. The method will
49
+ # automatically follow redirects.
50
+ def get(
51
+ url,
52
+ failOnStatusCode: nil,
53
+ headers: nil,
54
+ ignoreHTTPSErrors: nil,
55
+ params: nil,
56
+ timeout: nil)
57
+ raise NotImplementedError.new('get is not implemented yet.')
58
+ end
59
+
60
+ # Sends HTTP(S) [HEAD](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) request and returns its response.
61
+ # The method will populate request cookies from the context and update context cookies from the response. The method will
62
+ # automatically follow redirects.
63
+ def head(
64
+ url,
65
+ failOnStatusCode: nil,
66
+ headers: nil,
67
+ ignoreHTTPSErrors: nil,
68
+ params: nil,
69
+ timeout: nil)
70
+ raise NotImplementedError.new('head is not implemented yet.')
71
+ end
72
+
73
+ # Sends HTTP(S) [PATCH](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) request and returns its response.
74
+ # The method will populate request cookies from the context and update context cookies from the response. The method will
75
+ # automatically follow redirects.
76
+ def patch(
77
+ url,
78
+ data: nil,
79
+ failOnStatusCode: nil,
80
+ form: nil,
81
+ headers: nil,
82
+ ignoreHTTPSErrors: nil,
83
+ multipart: nil,
84
+ params: nil,
85
+ timeout: nil)
86
+ raise NotImplementedError.new('patch is not implemented yet.')
87
+ end
88
+
89
+ # Sends HTTP(S) [POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) request and returns its response.
90
+ # The method will populate request cookies from the context and update context cookies from the response. The method will
91
+ # automatically follow redirects.
92
+ def post(
93
+ url,
94
+ data: nil,
95
+ failOnStatusCode: nil,
96
+ form: nil,
97
+ headers: nil,
98
+ ignoreHTTPSErrors: nil,
99
+ multipart: nil,
100
+ params: nil,
101
+ timeout: nil)
102
+ raise NotImplementedError.new('post is not implemented yet.')
103
+ end
104
+
105
+ # Sends HTTP(S) [PUT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) request and returns its response. The
106
+ # method will populate request cookies from the context and update context cookies from the response. The method will
107
+ # automatically follow redirects.
108
+ def put(
109
+ url,
110
+ data: nil,
111
+ failOnStatusCode: nil,
112
+ form: nil,
113
+ headers: nil,
114
+ ignoreHTTPSErrors: nil,
115
+ multipart: nil,
116
+ params: nil,
117
+ timeout: nil)
118
+ raise NotImplementedError.new('put is not implemented yet.')
119
+ end
120
+
121
+ # Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
122
+ # the constructor.
123
+ def storage_state(path: nil)
124
+ raise NotImplementedError.new('storage_state is not implemented yet.')
125
+ end
126
+
127
+ # -- inherited from EventEmitter --
128
+ # @nodoc
129
+ def off(event, callback)
130
+ event_emitter_proxy.off(event, callback)
131
+ end
132
+
133
+ # -- inherited from EventEmitter --
134
+ # @nodoc
135
+ def once(event, callback)
136
+ event_emitter_proxy.once(event, callback)
137
+ end
138
+
139
+ # -- inherited from EventEmitter --
140
+ # @nodoc
141
+ def on(event, callback)
142
+ event_emitter_proxy.on(event, callback)
143
+ end
144
+
145
+ private def event_emitter_proxy
146
+ @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
147
+ end
148
+ end
149
+ end
@@ -6,7 +6,7 @@ module Playwright
6
6
  # If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser
7
7
  # context.
8
8
  #
9
- # Playwright allows creation of "incognito" browser contexts with `browser.newContext()` method. "Incognito" browser
9
+ # Playwright allows creating "incognito" browser contexts with [`method: Browser.newContext`] method. "Incognito" browser
10
10
  # contexts don't write any browsing data to disk.
11
11
  #
12
12
  # ```python sync
@@ -20,6 +20,11 @@ module Playwright
20
20
  # ```
21
21
  class BrowserContext < PlaywrightApi
22
22
 
23
+ # API testing helper associated with this context. Requests made with this API will use context cookies.
24
+ def request # property
25
+ raise NotImplementedError.new('request is not implemented yet.')
26
+ end
27
+
23
28
  def tracing # property
24
29
  wrap_impl(@impl.tracing)
25
30
  end
@@ -6,6 +6,8 @@ module Playwright
6
6
  # ElementHandle represents an in-page DOM element. ElementHandles can be created with the [`method: Page.querySelector`]
7
7
  # method.
8
8
  #
9
+ # > NOTE: The use of ElementHandle is discouraged, use `Locator` objects and web-first assertions instead.
10
+ #
9
11
  # ```python sync
10
12
  # href_element = page.query_selector("a")
11
13
  # href_element.click()
@@ -17,9 +19,6 @@ module Playwright
17
19
  # ElementHandle instances can be used as an argument in [`method: Page.evalOnSelector`] and [`method: Page.evaluate`]
18
20
  # methods.
19
21
  #
20
- # > NOTE: In most cases, you would want to use the `Locator` object instead. You should only use `ElementHandle` if you
21
- # want to retain a handle to a particular DOM Node that you intend to pass into [`method: Page.evaluate`] as an argument.
22
- #
23
22
  # The difference between the `Locator` and ElementHandle is that the ElementHandle points to a particular element, while
24
23
  # `Locator` captures the logic of how to retrieve an element.
25
24
  #
@@ -183,6 +183,9 @@ module Playwright
183
183
 
184
184
  # Returns the return value of `expression`.
185
185
  #
186
+ # > NOTE: This method does not wait for the element to pass actionability checks and therefore can lead to the flaky
187
+ # tests. Use [`method: Locator.evaluate`], other `Locator` helper methods or web-first assertions instead.
188
+ #
186
189
  # The method finds an element matching the specified selector within the frame and passes it as a first argument to
187
190
  # `expression`. See [Working with selectors](./selectors.md) for more details. If no elements match the selector, the
188
191
  # method throws an error.
@@ -203,6 +206,9 @@ module Playwright
203
206
 
204
207
  # Returns the return value of `expression`.
205
208
  #
209
+ # > NOTE: In most cases, [`method: Locator.evaluateAll`], other `Locator` helper methods and web-first assertions do a
210
+ # better job.
211
+ #
206
212
  # The method finds all elements matching the specified selector within the frame and passes an array of matched elements
207
213
  # as a first argument to `expression`. See [Working with selectors](./selectors.md) for more details.
208
214
  #
@@ -243,7 +249,7 @@ module Playwright
243
249
  # `ElementHandle` instances can be passed as an argument to the [`method: Frame.evaluate`]:
244
250
  #
245
251
  # ```python sync
246
- # body_handle = frame.query_selector("body")
252
+ # body_handle = frame.evaluate("document.body")
247
253
  # html = frame.evaluate("([body, suffix]) => body.innerHTML + suffix", [body_handle, "hello"])
248
254
  # body_handle.dispose()
249
255
  # ```
@@ -324,6 +330,18 @@ module Playwright
324
330
  wrap_impl(@impl.frame_element)
325
331
  end
326
332
 
333
+ # When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
334
+ # that iframe. Following snippet locates element with text "Submit" in the iframe with id `my-frame`, like `<iframe
335
+ # id="my-frame">`:
336
+ #
337
+ # ```python sync
338
+ # locator = frame.frame_locator("#my-iframe").locator("text=Submit")
339
+ # locator.click()
340
+ # ```
341
+ def frame_locator(selector)
342
+ wrap_impl(@impl.frame_locator(unwrap_impl(selector)))
343
+ end
344
+
327
345
  # Returns element attribute value.
328
346
  def get_attribute(selector, name, strict: nil, timeout: nil)
329
347
  wrap_impl(@impl.get_attribute(unwrap_impl(selector), unwrap_impl(name), strict: unwrap_impl(strict), timeout: unwrap_impl(timeout)))
@@ -478,6 +496,8 @@ module Playwright
478
496
 
479
497
  # Returns the ElementHandle pointing to the frame element.
480
498
  #
499
+ # > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects and web-first assertions instead.
500
+ #
481
501
  # The method finds an element matching the specified selector within the frame. See
482
502
  # [Working with selectors](./selectors.md) for more details. If no elements match the selector, returns `null`.
483
503
  def query_selector(selector, strict: nil)
@@ -486,6 +506,8 @@ module Playwright
486
506
 
487
507
  # Returns the ElementHandles pointing to the frame elements.
488
508
  #
509
+ # > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects instead.
510
+ #
489
511
  # The method finds all elements matching the specified selector within the frame. See
490
512
  # [Working with selectors](./selectors.md) for more details. If no elements match the selector, returns empty array.
491
513
  def query_selector_all(selector)
@@ -714,6 +736,9 @@ module Playwright
714
736
  # Returns when element specified by selector satisfies `state` option. Returns `null` if waiting for `hidden` or
715
737
  # `detached`.
716
738
  #
739
+ # > NOTE: Playwright automatically waits for element to be ready before performing an action. Using `Locator` objects and
740
+ # web-first assertions make the code wait-for-selector-free.
741
+ #
717
742
  # Wait for the `selector` to satisfy `state` option (either appear/disappear from dom, or become visible/hidden). If at
718
743
  # the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the
719
744
  # selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw.
@@ -0,0 +1,51 @@
1
+ module Playwright
2
+ # FrameLocator represents a view to the `iframe` on the page. It captures the logic sufficient to retrieve the `iframe`
3
+ # and locate elements in that iframe. FrameLocator can be created with either [`method: Page.frameLocator`] or
4
+ # [`method: Locator.frameLocator`] method.
5
+ #
6
+ # ```python sync
7
+ # locator = page.frame_locator("my-frame").locator("text=Submit")
8
+ # locator.click()
9
+ # ```
10
+ #
11
+ # **Strictness**
12
+ #
13
+ # Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
14
+ # given selector.
15
+ #
16
+ # ```python sync
17
+ # # Throws if there are several frames in DOM:
18
+ # page.frame_locator('.result-frame').locator('button').click()
19
+ #
20
+ # # Works because we explicitly tell locator to pick the first frame:
21
+ # page.frame_locator('.result-frame').first.locator('button').click()
22
+ # ```
23
+ class FrameLocator < PlaywrightApi
24
+
25
+ # Returns locator to the first matching frame.
26
+ def first
27
+ wrap_impl(@impl.first)
28
+ end
29
+
30
+ # When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
31
+ # that iframe.
32
+ def frame_locator(selector)
33
+ wrap_impl(@impl.frame_locator(unwrap_impl(selector)))
34
+ end
35
+
36
+ # Returns locator to the last matching frame.
37
+ def last
38
+ wrap_impl(@impl.last)
39
+ end
40
+
41
+ # The method finds an element matching the specified selector in the FrameLocator's subtree.
42
+ def locator(selector)
43
+ wrap_impl(@impl.locator(unwrap_impl(selector)))
44
+ end
45
+
46
+ # Returns locator to the n-th matching frame.
47
+ def nth(index)
48
+ wrap_impl(@impl.nth(unwrap_impl(index)))
49
+ end
50
+ end
51
+ end
@@ -264,6 +264,17 @@ module Playwright
264
264
  wrap_impl(@impl.focus(timeout: unwrap_impl(timeout)))
265
265
  end
266
266
 
267
+ # When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
268
+ # that iframe:
269
+ #
270
+ # ```python sync
271
+ # locator = page.frame_locator("text=Submit").locator("text=Submit")
272
+ # locator.click()
273
+ # ```
274
+ def frame_locator(selector)
275
+ wrap_impl(@impl.frame_locator(unwrap_impl(selector)))
276
+ end
277
+
267
278
  # Returns element attribute value.
268
279
  def get_attribute(name, timeout: nil)
269
280
  wrap_impl(@impl.get_attribute(unwrap_impl(name), timeout: unwrap_impl(timeout)))
@@ -338,8 +349,7 @@ module Playwright
338
349
  wrap_impl(@impl.last)
339
350
  end
340
351
 
341
- # The method finds an element matching the specified selector in the `Locator`'s subtree. See
342
- # [Working with selectors](./selectors.md) for more details.
352
+ # The method finds an element matching the specified selector in the `Locator`'s subtree.
343
353
  def locator(selector)
344
354
  wrap_impl(@impl.locator(unwrap_impl(selector)))
345
355
  end