capybara-playwright-driver 0.0.0.pre.r1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b85ab44fa6fca141e177e1ef28197af313cda7e2e1f23cd4d51a8edf5b12e97f
4
+ data.tar.gz: 7b665e1abde79e8f41804773de8af36b46e438cca2908671b87e7190bc23268d
5
+ SHA512:
6
+ metadata.gz: 8a0190946c3581203b63000d5e3cf1a5b5c97daeacbbfb4c9eeaba0ab2c1aba1757492e59b9ccc286090f5763116b660b18095400e9e2343645f4fa28a9e6086
7
+ data.tar.gz: acc5f5d43c7c46efa61ed501bce789c4fb22e86c19eb83d3133aa1b32cc4521f456973fcedcc0b2b8930bcb31d2c52d253fbfb5649ffd6a1f9aed7b3a9aeba71
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at iwaki@i3-systems.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in capybara-playwright.gemspec
8
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 YusukeIwaki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # 🎭 Playwright driver for Capybara
2
+
3
+ ```ruby
4
+ gem 'capybara-playwright-driver'
5
+ ```
6
+
7
+ ## Examples
8
+
9
+ ```ruby
10
+ require 'capybara-playwright-driver'
11
+
12
+ # setup
13
+ Capybara.register_driver(:playwright) do |app|
14
+ Capybara::Playwright::Driver.new(app, browser_type: :firefox, headless: false)
15
+ end
16
+ Capybara.default_max_wait_time = 15
17
+ Capybara.default_driver = :playwright
18
+ Capybara.save_path = 'tmp/capybara'
19
+
20
+ # run
21
+ Capybara.app_host = 'https://github.com'
22
+ visit '/'
23
+ fill_in('q', with: 'Capybara')
24
+
25
+ ## [REMARK] Use Playwright-native Page instead of flaky Capybara's selector/action.
26
+ # find('a[data-item-type="global_search"]').click
27
+ page.driver.with_playwright_page do |page|
28
+ page.click('a[data-item-type="global_search"]')
29
+ end
30
+
31
+ all('.repo-list-item').each do |li|
32
+ puts "#{li.all('a').first.text} by Capybara"
33
+ puts "#{li.with_playwright_element_handle { |handle| handle.query_selector('a').text_content }} by Playwright"
34
+ end
35
+ ```
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ 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).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'capybara/playwright'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'capybara/playwright/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'capybara-playwright-driver'
9
+ spec.version = Capybara::Playwright::VERSION
10
+
11
+ spec.authors = ['YusukeIwaki']
12
+ spec.email = ['q7w8e9w8q7w8e9@yahoo.co.jp']
13
+
14
+ spec.summary = 'Playwright driver for Capybara'
15
+ spec.homepage = 'https://github.com/YusukeIwaki/capybara-playwright-driver'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
+ `git ls-files -z`.split("\x0").reject do |f|
20
+ f.match(%r{^(test|spec|features)/}) || f.include?('.git')
21
+ end
22
+ end
23
+ spec.bindir = 'exe'
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.add_dependency 'capybara'
28
+ spec.add_dependency 'playwright-ruby-client', '>= 0.5.9'
29
+ spec.add_development_dependency 'bundler', '~> 2.2.3'
30
+ spec.add_development_dependency 'launchy', '>= 2.0.4'
31
+ spec.add_development_dependency 'pry-byebug'
32
+ spec.add_development_dependency 'rake', '~> 13.0.3'
33
+ spec.add_development_dependency 'rspec', '~> 3.10.0'
34
+ spec.add_development_dependency 'rubocop', '~> 1.7.0'
35
+ spec.add_development_dependency 'rubocop-rspec'
36
+ spec.add_development_dependency 'sinatra', '>= 1.4.0'
37
+ spec.add_development_dependency 'webrick'
38
+ end
@@ -0,0 +1,2 @@
1
+ # just an alias.
2
+ require 'capybara/playwright'
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara'
4
+ require 'playwright'
5
+
6
+ require 'capybara/playwright/browser'
7
+ require 'capybara/playwright/browser_options'
8
+ require 'capybara/playwright/dialog_event_handler'
9
+ require 'capybara/playwright/driver'
10
+ require 'capybara/playwright/node'
11
+ require 'capybara/playwright/page'
12
+ require 'capybara/playwright/page_options'
13
+ require 'capybara/playwright/version'
@@ -0,0 +1,319 @@
1
+ module Capybara
2
+ module Playwright
3
+ class Browser
4
+ extend Forwardable
5
+
6
+ class NoSuchWindowError < StandardError ; end
7
+
8
+ def initialize(playwright:, driver:, browser_type:, browser_options:, page_options:)
9
+ @driver = driver
10
+
11
+ browser_type = playwright.send(browser_type)
12
+ @playwright_browser = browser_type.launch(**browser_options)
13
+ @page_options = page_options
14
+ @playwright_page = create_page(create_browser_context)
15
+ end
16
+
17
+ private def create_browser_context
18
+ @playwright_browser.new_context(**@page_options).tap do |browser_context|
19
+ browser_context.on('page', ->(page) {
20
+ unless @playwright_page
21
+ @playwright_page = page
22
+ end
23
+ })
24
+ end
25
+ end
26
+
27
+ private def create_page(browser_context)
28
+ browser_context.new_page.tap do |page|
29
+ page.on('close', -> {
30
+ if @playwright_page
31
+ @playwright_page = nil
32
+ end
33
+ })
34
+ end
35
+ end
36
+
37
+ def quit
38
+ @playwright_browser.close
39
+ end
40
+
41
+ def current_url
42
+ assert_page_alive
43
+
44
+ @playwright_page.url
45
+ end
46
+
47
+ def visit(path)
48
+ assert_page_alive
49
+
50
+ url =
51
+ if Capybara.app_host
52
+ URI(Capybara.app_host).merge(path)
53
+ elsif Capybara.default_host
54
+ URI(Capybara.default_host).merge(path)
55
+ else
56
+ path
57
+ end
58
+
59
+ @playwright_page.capybara_current_frame.goto(url)
60
+ end
61
+
62
+ def refresh
63
+ assert_page_alive
64
+
65
+ @playwright_page.capybara_current_frame.evaluate('() => { location.reload(true) }')
66
+ end
67
+
68
+ def find_xpath(query, **options)
69
+ assert_page_alive
70
+
71
+ @playwright_page.capybara_current_frame.query_selector_all("xpath=#{query}").map do |el|
72
+ Node.new(@driver, @puppeteer_page, el)
73
+ end
74
+ end
75
+
76
+ def find_css(query, **options)
77
+ assert_page_alive
78
+
79
+ @playwright_page.capybara_current_frame.query_selector_all(query).map do |el|
80
+ Node.new(@driver, @playwright_page, el)
81
+ end
82
+ end
83
+
84
+ def response_headers
85
+ assert_page_alive
86
+
87
+ @playwright_page.capybara_response_headers
88
+ end
89
+
90
+ def status_code
91
+ assert_page_alive
92
+
93
+ @playwright_page.capybara_status_code
94
+ end
95
+
96
+ def html
97
+ assert_page_alive
98
+
99
+ js = <<~JAVASCRIPT
100
+ () => {
101
+ let html = '';
102
+ if (document.doctype) html += new XMLSerializer().serializeToString(document.doctype);
103
+ if (document.documentElement) html += document.documentElement.outerHTML;
104
+ return html;
105
+ }
106
+ JAVASCRIPT
107
+ @playwright_page.capybara_current_frame.evaluate(js)
108
+ end
109
+
110
+ def title
111
+ assert_page_alive
112
+
113
+ @playwright_page.title
114
+ end
115
+
116
+ def go_back
117
+ assert_page_alive
118
+
119
+ @playwright_page.go_back
120
+ end
121
+
122
+ def go_forward
123
+ assert_page_alive
124
+
125
+ @playwright_page.go_forward
126
+ end
127
+
128
+ def execute_script(script, *args)
129
+ assert_page_alive
130
+
131
+ @playwright_page.capybara_current_frame.evaluate("function (arguments) { #{script} }", arg: unwrap_node(args))
132
+ nil
133
+ end
134
+
135
+ def evaluate_script(script, *args)
136
+ assert_page_alive
137
+
138
+ result = @playwright_page.capybara_current_frame.evaluate_handle("function (arguments) { return #{script} }", arg: unwrap_node(args))
139
+ wrap_node(result)
140
+ end
141
+
142
+ def evaluate_async_script(script, *args)
143
+ assert_page_alive
144
+
145
+ js = <<~JAVASCRIPT
146
+ function(_arguments){
147
+ let args = Array.prototype.slice.call(_arguments);
148
+ return new Promise((resolve, reject) => {
149
+ args.push(resolve);
150
+ (function(){ #{script} }).apply(this, args);
151
+ });
152
+ }
153
+ JAVASCRIPT
154
+ result = @playwright_page.capybara_current_frame.evaluate_handle(js, arg: unwrap_node(args))
155
+ wrap_node(result)
156
+ end
157
+
158
+ def save_screenshot(path, **options)
159
+ assert_page_alive
160
+
161
+ @playwright_page.screenshot(path: path)
162
+ end
163
+
164
+ def send_keys(*args)
165
+ Node::SendKeys.new(@playwright_page.keyboard, args).execute
166
+ end
167
+
168
+ def switch_to_frame(frame)
169
+ assert_page_alive
170
+
171
+ case frame
172
+ when :top
173
+ @playwright_page.capybara_reset_frames
174
+ when :parent
175
+ @playwright_page.capybara_pop_frame
176
+ else
177
+ playwright_frame = frame.native.content_frame
178
+ raise ArgumentError.new("Not a frame element: #{frame}") unless playwright_frame
179
+ @playwright_page.capybara_push_frame(playwright_frame)
180
+ end
181
+ end
182
+
183
+ private def assert_page_alive
184
+ if !@playwright_page || @playwright_page.closed?
185
+ raise NoSuchWindowError
186
+ end
187
+ end
188
+
189
+ private def pages
190
+ @playwright_browser.contexts.flat_map(&:pages)
191
+ end
192
+
193
+ def window_handles
194
+ pages.map(&:guid)
195
+ end
196
+
197
+ def current_window_handle
198
+ @playwright_page&.guid
199
+ end
200
+
201
+ def open_new_window(kind = :tab)
202
+ browser_context =
203
+ if kind == :tab
204
+ @playwright_page&.context || create_browser_context
205
+ else
206
+ create_browser_context
207
+ end
208
+
209
+ create_page(browser_context)
210
+ end
211
+
212
+ private def on_window(handle, &block)
213
+ page = pages.find { |page| page.guid == handle }
214
+ if page
215
+ block.call(page)
216
+ else
217
+ raise NoSuchWindowError
218
+ end
219
+ end
220
+
221
+ def switch_to_window(handle)
222
+ if @playwright_page&.guid != handle
223
+ on_window(handle) do |page|
224
+ @playwright_page = page.tap(&:bring_to_front)
225
+ end
226
+ end
227
+ end
228
+
229
+ def close_window(handle)
230
+ on_window(handle) do |page|
231
+ page.close
232
+
233
+ if @playwright_page&.guid == handle
234
+ @playwright_page = nil
235
+ end
236
+ end
237
+ end
238
+
239
+ def window_size(handle)
240
+ on_window(handle) do |page|
241
+ page.evaluate('() => [window.innerWidth, window.innerHeight]')
242
+ end
243
+ end
244
+
245
+ def resize_window_to(handle, width, height)
246
+ on_window(handle) do |page|
247
+ page.viewport_size = { width: width, height: height }
248
+ end
249
+ end
250
+
251
+ def maximize_window(handle)
252
+ puts "[WARNING] maximize_window is not supported in Playwright driver"
253
+ # incomplete in Playwright
254
+ # ref: https://github.com/twalpole/apparition/blob/11aca464b38b77585191b7e302be2e062bdd369d/lib/capybara/apparition/page.rb#L346
255
+ on_window(handle) do |page|
256
+ screen_size = page.evaluate('() => ({ width: window.screen.width, height: window.screen.height})')
257
+ page.viewport_size = screen_size
258
+ end
259
+ end
260
+
261
+ def fullscreen_window(handle)
262
+ puts "[WARNING] fullscreen_window is not supported in Playwright driver"
263
+ # incomplete in Playwright
264
+ # ref: https://github.com/twalpole/apparition/blob/11aca464b38b77585191b7e302be2e062bdd369d/lib/capybara/apparition/page.rb#L341
265
+ on_window(handle) do |page|
266
+ page.evaluate('() => document.body.requestFullscreen()')
267
+ end
268
+ end
269
+
270
+ def accept_modal(dialog_type, **options, &block)
271
+ assert_page_alive
272
+
273
+ @playwright_page.capybara_accept_modal(dialog_type, **options, &block)
274
+ end
275
+
276
+ def dismiss_modal(dialog_type, **options, &block)
277
+ assert_page_alive
278
+
279
+ @playwright_page.capybara_dismiss_modal(dialog_type, **options, &block)
280
+ end
281
+
282
+ private def unwrap_node(args)
283
+ args.map do |arg|
284
+ if arg.is_a?(Node)
285
+ arg.send(:element)
286
+ else
287
+ arg
288
+ end
289
+ end
290
+ end
291
+
292
+ private def wrap_node(arg)
293
+ case arg
294
+ when Array
295
+ arg.map do |item|
296
+ wrap_node(item)
297
+ end
298
+ when Hash
299
+ arg.map do |key, value|
300
+ [key, wrap_node(value)]
301
+ end.to_h
302
+ when ::Playwright::ElementHandle
303
+ Node.new(@driver, @puppeteer_page, arg)
304
+ when ::Playwright::JSHandle
305
+ arg.json_value
306
+ else
307
+ arg
308
+ end
309
+ end
310
+
311
+ def with_playwright_page(&block)
312
+ assert_page_alive
313
+ raise ArgumentError.new('block must be given') unless block
314
+
315
+ block.call(@playwright_page)
316
+ end
317
+ end
318
+ end
319
+ end