playwright-ruby-client 1.16.0 → 1.18.beta1

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/api_request_context.md +49 -0
  3. data/documentation/docs/api/download.md +1 -3
  4. data/documentation/docs/api/element_handle.md +2 -3
  5. data/documentation/docs/api/frame.md +31 -1
  6. data/documentation/docs/api/frame_locator.md +79 -0
  7. data/documentation/docs/api/locator.md +36 -49
  8. data/documentation/docs/api/page.md +34 -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 +18 -0
  13. data/lib/playwright/channel.rb +1 -1
  14. data/lib/playwright/channel_owners/artifact.rb +1 -6
  15. data/lib/playwright/channel_owners/browser.rb +6 -8
  16. data/lib/playwright/channel_owners/browser_context.rb +5 -3
  17. data/lib/playwright/channel_owners/browser_type.rb +0 -1
  18. data/lib/playwright/channel_owners/fetch_request.rb +2 -0
  19. data/lib/playwright/channel_owners/frame.rb +10 -6
  20. data/lib/playwright/channel_owners/local_utils.rb +14 -0
  21. data/lib/playwright/channel_owners/page.rb +13 -10
  22. data/lib/playwright/connection.rb +9 -0
  23. data/lib/playwright/frame_locator_impl.rb +50 -0
  24. data/lib/playwright/locator_impl.rb +75 -8
  25. data/lib/playwright/tracing_impl.rb +25 -14
  26. data/lib/playwright/version.rb +2 -2
  27. data/lib/playwright/video.rb +3 -0
  28. data/lib/playwright.rb +2 -1
  29. data/lib/playwright_api/android_device.rb +4 -4
  30. data/lib/playwright_api/api_request_context.rb +50 -0
  31. data/lib/playwright_api/browser_context.rb +11 -6
  32. data/lib/playwright_api/download.rb +0 -4
  33. data/lib/playwright_api/element_handle.rb +2 -3
  34. data/lib/playwright_api/frame.rb +28 -3
  35. data/lib/playwright_api/frame_locator.rb +60 -0
  36. data/lib/playwright_api/local_utils.rb +9 -0
  37. data/lib/playwright_api/locator.rb +28 -48
  38. data/lib/playwright_api/page.rb +36 -6
  39. data/lib/playwright_api/playwright.rb +9 -4
  40. data/lib/playwright_api/selectors.rb +2 -2
  41. data/lib/playwright_api/tracing.rb +4 -4
  42. data/playwright.gemspec +1 -1
  43. metadata +13 -8
@@ -1,3 +1,5 @@
1
+ require_relative './api_request_context'
2
+
1
3
  module Playwright
2
4
  module ChannelOwners
3
5
  class FetchRequest < APIRequestContext
@@ -110,8 +110,8 @@ module Playwright
110
110
  def wait_for_load_state(state: nil, timeout: nil)
111
111
  option_state = state || 'load'
112
112
  option_timeout = timeout || @page.send(:timeout_settings).navigation_timeout
113
- unless %w(load domcontentloaded networkidle).include?(option_state)
114
- raise ArgumentError.new('state: expected one of (load|domcontentloaded|networkidle)')
113
+ unless %w(load domcontentloaded networkidle commit).include?(option_state)
114
+ raise ArgumentError.new('state: expected one of (load|domcontentloaded|networkidle|commit)')
115
115
  end
116
116
  if @load_states.include?(option_state)
117
117
  return
@@ -191,10 +191,6 @@ module Playwright
191
191
  @channel.send_message_to_server('isVisible', params)
192
192
  end
193
193
 
194
- def locator(selector)
195
- LocatorImpl.new(frame: self, timeout_settings: @page.send(:timeout_settings), selector: selector)
196
- end
197
-
198
194
  def dispatch_event(selector, type, eventInit: nil, strict: nil, timeout: nil)
