capybara-playwright-driver 0.5.7 → 0.5.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36e3eb177b25d9c711d190e31beeeebc7fcba4a40b5c1ffa6d2e995ac2b34219
4
- data.tar.gz: 961a358b0aeb9cf2ededd3491b0850e8df4ce9ccdf3c3a895cd69fea589923ad
3
+ metadata.gz: a319465a40c53ac5b47d86ccd4d15136c20dcf7f6dde6fe17b3906d44cb2fd94
4
+ data.tar.gz: 1e38fb6020a87a07e6eaa8e997084b3abd98a0a8d5cbec4e92819a960c525f62
5
5
  SHA512:
6
- metadata.gz: d62a3e62ceee42b31676fa7e2728e1d49e24592f32b889a7cf8732ad076bfae839b4009ee56f4031657c6906f2906e412f1df442482ac79b0974a35722fa07a4
7
- data.tar.gz: 12d105953dd329eb5eb118d1ab81370f395810004b2e940dc356f5457f36db96ede3fc75938727374124cc9cccb0fb83fe4d71e65b167071bdea7d47a7b8e634
6
+ metadata.gz: 5d1d8aeccb05f0c667c3b1a617cb1d2f19eb7fa096788a46b81985730f1a75b6ee7196ad952d2152c173b7401980090f738db399503b3ddceac097ced637678c
7
+ data.tar.gz: 141fd5024a9d322faf9031951e0f74a61426af350ed06d73017b2db90ec5b866e1af9b17edd5017e5c35f79f198cfa5916b220afcf621db9a2538c5fb50f443b
data/AGENTS.md ADDED
@@ -0,0 +1,107 @@
1
+ # AGENTS.md
2
+
3
+ ## Ruby Style
4
+ - This repository is Ruby-first. Prefer Ruby naming and control flow over Python- or Java-style ceremony.
5
+ - Use names that describe the role in the domain. Avoid vague plumbing names like `context`, `handler`, `data`, or `control` unless they are the clearest possible name.
6
+ - Boolean-returning methods must end with `?`.
7
+ - Prefer `find`, `any?`, early return, and guard clauses over `value = nil` plus later reassignment.
8
+ - Do not add `attr_reader` for every instance variable in a small internal object. If the state is only used internally, access the instance variable directly.
9
+ - If an object is already scoped to one responsibility, avoid redundant prefixes in every private method name.
10
+ - Do not prefix Ruby private methods with `_`. Use `private` for visibility and a normal method name for intent.
11
+
12
+ ## Naming Examples
13
+
14
+ Bad:
15
+
16
+ ```ruby
17
+ def _playwright_try_get_by_label(locator)
18
+ # ...
19
+ end
20
+ ```
21
+
22
+ Good:
23
+
24
+ ```ruby
25
+ def find_by_label(locator)
26
+ # ...
27
+ end
28
+ ```
29
+
30
+ Bad:
31
+
32
+ ```ruby
33
+ def click_associated_label(control)
34
+ return true if control.evaluate("el => !!el.checked")
35
+ end
36
+ ```
37
+
38
+ Good:
39
+
40
+ ```ruby
41
+ def click_associated_label?(playwright_locator)
42
+ return true if playwright_locator.evaluate("el => !!el.checked")
43
+ end
44
+ ```
45
+
46
+ Bad:
47
+
48
+ ```ruby
49
+ def initialize(node_context:, selector:, locator:, checked:)
50
+ @node_context = node_context
51
+ @selector = selector
52
+ @locator = locator
53
+ @checked = checked
54
+ end
55
+ ```
56
+
57
+ Good:
58
+
59
+ ```ruby
60
+ def initialize(node:, node_type:, locator:, checked:)
61
+ @node = node
62
+ @node_type = node_type
63
+ @locator = locator
64
+ @checked = checked
65
+ end
66
+ ```
67
+
68
+ Bad:
69
+
70
+ ```ruby
71
+ control = nil
72
+
73
+ candidates.each do |candidate|
74
+ next unless matches?(candidate)
75
+
76
+ control = candidate
77
+ break
78
+ end
79
+
80
+ control
81
+ ```
82
+
83
+ Good:
84
+
85
+ ```ruby
86
+ candidates.find do |element_handle|
87
+ matches?(element_handle)
88
+ end
89
+ ```
90
+
91
+ Bad:
92
+
93
+ ```ruby
94
+ attr_reader :node, :node_type, :locator, :checked
95
+ ```
96
+
97
+ Good:
98
+
99
+ ```ruby
100
+ def click_associated_label?(playwright_locator)
101
+ return true if playwright_locator.evaluate("el => !!el.checked") == @checked
102
+ end
103
+ ```
104
+
105
+ ## General Guidance
106
+ - Prefer concise Ruby that reads top-to-bottom without carrying unnecessary temporary state.
107
+ - Prefer small private helper objects only when they reduce complexity. Once extracted, give them Ruby-like method names and keep their public surface minimal.
data/README.md CHANGED
@@ -43,10 +43,27 @@ end
43
43
 
