capybara-playwright-driver 0.2.1 → 0.3.1

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: 9d6815293bb6fcd9d4c733b7b3f2da1ef8e29708eec7a44975a83a1544cca82e
4
- data.tar.gz: 1eaf8b0d1c4a53239727486e3e3fd977d6d51af08f11e2b74e38684eba4fd175
3
+ metadata.gz: 931fea1c143e8de209fb6a360e072b0b5dfd724e7f9d240d8a4b0a36b39b443e
4
+ data.tar.gz: 53fa84147fadb311502981d7a51311d147a140f6983cd8d66567cea79918d332
5
5
  SHA512:
6
- metadata.gz: 7dc61c122ce58f779c0cb7dd5a314e10fa2c1d84fd39c0deb54f2cc12a11eb03f94fa6ca33926d615aa9c15171f22318be4a77ba3037daab9718d4392ce41ba0
7
- data.tar.gz: 8846baa2461b388340d92ac4ba2f3a8102552ea53b65f189413220cf2ea84190ab113972ff250fc15fa16c3c6c16dd5e6a3d719f80153a4dbf59eeb072121d49
6
+ metadata.gz: 1e3e16c8377aa75d7fb5716e875b0291d50332ef9e447c52205b0dab27cfd8de95d32388112a3f1a280137569eba270f77b236f826dcc7117d374ff43b18943c
7
+ data.tar.gz: cc15d0f5a5ddfa9d024d87e800f9e073df34220318b68465e9e77ff4bfc8ac3ce3a3cd7ed7e393b9b884de85fbf0ac4eae65b4e7ee68740cb4c68eafa17524c9
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency 'bundler'
32
32
  spec.add_development_dependency 'launchy', '>= 2.0.4'
33
33
  spec.add_development_dependency 'pry-byebug'
34
+ spec.add_development_dependency 'rack-test_server'
34
35
  spec.add_development_dependency 'rake', '~> 13.0.3'
35
36
  spec.add_development_dependency 'rspec', '~> 3.11.0'
36
37
  spec.add_development_dependency 'rubocop-rspec'
@@ -49,15 +49,14 @@ module Capybara
49
49
  end
50
50
 
51
51
  def current_url
52
- assert_page_alive
53
-
54
- @playwright_page.url
52
+ assert_page_alive {
53
+ @playwright_page.url
54
+ }
55
55
  end
56
56
 
57
57
  def visit(path)
58
- assert_page_alive
59
-
60
- url =
58
+ assert_page_alive {
59
+ url =
61
60
  if Capybara.app_host
62
61
  URI(Capybara.app_host).merge(path)
63
62
  elsif Capybara.default_host
@@ -66,103 +65,104 @@ module Capybara
66
65
  path
67
66
  end
68
67
 
69
- @playwright_page.capybara_current_frame.goto(url)
68
+ @playwright_page.capybara_current_frame.goto(url)
69
+ }
70
70
  end
71
71
 
72
72
  def refresh
73
- assert_page_alive
74
-
75
- @playwright_page.capybara_current_frame.evaluate('() => { location.reload(true) }')
73
+ assert_page_alive {
74
+ @playwright_page.capybara_current_frame.evaluate('() => { location.reload(true) }')
75
+ }
76
76
  end
77
77
 
78
78
  def find_xpath(query, **options)
79
- assert_page_alive
80
-
81
- @playwright_page.capybara_current_frame.query_selector_all("xpath=#{query}").map do |el|
82
- Node.new(@driver, @playwright_page, el)
83
- end
79
+ assert_page_alive {
80
+ @playwright_page.capybara_current_frame.query_selector_all("xpath=#{query}").map do |el|
81
+ Node.new(@driver, @playwright_page, el)
82
+ end
83
+ }
84
84
  end
85
85
 
86
86
  def find_css(query, **options)
87
- assert_page_alive
88
-
89
- @playwright_page.capybara_current_frame.query_selector_all(query).map do |el|
90
- Node.new(@driver, @playwright_page, el)
91
- end
87
+ assert_page_alive {
88
+ @playwright_page.capybara_current_frame.query_selector_all(query).map do |el|
89
+ Node.new(@driver, @playwright_page, el)
90
+ end
91
+ }
92
92
  end