199
195
  params = {
200
196
  selector: selector,
@@ -404,6 +400,14 @@ module Playwright
404
400
  nil
405
401
  end
406
402
 
403
+ def locator(selector, hasText: nil)
404
+ LocatorImpl.new(frame: self, timeout_settings: @page.send(:timeout_settings), selector: selector, hasText: hasText)
405
+ end
406
+
407
+ def frame_locator(selector)
408
+ FrameLocatorImpl.new(frame: self, timeout_settings: @page.send(:timeout_settings), frame_selector: selector)
409
+ end
410
+
407
411
  def focus(selector, strict: nil, timeout: nil)
408
412
  params = { selector: selector, strict: strict, timeout: timeout }.compact
409
413
  @channel.send_message_to_server('focus', params)
@@ -0,0 +1,14 @@
1
+ module Playwright
2
+ define_channel_owner :LocalUtils do
3
+ # @param zip_file [String]
4
+ # @param name_value_array [Array<Hash<{name: string, value: string}>>]
5
+ def zip(zip_file, name_value_array)
6
+ params = {
7
+ zipFile: zip_file,
8
+ entries: name_value_array,
9
+ }
10
+ @channel.send_message_to_server('zip', params)
11
+ nil
12
+ end
13
+ end
14
+ end
@@ -138,9 +138,6 @@ module Playwright
138
138
 
139
139
  private def on_download(params)
140
140
  artifact = ChannelOwners::Artifact.from(params['artifact'])
141
- if @browser_context.browser.send(:remote?)
142
- artifact.update_as_remote
143
- end
144
141
  download = DownloadImpl.new(
145
142
  page: self,
146
143
  url: params['url'],
@@ -152,9 +149,6 @@ module Playwright
152
149
 
153
150
  private def on_video(params)
154
151
  artifact = ChannelOwners::Artifact.from(params['artifact'])
155
- if @browser_context.browser.send(:remote?)
156
- artifact.update_as_remote
157
- end
158
152
  video.send(:set_artifact, artifact)
159
153
  end
160
154
 
@@ -261,10 +255,6 @@ module Playwright
261
255
  @main_frame.visible?(selector, strict: strict, timeout: timeout)
262
256
  end
263
257
 
264
- def locator(selector)
265
- @main_frame.locator(selector)
266
- end
267
-
268
258
  def dispatch_event(selector, type, eventInit: nil, strict: nil, timeout: nil)
269
259
  @main_frame.dispatch_event(selector, type, eventInit: eventInit, strict: strict, timeout: timeout)
270
260
  end
@@ -568,6 +558,14 @@ module Playwright
568
558
  timeout: timeout)
569
559
  end
570
560
 
561
+ def locator(selector, hasText: nil)
562
+ @main_frame.locator(selector, hasText: hasText)
563
+ end
564
+
565
+ def frame_locator(selector)
566
+ @main_frame.frame_locator(selector)
567
+ end
568
+
571
569
  def focus(selector, strict: nil, timeout: nil)
572
570
  @main_frame.focus(selector, strict: strict, timeout: timeout)
573
571
  end
@@ -925,6 +923,11 @@ module Playwright
925
923
  @workers.delete(worker)
926
924
  end
927
925
 
926
+ # called from Video
927
+ private def remote_connection?
928
+ @connection.remote?
929
+ end
930
+
928
931
  # Expose guid for library developers.
929
932
  # Not intended to be used by users.
930
933
  def guid
@@ -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,50 @@
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, hasText: nil)
10
+ LocatorImpl.new(
11
+ frame: @frame,
12
+ timeout_settings: @timeout_settings,
13
+ selector: "#{@frame_selector} >> control=enter-frame >> #{selector}",
14
+ hasText: hasText,
15
+ )
16
+ end
17
+
18
+ def frame_locator(selector)
19
+ FrameLocatorImpl.new(
20
+ frame: @frame,
21
+ timeout_settings: @timeout_settings,
22
+ frame_selector: "#{@frame_selector} >> control=enter-frame >> #{selector}",
23
+ )
24
+ end
25
+
26
+ def first
27
+ FrameLocatorImpl.new(
28
+ frame: @frame,
29
+ timeout_settings: @timeout_settings,
30
+ frame_selector: "#{@frame_selector} >> nth=0",
31
+ )
32
+ end
33
+
34
+ def last
35
+ FrameLocatorImpl.new(
36
+ frame: @frame,
37
+ timeout_settings: @timeout_settings,
38
+ frame_selector: "#{@frame_selector} >> nth=-1",
39
+ )
40
+ end
41
+
42
+ def nth(index)
43
+ FrameLocatorImpl.new(
44
+ frame: @frame,
45
+ timeout_settings: @timeout_settings,
46
+ frame_selector: "#{@frame_selector} >> nth=#{index}",
47
+ )
48
+ end
49
+ end
50
+ end
@@ -1,9 +1,46 @@
1
+ require 'json'
2
+
1
3
  module Playwright