44
44
  Refer the [documentation](https://playwright-ruby-client.vercel.app/docs/article/guides/rails_integration) for more detailed configuration.
45
45
 
46
+ ## Development
47
+
48
+ Prepare to run tests:
49
+
50
+ ```bash
51
+ bundle install
52
+ export PLAYWRIGHT_CLI_VERSION=$(bundle exec ruby -e 'require "playwright"; puts Playwright::COMPATIBLE_PLAYWRIGHT_VERSION.strip')
53
+ npm install playwright@${PLAYWRIGHT_CLI_VERSION}
54
+ ./node_modules/.bin/playwright install --with-deps
55
+ ```
56
+
57
+ Now, run tests: note that they are run in a virtual framebuffer (Xvfb).
58
+
59
+ ```bash
60
+ PLAYWRIGHT_CLI_EXECUTABLE_PATH=./node_modules/.bin/playwright xvfb-run bundle exec rspec
61
+ ```
62
+
46
63
  ## License
47
64
 
48
65
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
49
66
 
50
67
  ## Code of Conduct
51
68
 
52
- Everyone interacting in the Capybara::Playwright project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/capybara-playwright/blob/master/CODE_OF_CONDUCT.md).
69
+ Everyone interacting in the Capybara::Playwright project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/YusukeIwaki/capybara-playwright-driver/blob/main/CODE_OF_CONDUCT.md).
@@ -20,8 +20,6 @@ Gem::Specification.new do |spec|
20
20
  f.match(%r{^(test|spec|features)/}) || f.include?('.git')
21
21
  end
22
22
  end
23
- spec.bindir = 'exe'
24
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
23
  spec.require_paths = ['lib']
26
24
 
27
25
  spec.required_ruby_version = '>= 2.4'
@@ -48,7 +48,7 @@ module Capybara
48
48
  private def create_page(browser_context)
49
49
  browser_context.new_page.tap do |page|
50
50
  page.on('close', -> {
51
- if @playwright_page
51
+ if @playwright_page&.guid == page.guid
52
52
  @playwright_page = nil
53
53
  end
54
54
  })
@@ -84,7 +84,8 @@ module Capybara
84
84
  path
85
85
  end
86
86
 
87
- @playwright_page.capybara_current_frame.goto(url)
87
+ response = @playwright_page.capybara_current_frame.goto(url)
88
+ @playwright_page.capybara_set_last_response(response)
88
89
  }
89
90
  end
90
91
 
@@ -144,13 +145,15 @@ module Capybara
144
145
 
145
146
  def go_back
146
147
  assert_page_alive {
147
- @playwright_page.go_back
148
+ response = @playwright_page.go_back
149
+ @playwright_page.capybara_set_last_response(response)
148
150
  }
149
151
  end
150
152
 
151
153
  def go_forward
152
154
  assert_page_alive {
153
- @playwright_page.go_forward
155
+ response = @playwright_page.go_forward
156
+ @playwright_page.capybara_set_last_response(response)
154
157
  }
155
158
  end
156
159
 
@@ -90,8 +90,8 @@ module Capybara
90
90
  end
