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 +4 -4
- data/AGENTS.md +107 -0
- data/README.md +18 -1
- data/capybara-playwright.gemspec +0 -2
- data/lib/capybara/playwright/browser.rb +7 -4
- data/lib/capybara/playwright/driver.rb +2 -2
- data/lib/capybara/playwright/node.rb +219 -4
- data/lib/capybara/playwright/page.rb +12 -10
- data/lib/capybara/playwright/shadow_root_node.rb +3 -3
- data/lib/capybara/playwright/version.rb +1 -1
- metadata +5 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a319465a40c53ac5b47d86ccd4d15136c20dcf7f6dde6fe17b3906d44cb2fd94
|
|
4
|
+
data.tar.gz: 1e38fb6020a87a07e6eaa8e997084b3abd98a0a8d5cbec4e92819a960c525f62
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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/
|
|
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).
|
data/capybara-playwright.gemspec
CHANGED
|
@@ -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
|
|
94
|
-
# video is
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
@
|
|
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 = @
|
|
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 = @
|
|
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(' ')
|
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.
|
|
4
|
+
version: 0.5.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- YusukeIwaki
|
|
8
|
-
|
|
9
|
-
bindir: exe
|
|
8
|
+
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
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.
|
|
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: []
|