4
+ class EscapeWithQuotes
5
+ def initialize(text, char = "'")
6
+ stringified = text.to_json
7
+ escaped_text = stringified[1...-1].gsub(/\\"/, '"')
8
+
9
+ case char
10
+ when '"'
11
+ text = escaped_text.gsub(/["]/, '\\"')
12
+ @text = "\"#{text}\""
13
+ when "'"
14
+ text = escaped_text.gsub(/[']/, '\\\'')
15
+ @text = "'#{text}'"
16
+ else
17
+ raise ArgumentError.new('Invalid escape char')
18
+ end
19
+ end
20
+
21
+ def to_s
22
+ @text
23
+ end
24
+ end
25
+
2
26
  define_api_implementation :LocatorImpl do
3
- def initialize(frame:, timeout_settings:, selector:)
27
+ def initialize(frame:, timeout_settings:, selector:, hasText: nil)
4
28
  @frame = frame
5
29
  @timeout_settings = timeout_settings
6
- @selector = selector
30
+ @selector =
31
+ case hasText
32
+ when Regexp
33
+ source = EscapeWithQuotes.new(hasText.source, '"')
34
+ flags = []
35
+ flags << 'ms' if (hasText.options & Regexp::MULTILINE) != 0
36
+ flags << 'i' if (hasText.options & Regexp::IGNORECASE) != 0
37
+ "#{selector} >> :scope:text-matches(#{source}, \"#{flags.join('')}\")"
38
+ when String
39
+ text = EscapeWithQuotes.new(hasText, '"')
40
+ "#{selector} >> :scope:has-text(#{text})"
41
+ else
42
+ selector
43
+ end
7
44
  end
8
45
 
9
46
  def to_s
@@ -11,17 +48,17 @@ module Playwright
11
48
  end
12
49
 
13
50
  private def with_element(timeout: nil, &block)
51
+ timeout_or_default = @timeout_settings.timeout(timeout)
14
52
  start_time = Time.now
15
53
 
16
- handle = @frame.wait_for_selector(@selector, strict: true, state: 'attached', timeout: timeout)
54
+ handle = @frame.wait_for_selector(@selector, strict: true, state: 'attached', timeout: timeout_or_default)
17
55
  unless handle
18
56
  raise "Could not resolve #{@selector} to DOM Element"
19
57
  end
20
58
 
21
- call_options = {}
22
- if timeout
23
- call_options[:timeout] = (timeout - (Time.now - start_time) * 1000).to_i
24
- end
59
+ call_options = {
60
+ timeout: (timeout_or_default - (Time.now - start_time) * 1000).to_i,
61
+ }
25
62
 
26
63
  begin
27
64
  block.call(handle, call_options)
@@ -102,6 +139,27 @@ module Playwright
102
139
  @frame.dispatch_event(@selector, type, strict: true, eventInit: eventInit, timeout: timeout)
103
140
  end
104
141
 
142
+ def drag_to(target,
143
+ force: nil,
144
+ noWaitAfter: nil,
145
+ sourcePosition: nil,
146
+ targetPosition: nil,
147
+ timeout: nil,
148
+ trial: nil)
149
+
150
+ @frame.drag_and_drop(
151
+ @selector,
152
+ target.instance_variable_get(:@selector),
153
+ force: force,
154
+ noWaitAfter: noWaitAfter,
155
+ sourcePosition: sourcePosition,
156
+ targetPosition: targetPosition,
157
+ timeout: timeout,
158
+ trial: trial,
159
+ strict: true,
160
+ )
161
+ end
162
+
105
163
  def evaluate(expression, arg: nil, timeout: nil)
106
164
  with_element(timeout: timeout) do |handle|
107
165
  handle.evaluate(expression, arg: arg)
@@ -122,11 +180,20 @@ module Playwright
122
180
  @frame.fill(@selector, value, strict: true, force: force, noWaitAfter: noWaitAfter, timeout: timeout)
123
181
  end
124
182
 
125
- def locator(selector)
183
+ def locator(selector, hasText: nil)
126
184
  LocatorImpl.new(
127
185
  frame: @frame,
128
186
  timeout_settings: @timeout_settings,
129
187
  selector: "#{@selector} >> #{selector}",
188
+ hasText: hasText,
189
+ )
190
+ end
191
+
192
+ def frame_locator(selector)
193
+ FrameLocatorImpl.new(
194
+ frame: @frame,
195
+ timeout_settings: @timeout_settings,
196
+ frame_selector: "#{@selector} >> #{selector}",
130
197
  )
131
198
  end
132
199
 
@@ -5,39 +5,50 @@ 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)
23
- do_stop_chunk(path: path)
23
+ do_stop_chunk(file_path: path)
24
24
  end
25
25
 
26
26
  def stop(path: nil)
27
- do_stop_chunk(path: path)
27
+ do_stop_chunk(file_path: path)
28
28
  @channel.send_message_to_server('tracingStop')
29
29
  end
30
30
 
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)
34
- return unless artifact
35
-
36
- if @context.browser.send(:remote?)
37
- artifact.update_as_remote
31
+ private def do_stop_chunk(file_path:)
32
+ mode = 'doNotSave'
33
+ if file_path
34
+ if @context.send(:remote_connection?)
35
+ mode = 'compressTrace'
36
+ else
37
+ mode = 'compressTraceAndSources'
38
+ end
38
39
  end
39
- artifact.save_as(path)
40
+
41
+ result = @channel.send_message_to_server_result('tracingStopChunk', mode: mode)
42
+ return unless file_path # Not interested in artifacts.
43
+ return unless result['artifact'] # The artifact may be missing if the browser closed while stopping tracing.
44
+
45
+ artifact = ChannelOwners::Artifact.from(result['artifact'])
46
+ artifact.save_as(file_path)
40
47
  artifact.delete
48
+
49
+ # // Add local sources to the remote trace if necessary.
50
+ # if (result.sourceEntries?.length)
51
+ # await this._context._localUtils.zip(filePath, result.sourceEntries);
41
52
  end
42
53
  end
43
54
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '1.16.0'
5
- COMPATIBLE_PLAYWRIGHT_VERSION = '1.16.0'
4
+ VERSION = '1.18.beta1'
5
+ COMPATIBLE_PLAYWRIGHT_VERSION = '1.18.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
@@ -165,13 +165,13 @@ module Playwright
165
165
  end
166
166
 
167
167
  # @nodoc
168
- def tree
169
- wrap_impl(@impl.tree)
168
+ def tap_on(selector, duration: nil, timeout: nil)
169
+ wrap_impl(@impl.tap_on(unwrap_impl(selector), duration: unwrap_impl(duration), timeout: unwrap_impl(timeout)))
170
170
  end
171
171
 
172
172
  # @nodoc
173
- def tap_on(selector, duration: nil, timeout: nil)
174
- wrap_impl(@impl.tap_on(unwrap_impl(selector), duration: unwrap_impl(duration), timeout: unwrap_impl(timeout)))
173
+ def tree
174
+ wrap_impl(@impl.tree)
175
175
  end
176
176
 
177
177
  # -- inherited from EventEmitter --
@@ -3,6 +3,53 @@ module Playwright
3
3
  # environment or the service to your e2e test. When used on `Page` or a `BrowserContext`, this API will automatically use
4
4
  # the cookies from the corresponding `BrowserContext`. This means that if you log in using this API, your e2e test will be
5
5
  # logged in and vice versa.
6
+ #
7
+ # ```python sync
8
+ # import os
9
+ # from playwright.sync_api import sync_playwright
10
+ #
11
+ # REPO = "test-repo-1"
12
+ # USER = "github-username"
13
+ # API_TOKEN = os.getenv("GITHUB_API_TOKEN")
14
+ #
15
+ # with sync_playwright() as p:
16
+ # # This will launch a new browser, create a context and page. When making HTTP
17
+ # # requests with the internal APIRequestContext (e.g. `context.request` or `page.request`)
18
+ # # it will automatically set the cookies to the browser page and vise versa.
19
+ # browser = playwright.chromium.launch()
20
+ # context = browser.new_context(base_url="https://api.github.com")
21
+ # api_request_context = context.request
22
+ # page = context.new_page()
23
+ #
24
+ # # Alternatively you can create a APIRequestContext manually without having a browser context attached:
25
+ # # api_request_context = playwright.request.new_context(base_url="https://api.github.com")
26
+ #
27
+ #
28
+ # # Create a repository.
29
+ # response = api_request_context.post(
30
+ # "/user/repos",
31
+ # headers={
32
+ # "Accept": "application/vnd.github.v3+json",
33
+ # # Add GitHub personal access token.
34
+ # "Authorization": f"token {API_TOKEN}",
35
+ # },
36
+ # data={"name": REPO},
37
+ # )
38
+ # assert response.ok
39
+ # assert response.json()["name"] == REPO
40
+ #
41
+ # # Delete a repository.
42
+ # response = api_request_context.delete(
43
+ # f"/repos/{USER}/{REPO}",
44
+ # headers={
45
+ # "Accept": "application/vnd.github.v3+json",
46
+ # # Add GitHub personal access token.
47
+ # "Authorization": f"token {API_TOKEN}",
48
+ # },
49
+ # )
50
+ # assert response.ok
51
+ # assert await response.body() == '{"status": "ok"}'
52
+ # ```
6
53
  class APIRequestContext < PlaywrightApi
7
54
 
8
55
  # Sends HTTP(S) [DELETE](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) request and returns its
@@ -10,9 +57,12 @@ module Playwright
10
57
  # method will automatically follow redirects.
11
58
  def delete(
12
59
  url,
60
+ data: nil,
13
61
  failOnStatusCode: nil,
62
+ form: nil,
14
63
  headers: nil,
15
64
  ignoreHTTPSErrors: nil,
65
+ multipart: nil,
16
66
  params: nil,
17
67
  timeout: nil)
18
68
  raise NotImplementedError.new('delete is not implemented yet.')
@@ -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
@@ -367,8 +372,8 @@ module Playwright
367
372
  end
368
373
 
369
374
  # @nodoc
370
- def enable_debug_console!
371
- wrap_impl(@impl.enable_debug_console!)
375
+ def pause
376
+ wrap_impl(@impl.pause)
372
377
  end
373
378
 
374
379
  # @nodoc
@@ -382,13 +387,13 @@ module Playwright
382
387
  end
383
388
 
384
389
  # @nodoc
385
- def pause
386
- wrap_impl(@impl.pause)
390
+ def options=(req)
391
+ wrap_impl(@impl.options=(unwrap_impl(req)))
387
392
  end
388
393
 
389
394
  # @nodoc
390
- def options=(req)
391
- wrap_impl(@impl.options=(unwrap_impl(req)))
395
+ def enable_debug_console!
396
+ wrap_impl(@impl.enable_debug_console!)
392
397
  end
393
398
 
394
399
  # -- inherited from EventEmitter --
@@ -12,10 +12,6 @@ module Playwright
12
12
  # # wait for download to complete
13
13
  # path = download.path()
14
14
  # ```
15
- #
16
- # > NOTE: Browser context **must** be created with the `acceptDownloads` set to `true` when user needs access to the
17
- # downloaded content. If `acceptDownloads` is not set, download events are emitted, but the actual download is not
18
- # performed and user has no access to the downloaded files.
19
15
  class Download < PlaywrightApi
20
16
 
21
17
  # Cancels a download. Will not fail if the download is already finished or canceled. Upon successful cancellations,
@@ -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)))
@@ -427,8 +445,8 @@ module Playwright
427
445
  # The method returns an element locator that can be used to perform actions in the frame. Locator is resolved to the
428
446
  # element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
429
447
  # different DOM elements. That would happen if the DOM structure between those actions has changed.
430
- def locator(selector)
431
- wrap_impl(@impl.locator(unwrap_impl(selector)))
448
+ def locator(selector, hasText: nil)
449
+ wrap_impl(@impl.locator(unwrap_impl(selector), hasText: unwrap_impl(hasText)))
432
450
  end
433
451
 
434
452
  # Returns frame's name attribute as specified in the tag.
@@ -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.