91
91
  end
92
92
 
93
- # video path can be aquired only before closing context.
94
- # video is completedly saved only after closing context.
93
+ # video path can be acquired only before closing context.
94
+ # video is completely saved only after closing context.
95
95
  video_path = @browser&.video_path
96
96
 
97
97
  # [NOTE] @playwright_browser should keep alive for better performance.
@@ -30,6 +30,145 @@ module Capybara
30
30
  end
31
31
  Node::Element.prepend(WithElementHandlePatch)
32
32
 
33
+ module NodeActionsAllowLabelClickPatch
34
+ class SelectableElementHandler
35
+ def initialize(node:, node_type:, locator:, checked:)
36
+ @node = node
37
+ @node_type = node_type
38
+ @locator = locator
39
+ @checked = checked
40
+ end
41
+
42
+ def set_checked_state_via_label?
43
+ playwright_checkable = find_playwright_element_handle_by_non_label_locator
44
+ playwright_checkable ||= playwright_locator_by_label unless @locator.nil?
45
+ return false unless playwright_checkable
46
+
47
+ click_associated_label?(playwright_checkable)
48
+ rescue Capybara::ElementNotFound, Capybara::ExpectationNotMet, ::Playwright::Error
49
+ false
50
+ end
51
+
52
+ private
53
+
54
+ def playwright_locator_by_label
55
+ driver.with_playwright_page do |playwright_page|
56
+ return playwright_page.get_by_label(@locator.to_s)
57
+ end
58
+ end
59
+
60
+ def click_associated_label?(playwright_element_handle_or_locator)
61
+ return true if playwright_element_handle_or_locator.evaluate('el => !!el.checked') == @checked
62
+
63
+ label_element_handle = playwright_element_handle_or_locator.evaluate_handle('(el) => (el.labels && el.labels[0]) || el.closest("label") || null')
64
+ return false unless label_element_handle.is_a?(::Playwright::ElementHandle)
65
+
66
+ label_element_handle.click
67
+
68
+ playwright_element_handle_or_locator.evaluate('el => !!el.checked') == @checked
69
+ end
70
+
71
+ def find_playwright_element_handle_by_non_label_locator
72
+ return nil if @locator.nil?
73
+
74
+ locator_string = @locator.to_s
75
+ test_id_attr = session_options.test_id&.to_s
76
+
77
+ driver.with_playwright_page do |playwright_page|
78
+ return non_label_playwright_element_handle_candidates(playwright_page).find do |element_handle|
79
+ attribute_values = element_handle.evaluate(<<~JAVASCRIPT, arg: test_id_attr)
80
+ (el, testIdAttr) => ({
81
+ id: el.id || '',
82
+ name: el.getAttribute('name') || '',
83
+ testId: testIdAttr ? (el.getAttribute(testIdAttr) || '') : '',
84
+ })
85
+ JAVASCRIPT
86
+ [attribute_values['id'], attribute_values['name'], attribute_values['testId']].include?(locator_string)
87
+ end
88
+ end
89
+ rescue ::Playwright::Error
90
+ nil
91
+ end
92
+
93
+ def non_label_playwright_element_handle_candidates(playwright_page)
94
+ input_type =
95
+ case @node_type
96
+ when :checkbox
97
+ 'checkbox'
98
+ when :radio_button
99
+ 'radio'
100
+ else
101
+ return []
102
+ end
103
+
104
+ current_scope = scope_element
105
+ return current_scope.query_selector_all(%(input[type="#{input_type}"])) if current_scope
106
+
107
+ playwright_page.capybara_current_frame.query_selector_all(%(input[type="#{input_type}"]))
108
+ end
109
+
110
+ def scope_element
111
+ return nil unless @node.is_a?(Capybara::Node::Element)
112
+ return nil unless @node.send(:base).is_a?(Capybara::Playwright::Node)
113
+
114
+ @node.send(:base).send(:element)
115
+ end
116
+
117
+ def driver
118
+ @node.send(:driver)
119
+ end
120
+
121
+ def session_options
122
+ @node.send(:session_options)
123
+ end
124
+ end
125
+
126
+ def choose(locator = nil, **options)
127
+ check_via_label_click(:radio_button, locator, checked: true, **options) { super }
128
+ end
129
+
130
+ def check(locator = nil, **options)
131
+ check_via_label_click(:checkbox, locator, checked: true, **options) { super }
132
+ end
133
+
134
+ def uncheck(locator = nil, **options)
135
+ check_via_label_click(:checkbox, locator, checked: false, **options) { super }
136
+ end
137
+
138
+ private def check_via_label_click(node_type, locator, checked:, allow_label_click: session_options.automatic_label_click, **options)
139
+ unless should_use_label_click?(allow_label_click, options)
140
+ return yield
141
+ end
142
+
143
+ handler = SelectableElementHandler.new(
144
+ node: self,
145
+ node_type: node_type,
146
+ locator: locator,
147
+ checked: checked,
148
+ )
149
+ return self if handler.set_checked_state_via_label?
150
+
151
+ yield
152
+ end
153
+
154
+ private def should_use_label_click?(allow_label_click, options)
155
+ return false unless allow_label_click
156
+ return false unless driver.is_a?(Capybara::Playwright::Driver)
157
+ return false if Hash.try_convert(allow_label_click)
158
+ return false unless options.empty?
159
+
160
+ true
161
+ end
162
+ end
163
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7')
164
+ # Prepend to Node::Base instead of Node::Actions because Ruby < 3.1
165
+ # does not propagate Module#prepend to classes that have already
166
+ # included the target module. Node::Base includes Node::Actions at
167
+ # load time, so prepending to Node::Actions afterwards has no effect
168
+ # on Node::Document / Node::Element in older Rubies.
169
+ Node::Base.prepend(NodeActionsAllowLabelClickPatch)
170
+ end
171
+
33
172
  module CapybaraObscuredPatch
