playwright-ruby-client 1.16.beta1 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
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