93
93
 
94
94
  def response_headers
95
- assert_page_alive
96
-
97
- @playwright_page.capybara_response_headers
95
+ assert_page_alive {
96
+ @playwright_page.capybara_response_headers
97
+ }
98
98
  end
99
99
 
100
100
  def status_code
101
- assert_page_alive
102
-
103
- @playwright_page.capybara_status_code
101
+ assert_page_alive {
102
+ @playwright_page.capybara_status_code
103
+ }
104
104
  end
105
105
 
106
106
  def html
107
- assert_page_alive
108
-
109
- js = <<~JAVASCRIPT
110
- () => {
111
- let html = '';
112
- if (document.doctype) html += new XMLSerializer().serializeToString(document.doctype);
113
- if (document.documentElement) html += document.documentElement.outerHTML;
114
- return html;
107
+ assert_page_alive {
108
+ js = <<~JAVASCRIPT
109
+ () => {
110
+ let html = '';
111
+ if (document.doctype) html += new XMLSerializer().serializeToString(document.doctype);
112
+ if (document.documentElement) html += document.documentElement.outerHTML;
113
+ return html;
114
+ }
115
+ JAVASCRIPT
116
+ @playwright_page.capybara_current_frame.evaluate(js)
115
117
  }
116
- JAVASCRIPT
117
- @playwright_page.capybara_current_frame.evaluate(js)
118
118
  end
119
119
 
120
120
  def title
121
- assert_page_alive
122
-
123
- @playwright_page.title
121
+ assert_page_alive {
122
+ @playwright_page.title
123
+ }
124
124
  end
125
125
 
126
126
  def go_back
127
- assert_page_alive
128
-
129
- @playwright_page.go_back
127
+ assert_page_alive {
128
+ @playwright_page.go_back
129
+ }
130
130
  end
131
131
 
132
132
  def go_forward
133
- assert_page_alive
134
-
135
- @playwright_page.go_forward
133
+ assert_page_alive {
134
+ @playwright_page.go_forward
135
+ }
136
136
  end
137
137
 
138
138
  def execute_script(script, *args)
139
- assert_page_alive
140
-
141
- @playwright_page.capybara_current_frame.evaluate("function (arguments) { #{script} }", arg: unwrap_node(args))
139
+ assert_page_alive {
140
+ @playwright_page.capybara_current_frame.evaluate("function (arguments) { #{script} }", arg: unwrap_node(args))
141
+ }
142
142
  nil
143
143
  end
144
144
 
145
145
  def evaluate_script(script, *args)
146
- assert_page_alive
147
-
148
- result = @playwright_page.capybara_current_frame.evaluate_handle("function (arguments) { return #{script} }", arg: unwrap_node(args))
149
- wrap_node(result)
146
+ assert_page_alive {
147
+ result = @playwright_page.capybara_current_frame.evaluate_handle("function (arguments) { return #{script} }", arg: unwrap_node(args))
148
+ wrap_node(result)
149
+ }
150
150
  end
151
151
 
152
152
  def evaluate_async_script(script, *args)
153
- assert_page_alive
154
-
155
- js = <<~JAVASCRIPT
156
- function(_arguments){
157
- let args = Array.prototype.slice.call(_arguments);
158
- return new Promise((resolve, reject) => {
159
- args.push(resolve);
160
- (function(){ #{script} }).apply(this, args);
161
- });
153
+ assert_page_alive {
154
+ js = <<~JAVASCRIPT
155
+ function(_arguments){
156
+ let args = Array.prototype.slice.call(_arguments);
157
+ return new Promise((resolve, reject) => {
158
+ args.push(resolve);
159
+ (function(){ #{script} }).apply(this, args);
160
+ });
161
+ }
162
+ JAVASCRIPT
163
+ result = @playwright_page.capybara_current_frame.evaluate_handle(js, arg: unwrap_node(args))
164
+ wrap_node(result)
162
165
  }
163
- JAVASCRIPT
164
- result = @playwright_page.capybara_current_frame.evaluate_handle(js, arg: unwrap_node(args))
165
- wrap_node(result)
166
166
  end
167
167
 
168
168
  def active_element
@@ -191,9 +191,9 @@ module Capybara
191
191
  end
192
192
 
193
193
  def save_screenshot(path, **options)
194
- assert_page_alive
195
-
196
- @playwright_page.screenshot(path: path)
194
+ assert_page_alive {
195
+ @playwright_page.screenshot(path: path)
196
+ }
197
197
  end
198
198
 
199
199
  def send_keys(*args)
@@ -201,24 +201,47 @@ module Capybara
201
201
  end
202
202
 
203
203
  def switch_to_frame(frame)
204
- assert_page_alive
205
-
206
- case frame
207
- when :top
208
- @playwright_page.capybara_reset_frames
209
- when :parent
210
- @playwright_page.capybara_pop_frame
211
- else
212
- playwright_frame = frame.native.content_frame
213
- raise ArgumentError.new("Not a frame element: #{frame}") unless playwright_frame
214
- @playwright_page.capybara_push_frame(playwright_frame)
215
- end
204
+ assert_page_alive {
205
+ case frame
206
+ when :top
207
+ @playwright_page.capybara_reset_frames
208
+ when :parent
209
+ @playwright_page.capybara_pop_frame
210
+ else
211
+ playwright_frame = frame.native.content_frame
212
+ raise ArgumentError.new("Not a frame element: #{frame}") unless playwright_frame
213
+ @playwright_page.capybara_push_frame(playwright_frame)
214
+ end
215
+ }
216
216
  end
217
217
 
218
- private def assert_page_alive
218
+ # Capybara doesn't retry at this case since it doesn't use `synchronize { ... } for driver/browser methods.`
219
+ # We have to retry ourselves.
220
+ private def assert_page_alive(retry_count: 5, &block)
219
221
  if !@playwright_page || @playwright_page.closed?
220
222
  raise NoSuchWindowError
221
223
  end
224
+
225
+ if retry_count <= 0
226
+ return block.call
227
+ end
228
+
229
+ begin
230
+ return block.call
231
+ rescue ::Playwright::Error => err
232
+ case err.message
233
+ when /Element is not attached to the DOM/,
234
+ /Execution context was destroyed, most likely because of a navigation/,
235
+ /Cannot find context with specified id/,
236
+ /Unable to adopt element handle from a different document/
237
+ # ignore error for retry
238
+ puts "[WARNING] #{err.message}"
239
+ else
240
+ raise
241
+ end
242
+ end
243
+
244
+ assert_page_alive(retry_count: retry_count - 1, &block)
222
245
  end
223
246
 
224
247
  private def pages
@@ -303,15 +326,15 @@ module Capybara
303
326
  end
304
327
 
305
328
  def accept_modal(dialog_type, **options, &block)
306
- assert_page_alive
307
-
308
- @playwright_page.capybara_accept_modal(dialog_type, **options, &block)
329
+ assert_page_alive {
330
+ @playwright_page.capybara_accept_modal(dialog_type, **options, &block)
331
+ }
309
332
  end
310
333
 
311
334
  def dismiss_modal(dialog_type, **options, &block)
312
- assert_page_alive
313
-
314
- @playwright_page.capybara_dismiss_modal(dialog_type, **options, &block)
335
+ assert_page_alive {
336
+ @playwright_page.capybara_dismiss_modal(dialog_type, **options, &block)
337
+ }
315
338
  end
316
339
 
317
340
  private def unwrap_node(args)
@@ -361,9 +384,9 @@ module Capybara
361
384
  end
362
385
 
363
386
  def with_playwright_page(&block)
364
- assert_page_alive
365
-
366
- block.call(@playwright_page)
387
+ assert_page_alive {
388
+ block.call(@playwright_page)
389
+ }
367
390
  end
368
391
  end
369
392
  end
@@ -74,15 +74,19 @@ module Capybara
74
74
  @element
75
75
  end
76
76
 
77
- private def assert_element_not_stale
77
+ private def assert_element_not_stale(&block)
78
78
  # Playwright checks the staled state only when
79
79
  # actionable methods. (click, select_option, hover, ...)
80
80
  # Capybara expects stale checking also when getting inner text, and so on.
81
81
  @element.enabled?
82
+
83
+ block.call
82
84
  rescue ::Playwright::Error => err
83
85
  case err.message
84
86
  when /Element is not attached to the DOM/
85
87
  raise StaleReferenceError.new(err)
88
+ when /Execution context was destroyed, most likely because of a navigation/
89
+ raise StaleReferenceError.new(err)
86
90
  when /Cannot find context with specified id/
87
91
  raise StaleReferenceError.new(err)
88
92
  when /Unable to adopt element handle from a different document/ # for WebKit.
@@ -102,42 +106,42 @@ module Capybara
102
106
  class StaleReferenceError < StandardError ; end
103
107
 
104
108
  def all_text
105
- assert_element_not_stale
106
-
107
- text = @element.text_content
108
- text.to_s.gsub(/[\u200b\u200e\u200f]/, '')
109
- .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
110
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
111
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
112
- .tr("\u00a0", ' ')
109
+ assert_element_not_stale {
110
+ text = @element.text_content
111
+ text.to_s.gsub(/[\u200b\u200e\u200f]/, '')
112
+ .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
113
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
114
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
115
+ .tr("\u00a0", ' ')
116
+ }
113
117
  end
114
118
 
115
119
  def visible_text
116
- assert_element_not_stale
117
-
118
- return '' unless visible?
119
-
120
- text = @element.evaluate(<<~JAVASCRIPT)
121
- function(el){
122
- if (el.nodeName == 'TEXTAREA'){
123
- return el.textContent;
124
- } else if (el instanceof SVGElement) {
125
- return el.textContent;
126
- } else {
127
- return el.innerText;
120
+ assert_element_not_stale {
121
+ return '' unless visible?
122
+
123
+ text = @element.evaluate(<<~JAVASCRIPT)
124
+ function(el){
125
+ if (el.nodeName == 'TEXTAREA'){
126
+ return el.textContent;
127
+ } else if (el instanceof SVGElement) {
128
+ return el.textContent;
129
+ } else {
130
+ return el.innerText;
131
+ }
128
132
  }
129
- }
130
- JAVASCRIPT
131
- text.to_s.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
132
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
133
- .gsub(/\n+/, "\n")
134
- .tr("\u00a0", ' ')
133
+ JAVASCRIPT
134
+ text.to_s.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
135
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
136
+ .gsub(/\n+/, "\n")
137
+ .tr("\u00a0", ' ')
138
+ }
135
139
  end
136
140
 
137
141
  def [](name)
138
- assert_element_not_stale
139
-
140
- property(name) || attribute(name)
142
+ assert_element_not_stale {
143
+ property(name) || attribute(name)
144
+ }
141
145
  end
142
146
 
143
147
  private def property(name)
@@ -150,17 +154,17 @@ module Capybara
150
154
  end
151
155
 
152
156
  def value
153
- assert_element_not_stale
154
-
155
- # ref: https://github.com/teamcapybara/capybara/blob/f7ab0b5cd5da86185816c2d5c30d58145fe654ed/lib/capybara/selenium/node.rb#L31
156
- # ref: https://github.com/twalpole/apparition/blob/11aca464b38b77585191b7e302be2e062bdd369d/lib/capybara/apparition/node.rb#L728
157
- if tag_name == 'select' && @element.evaluate('el => el.multiple')
158
- @element.query_selector_all('option:checked').map do |option|
159
- option.evaluate('el => el.value')
157
+ assert_element_not_stale {
158
+ # ref: https://github.com/teamcapybara/capybara/blob/f7ab0b5cd5da86185816c2d5c30d58145fe654ed/lib/capybara/selenium/node.rb#L31
159
+ # ref: https://github.com/twalpole/apparition/blob/11aca464b38b77585191b7e302be2e062bdd369d/lib/capybara/apparition/node.rb#L728
160
+ if tag_name == 'select' && @element.evaluate('el => el.multiple')
161
+ @element.query_selector_all('option:checked').map do |option|
162
+ option.evaluate('el => el.value')
163
+ end
164
+ else
165
+ @element.evaluate('el => el.value')
160
166
  end
161
- else
162
- @element.evaluate('el => el.value')
163
- end
167
+ }
164
168
  end
165
169
 
166
170
  def style(styles)
@@ -245,7 +249,15 @@ module Capybara
245
249
 
246
250
  class FileUpload < Settable
247
251
  def set(value, **options)
248
- @element.set_input_files(value, timeout: @timeout)
252
+ file =
253
+ if value.is_a?(File)
254
+ value.path
255
+ elsif value.is_a?(Enumerable)
256
+ value.map(&:to_s)
257
+ else
258
+ value.to_s
259
+ end
260
+ @element.set_input_files(file, timeout: @timeout)
249
261
  end
250
262
  end
251
263
 
@@ -772,37 +784,37 @@ module Capybara
772
784
  end
773
785
 
774
786
  def visible?
775
- assert_element_not_stale
776
-
777
- # if an area element, check visibility of relevant image
778
- @element.evaluate(<<~JAVASCRIPT)
779
- function(el) {
780
- if (el.tagName == 'AREA'){
781
- const map_name = document.evaluate('./ancestor::map/@name', el, null, XPathResult.STRING_TYPE, null).stringValue;
782
- el = document.querySelector(`img[usemap='#${map_name}']`);
783
- if (!el){
784
- return false;
785
- }
786
- }
787
- var forced_visible = false;
788
- while (el) {
789
- const style = window.getComputedStyle(el);
790
- if (style.visibility == 'visible')
791
- forced_visible = true;
792
- if ((style.display == 'none') ||
793
- ((style.visibility == 'hidden') && !forced_visible) ||
794
- (parseFloat(style.opacity) == 0)) {
787
+ assert_element_not_stale {
788
+ # if an area element, check visibility of relevant image
789
+ @element.evaluate(<<~JAVASCRIPT)
790
+ function(el) {
791
+ if (el.tagName == 'AREA'){
792
+ const map_name = document.evaluate('./ancestor::map/@name', el, null, XPathResult.STRING_TYPE, null).stringValue;
793
+ el = document.querySelector(`img[usemap='#${map_name}']`);
794
+ if (!el){
795
795
  return false;
796
+ }
796
797
  }
797
- var parent = el.parentElement;
798
- if (parent && (parent.tagName == 'DETAILS') && !parent.open && (el.tagName != 'SUMMARY')) {
799
- return false;
798
+ var forced_visible = false;
799
+ while (el) {
800
+ const style = window.getComputedStyle(el);
801
+ if (style.visibility == 'visible')
802
+ forced_visible = true;
803
+ if ((style.display == 'none') ||
804
+ ((style.visibility == 'hidden') && !forced_visible) ||
805
+ (parseFloat(style.opacity) == 0)) {
806
+ return false;
807
+ }
808
+ var parent = el.parentElement;
809
+ if (parent && (parent.tagName == 'DETAILS') && !parent.open && (el.tagName != 'SUMMARY')) {
810
+ return false;
811
+ }
812
+ el = parent;
800
813
  }
801
- el = parent;
814
+ return true;
802
815
  }
803
- return true;
816
+ JAVASCRIPT
804
817
  }
805
- JAVASCRIPT
806
818
  end
807
819
 
808
820
  def obscured?
@@ -810,15 +822,15 @@ module Capybara
810
822
  end
811
823
 
812
824
  def checked?
813
- assert_element_not_stale
814
-
815
- @element.evaluate('el => !!el.checked')
825
+ assert_element_not_stale {
826
+ @element.evaluate('el => !!el.checked')
827
+ }
816
828
  end
817
829
 
818
830
  def selected?
819
- assert_element_not_stale
820
-
821
- @element.evaluate('el => !!el.selected')
831
+ assert_element_not_stale {
832
+ @element.evaluate('el => !!el.selected')
833
+ }
822
834
  end
823
835
 
824
836
  def disabled?
@@ -842,55 +854,66 @@ module Capybara
842
854
  end
843
855
 
844
856
  def rect
845
- assert_element_not_stale
846
-
847
- @element.evaluate(<<~JAVASCRIPT)
848
- function(el){
849
- const rects = [...el.getClientRects()]
850
- const rect = rects.find(r => (r.height && r.width)) || el.getBoundingClientRect();
851
- return rect.toJSON();
857
+ assert_element_not_stale {
858
+ @element.evaluate(<<~JAVASCRIPT)
859
+ function(el){
860
+ const rects = [...el.getClientRects()]
861
+ const rect = rects.find(r => (r.height && r.width)) || el.getBoundingClientRect();
862
+ return rect.toJSON();
863
+ }
864
+ JAVASCRIPT
852
865
  }
853
- JAVASCRIPT
854
866
  end
855
867
 
856
868
  def path
857
- assert_element_not_stale
858
-
859
- @element.evaluate(<<~JAVASCRIPT)
860
- (el) => {
861
- var xml = document;
862
- var xpath = '';
863
- var pos, tempitem2;
864
- if (el.getRootNode && el.getRootNode() instanceof ShadowRoot) {
865
- return "(: Shadow DOM element - no XPath :)";
866
- };
867
- while(el !== xml.documentElement) {
868
- pos = 0;
869
- tempitem2 = el;
870
- while(tempitem2) {
871
- if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name
872
- pos += 1;
869
+ assert_element_not_stale {
870
+ @element.evaluate(<<~JAVASCRIPT)
871
+ (el) => {
872
+ var xml = document;
873
+ var xpath = '';
874
+ var pos, tempitem2;
875
+ if (el.getRootNode && el.getRootNode() instanceof ShadowRoot) {
876
+ return "(: Shadow DOM element - no XPath :)";
877
+ };
878
+ while(el !== xml.documentElement) {
879
+ pos = 0;
880
+ tempitem2 = el;
881
+ while(tempitem2) {
882
+ if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name
883
+ pos += 1;
884
+ }
885
+ tempitem2 = tempitem2.previousSibling;
873
886
  }
874
- tempitem2 = tempitem2.previousSibling;
875
- }
876
- if (el.namespaceURI != xml.documentElement.namespaceURI) {
877
- xpath = "*[local-name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
878
- } else {
879
- xpath = el.nodeName.toUpperCase()+"["+pos+"]/"+xpath;
887
+ if (el.namespaceURI != xml.documentElement.namespaceURI) {
888
+ xpath = "*[local-name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
889
+ } else {
890
+ xpath = el.nodeName.toUpperCase()+"["+pos+"]/"+xpath;
891
+ }
892
+ el = el.parentNode;
880
893
  }
881
- el = el.parentNode;
894
+ xpath = '/'+xml.documentElement.nodeName.toUpperCase()+'/'+xpath;
895
+ xpath = xpath.replace(/\\/$/, '');
896
+ return xpath;
882
897
  }
883
- xpath = '/'+xml.documentElement.nodeName.toUpperCase()+'/'+xpath;
884
- xpath = xpath.replace(/\\/$/, '');
885
- return xpath;
898
+ JAVASCRIPT
886
899
  }
887
- JAVASCRIPT
888
900
  end
889
901
 
890
902
  def trigger(event)
891
903
  @element.dispatch_event(event)
892
904
  end
893
905
 
906
+ def shadow_root
907
+ # Playwright does not distinguish shadow DOM.
908
+ # https://playwright.dev/docs/selectors#selecting-elements-in-shadow-dom
909
+ # Just do with Host element as shadow root Element.
910
+ #
911
+ # Node.new(@driver, @page, @element.evaluate_handle('el => el.shadowRoot'))
912
+ #
913
+ # does not work well because of the Playwright Error 'Element is not attached to the DOM'
914
+ ShadowRootNode.new(@driver, @page, @element)
915
+ end
916
+
894
917
  def inspect
895
918
  %(#<#{self.class} tag="#{tag_name}" path="#{path}">)
896
919
  end
@@ -902,19 +925,19 @@ module Capybara
902
925
  end
903
926
 
904
927
  def find_xpath(query, **options)
905
- assert_element_not_stale
906
-
907
- @element.query_selector_all("xpath=#{query}").map do |el|
908
- Node.new(@driver, @page, el)
909
- end
928
+ assert_element_not_stale {
929
+ @element.query_selector_all("xpath=#{query}").map do |el|
930
+ Node.new(@driver, @page, el)
931
+ end
932
+ }
910
933
  end
911
934
 
912
935
  def find_css(query, **options)
913
- assert_element_not_stale
914
-
915
- @element.query_selector_all(query).map do |el|
916
- Node.new(@driver, @page, el)
917
- end
936
+ assert_element_not_stale {
937
+ @element.query_selector_all(query).map do |el|
938
+ Node.new(@driver, @page, el)
939
+ end
940
+ }
918
941
  end
919
942
  end
920
943
  end
@@ -27,6 +27,7 @@ module Capybara
27
27
  record_video_dir: nil,
28
28
  record_video_size: nil,
29
29
  screen: nil,
30
+ serviceWorkers: nil,
30
31
  storageState: nil,
31
32
  timezoneId: nil,
32
33
  userAgent: nil,
@@ -0,0 +1,39 @@
1
+ require_relative './node'
2
+
3
+ module Capybara
4
+ module Playwright
5
+ class ShadowRootNode < Node
6
+ def initialize(driver, page, element)
7
+ super
8
+ @shadow_roow_element = element.evaluate_handle('el => el.shadowRoot')
9
+ end
10
+
11
+ def all_text
12
+ assert_element_not_stale
13
+
14
+ text = @shadow_roow_element.text_content
15
+ text.to_s.gsub(/[\u200b\u200e\u200f]/, '')
16
+ .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
17
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
18
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
19
+ .tr("\u00a0", ' ')
20
+ end
21
+
22
+ def visible_text
23
+ assert_element_not_stale
24
+
25
+ return '' unless visible?
26
+
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.
29
+ gsub(/[\u200b\u200e\u200f]/, '').
30
+ gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
31
+ displayed_text.squeeze(' ')
32
+ .gsub(/[\ \n]*\n[\ \n]*/, "\n")
33
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
34
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
35
+ .tr("\u00a0", ' ')
36
+ end
37
+ end
38
+ end
39
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Playwright
5
- VERSION = '0.2.1'
5
+ VERSION = '0.3.1'
6
6
  end
7
7
  end
@@ -11,4 +11,5 @@ require 'capybara/playwright/driver'
11
11
  require 'capybara/playwright/node'
12
12
  require 'capybara/playwright/page'
13
13
  require 'capybara/playwright/page_options'
14
+ require 'capybara/playwright/shadow_root_node'
14
15
  require 'capybara/playwright/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-playwright-driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-23 00:00:00.000000000 Z
11
+ date: 2022-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rack-test_server
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rake
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -191,6 +205,7 @@ files:
191
205
  - lib/capybara/playwright/node.rb
192
206
  - lib/capybara/playwright/page.rb
193
207
  - lib/capybara/playwright/page_options.rb
208
+ - lib/capybara/playwright/shadow_root_node.rb
194
209
  - lib/capybara/playwright/tmpdir_owner.rb
195
210
  - lib/capybara/playwright/version.rb
196
211
  homepage: https://github.com/YusukeIwaki/capybara-playwright-driver
@@ -212,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
227
  - !ruby/object:Gem::Version
213
228
  version: '0'
214
229
  requirements: []
215
- rubygems_version: 3.3.7
230
+ rubygems_version: 3.3.26
216
231
  signing_key:
217
232
  specification_version: 4
218
233
  summary: Playwright driver for Capybara