34
173
  # ref: https://github.com/teamcapybara/capybara/blob/f7ab0b5cd5da86185816c2d5c30d58145fe654ed/lib/capybara/selenium/node.rb#L523
35
174
  OBSCURED_OR_OFFSET_SCRIPT = <<~JAVASCRIPT
@@ -59,6 +198,20 @@ module Capybara
59
198
  end
60
199
  ::Playwright::ElementHandle.prepend(CapybaraObscuredPatch)
61
200
 
201
+ # ref: https://github.com/teamcapybara/capybara/pull/2424
202
+ module ElementDropPathCompatPatch
203
+ def drop(*args)
204
+ options = args.map { |arg| arg.respond_to?(:to_path) ? arg.to_path : arg }
205
+ synchronize { base.drop(*options) }
206
+ self
207
+ end
208
+ end
209
+ if Gem::Version.new(Capybara::VERSION) < Gem::Version.new('3.34.0')
210
+ # Older Capybara returns early for Pathname arguments in Element#drop,
211
+ # so the driver implementation never runs.
212
+ Node::Element.prepend(ElementDropPathCompatPatch)
213
+ end
214
+
62
215
  module Playwright
63
216
  # Selector and checking methods are derived from twapole/apparition
64
217
  # Action methods (click, select_option, ...) uses playwright.
@@ -97,6 +250,8 @@ module Capybara
97
250
  raise StaleReferenceError.new(err)
98
251
  when /error in channel "content::page": exception while running method "adoptNode"/ # for Firefox
99
252
  raise StaleReferenceError.new(err)
253
+ when /(: Shadow DOM element - no XPath :)/
254
+ raise StaleReferenceError.new(err)
100
255
  else
101
256
  raise
102
257
  end
@@ -244,13 +399,31 @@ module Capybara
244
399
 
245
400
  class TextInput < Settable
246
401
  def set(value, **options)
402
+ case options[:clear]
403
+ when :backspace
404
+ @element.press('End', timeout: @timeout)
405
+ existing_text = @element.evaluate('el => el.value')
406
+ existing_text.length.times { @element.press('Backspace', timeout: @timeout) }
407
+ when :none
408
+ @element.press('End', timeout: @timeout)
409
+ when Array
410
+ @internal_logger.warn "options { clear: #{options[:clear]} } is ignored"
411
+ end
412
+
247
413
  text = value.to_s
248
- if text.end_with?("\n")
249
- @element.fill(text[0...-1], timeout: @timeout)
250
- @element.press('Enter', timeout: @timeout)
414
+ if press_enter = text.end_with?("\n")
415
+ text = text[0...-1]
416
+ end
417
+
418
+ if options[:clear] == :none
419
+ @element.type(text, timeout: @timeout)
251
420
  else
252
421
  @element.fill(text, timeout: @timeout)
253
422
  end
423
+
424
+ if press_enter
425
+ @element.press('Enter', timeout: @timeout)
426
+ end
254
427
  rescue ::Playwright::TimeoutError
255
428
  raise if @element.editable?
256
429
 
@@ -711,8 +884,47 @@ module Capybara
711
884
  end
712
885
  end
713
886
 
887
+ ATTACH_FILE = <<~JAVASCRIPT
888
+ () => {
889
+ const input = document.createElement('INPUT');
890
+ input.type = 'file';
891
+ input.multiple = true;
892
+ input.style.display = 'none';
893
+ document.body.appendChild(input);
894
+ return input;
895
+ }
896
+ JAVASCRIPT
897
+
898
+ DROP_FILE = <<~JAVASCRIPT
899
+ (el, input) => {
900
+ const dt = new DataTransfer();
901
+ for (const file of input.files) { dt.items.add(file); }
902
+ input.remove();
903
+ el.dispatchEvent(new DragEvent('drop', {
904
+ cancelable: true, bubbles: true, dataTransfer: dt
905
+ }));
906
+ }
907
+ JAVASCRIPT
908
+
909
+ DROP_STRING = <<~JAVASCRIPT
910
+ (el, items) => {
911
+ const dt = new DataTransfer();
912
+ for (const item of items) { dt.items.add(item.data, item.type); }
913
+ el.dispatchEvent(new DragEvent('drop', {
914
+ cancelable: true, bubbles: true, dataTransfer: dt
915
+ }));
916
+ }
917
+ JAVASCRIPT
918
+
714
919
  def drop(*args)
715
- raise NotImplementedError
920
+ if args.first.is_a?(String) || args.first.is_a?(Pathname)
921
+ input = @page.evaluate_handle(ATTACH_FILE)
922
+ input.as_element.set_input_files(args.map(&:to_s))
923
+ @element.evaluate(DROP_FILE, arg: input)
924
+ else
925
+ items = args.flat_map { |arg| arg.map { |(type, data)| { type: type, data: data } } }
926
+ @element.evaluate(DROP_STRING, arg: items)
927
+ end
716
928
  end
717
929
 
718
930
  def scroll_by(x, y)
@@ -905,6 +1117,9 @@ module Capybara
905
1117
  xpath = el.nodeName.toUpperCase()+"["+pos+"]/"+xpath;
906
1118
  }
907
1119
  el = el.parentNode;
1120
+ if (!el) {
1121
+ throw "(: Shadow DOM element - no XPath :)";
1122
+ }
908
1123
  }
909
1124
  xpath = '/'+xml.documentElement.nodeName.toUpperCase()+'/'+xpath;
910
1125
  xpath = xpath.replace(/\\/$/, '');
@@ -17,7 +17,7 @@ module Capybara
17
17
  end
18
18
 
19
19
  private def capybara_initialize
20
- @capybara_all_responses = {}
20
+ @capybara_response_mutex = Mutex.new
21
21
  @capybara_last_response = nil
22
22
  @capybara_frames = []
23
23
 
@@ -31,13 +31,9 @@ module Capybara
31
31
  Thread.new(dest) { |_dest| download.save_as(_dest) }
32
32
  })
33
33
  on('response', -> (response) {
34
- @capybara_all_responses[response.url] = response
35
- })
36
- on('framenavigated', -> (frame) {
37
- @capybara_last_response = @capybara_all_responses[frame.url]
38
- })
39
- on('load', -> (page) {
40
- @capybara_all_responses.clear
34
+ if response.request.navigation_request?
35
+ capybara_set_last_response(response)
36
+ end
41
37
  })
42
38
  end
43
39
 
@@ -146,6 +142,12 @@ module Capybara
146
142
  end
147
143
  end
148
144
 
145
+ def capybara_set_last_response(response)
146
+ @capybara_response_mutex.synchronize do
147
+ @capybara_last_response = response
148
+ end
149
+ end
150
+
149
151
  class Headers < Hash
150
152
  def [](key)
151
153
  # Playwright accepts lower-cased keys.
@@ -155,7 +157,7 @@ module Capybara
155
157
  end
156
158
 
157
159
  def capybara_response_headers
158
- headers = @capybara_last_response&.headers || {}
160
+ headers = @capybara_response_mutex.synchronize { @capybara_last_response&.headers || {} }
159
161
 
160
162
  Headers.new.tap do |h|
161
163
  headers.each do |key, value|
@@ -165,7 +167,7 @@ module Capybara
165
167
  end
166
168
 
167
169
  def capybara_status_code
168
- @capybara_last_response&.status.to_i
170
+ @capybara_response_mutex.synchronize { @capybara_last_response&.status.to_i }
169
171
  end
170
172
 
171
173
  def capybara_reset_frames
@@ -5,12 +5,12 @@ module Capybara
5
5
  class ShadowRootNode < Node
6
6
  def initialize(driver, internal_logger, page, element)
7
7
  super
8
- @shadow_roow_element = element.evaluate_handle('el => el.shadowRoot')
8
+ @shadow_root_element = element.evaluate_handle('el => el.shadowRoot')
9
9
  end
10
10
 
11
11
  def all_text
12
12
  assert_element_not_stale {
13
- text = @shadow_roow_element.text_content
13
+ text = @shadow_root_element.text_content
14
14
  text.to_s.gsub(/[\u200b\u200e\u200f]/, '')
15
15
  .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
16
16
  .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
@@ -25,7 +25,7 @@ module Capybara
25
25
  return '' unless visible?
26
26
 
27
27
  # https://github.com/teamcapybara/capybara/blob/1c164b608fa6452418ec13795b293655f8a0102a/lib/capybara/rack_test/node.rb#L18
28
- displayed_text = @shadow_roow_element.text_content.to_s.
28
+ displayed_text = @shadow_root_element.text_content.to_s.
29
29
  gsub(/[\u200b\u200e\u200f]/, '').
30
30
  gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
31
31
  displayed_text.squeeze(' ')
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Playwright
5
- VERSION = '0.5.7'
5
+ VERSION = '0.5.9'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-playwright-driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.7
4
+ version: 0.5.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
8
- autorequire:
9
- bindir: exe
8
+ bindir: bin
10
9
  cert_chain: []
11
- date: 2025-08-05 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: addressable
@@ -52,7 +51,6 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: 1.16.0
55
- description:
56
54
  email:
57
55
  - q7w8e9w8q7w8e9@yahoo.co.jp
58
56
  executables: []
@@ -60,6 +58,7 @@ extensions: []
60
58
  extra_rdoc_files: []
61
59
  files:
62
60
  - ".rspec"
61
+ - AGENTS.md
63
62
  - CODE_OF_CONDUCT.md
64
63
  - Gemfile
65
64
  - LICENSE.txt
@@ -86,7 +85,6 @@ homepage: https://github.com/YusukeIwaki/capybara-playwright-driver
86
85
  licenses:
87
86
  - MIT
88
87
  metadata: {}
89
- post_install_message:
90
88
  rdoc_options: []
91
89
  require_paths:
92
90
  - lib
@@ -101,8 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
99
  - !ruby/object:Gem::Version
102
100
  version: '0'
103
101
  requirements: []
104
- rubygems_version: 3.5.22
105
- signing_key:
102
+ rubygems_version: 3.6.9
106
103
  specification_version: 4
107
104
  summary: Playwright driver for Capybara
108
105
  test_files: []