capybara-playwright-driver 0.0.0.pre.r1 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b85ab44fa6fca141e177e1ef28197af313cda7e2e1f23cd4d51a8edf5b12e97f
4
- data.tar.gz: 7b665e1abde79e8f41804773de8af36b46e438cca2908671b87e7190bc23268d
3
+ metadata.gz: 17075d587d4657d2c2f70b68256bbaeac10e011d553b092cbbed35cb33de6f05
4
+ data.tar.gz: c42763d910584f0687985887ab07180f1fde752c9b7543b1613d481757fd3898
5
5
  SHA512:
6
- metadata.gz: 8a0190946c3581203b63000d5e3cf1a5b5c97daeacbbfb4c9eeaba0ab2c1aba1757492e59b9ccc286090f5763116b660b18095400e9e2343645f4fa28a9e6086
7
- data.tar.gz: acc5f5d43c7c46efa61ed501bce789c4fb22e86c19eb83d3133aa1b32cc4521f456973fcedcc0b2b8930bcb31d2c52d253fbfb5649ffd6a1f9aed7b3a9aeba71
6
+ metadata.gz: 94bf8ef42f8d6a8e59b1f160eeb10f142432015beb9c219bab1c0dac31fbbf7dfcea1c2a63349aaf32dac58e87b33add9a690471eef90d96a5d228a9bc9f456e
7
+ data.tar.gz: 894cc32d6ef13f2611d81b240a56f75b4ea445667ad7b8215a8955c96e742118dcb08ddf2f15df02fe375674665f45ead8ae892869421e99b01b2ec4bb687082
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
+ [![Gem Version](https://badge.fury.io/rb/capybara-playwright-driver.svg)](https://badge.fury.io/rb/capybara-playwright-driver)
2
+
1
3
  # 🎭 Playwright driver for Capybara
2
4
 
5
+ #### [Docs](https://playwright-ruby-client.vercel.app/docs/article/guides/rails_integration)
6
+
3
7
  ```ruby
4
8
  gem 'capybara-playwright-driver'
5
9
  ```
@@ -22,14 +26,14 @@ Capybara.app_host = 'https://github.com'
22
26
  visit '/'
23
27
  fill_in('q', with: 'Capybara')
24
28
 
25
- ## [REMARK] Use Playwright-native Page instead of flaky Capybara's selector/action.
29
+ ## [REMARK] We can use Playwright-native selector and action, instead of Capybara DSL.
26
30
  # find('a[data-item-type="global_search"]').click
27
31
  page.driver.with_playwright_page do |page|
28
32
  page.click('a[data-item-type="global_search"]')
29
33
  end
30
34
 
31
35
  all('.repo-list-item').each do |li|
32
- puts "#{li.all('a').first.text} by Capybara"
36
+ #puts "#{li.all('a').first.text} by Capybara"
33
37
  puts "#{li.with_playwright_element_handle { |handle| handle.query_selector('a').text_content }} by Playwright"
34
38
  end
35
39
  ```
@@ -24,14 +24,15 @@ Gem::Specification.new do |spec|
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ['lib']
26
26
 
27
+ spec.required_ruby_version = '>= 2.4'
27
28
  spec.add_dependency 'capybara'
28
- spec.add_dependency 'playwright-ruby-client', '>= 0.5.9'
29
+ spec.add_dependency 'playwright-ruby-client', '>= 0.6.2'
30
+ spec.add_development_dependency 'allure-rspec'
29
31
  spec.add_development_dependency 'bundler', '~> 2.2.3'
30
32
  spec.add_development_dependency 'launchy', '>= 2.0.4'
31
33
  spec.add_development_dependency 'pry-byebug'
32
34
  spec.add_development_dependency 'rake', '~> 13.0.3'
33
35
  spec.add_development_dependency 'rspec', '~> 3.10.0'
34
- spec.add_development_dependency 'rubocop', '~> 1.7.0'
35
36
  spec.add_development_dependency 'rubocop-rspec'
36
37
  spec.add_development_dependency 'sinatra', '>= 1.4.0'
37
38
  spec.add_development_dependency 'webrick'
@@ -1,16 +1,26 @@
1
+ require_relative './tmpdir_owner'
2
+
1
3
  module Capybara
2
4
  module Playwright
5
+ # Responsibility of this class is:
6
+ # - Handling Capybara::Driver commands.
7
+ # - Managing Playwright browser contexts and pages.
8
+ #
9
+ # Note that this class doesn't manage Playwright::Browser.
10
+ # We should not use Playwright::Browser#close in this class.
3
11
  class Browser
12
+ include TmpdirOwner
4
13
  extend Forwardable
5
14
 
6
15
  class NoSuchWindowError < StandardError ; end
7
16
 
8
- def initialize(playwright:, driver:, browser_type:, browser_options:, page_options:)
17
+ def initialize(driver:, playwright_browser:, page_options:, record_video: false)
9
18
  @driver = driver
10
-
11
- browser_type = playwright.send(browser_type)
12
- @playwright_browser = browser_type.launch(**browser_options)
19
+ @playwright_browser = playwright_browser
13
20
  @page_options = page_options
21
+ if record_video
22
+ @page_options[:record_video_dir] ||= tmpdir
23
+ end
14
24
  @playwright_page = create_page(create_browser_context)
15
25
  end
16
26
 
@@ -34,8 +44,8 @@ module Capybara
34
44
  end
35
45
  end
36
46
 
37
- def quit
38
- @playwright_browser.close
47
+ def clear_browser_contexts
48
+ @playwright_browser.contexts.each(&:close)
39
49
  end
40
50
 
41
51
  def current_url
@@ -69,7 +79,7 @@ module Capybara
69
79
  assert_page_alive
70
80
 
71
81
  @playwright_page.capybara_current_frame.query_selector_all("xpath=#{query}").map do |el|
72
- Node.new(@driver, @puppeteer_page, el)
82
+ Node.new(@driver, @playwright_page, el)
73
83
  end
74
84
  end
75
85
 
@@ -155,6 +165,22 @@ module Capybara
155
165
  wrap_node(result)
156
166
  end
157
167
 
168
+ # Not used by Capybara::Session.
169
+ # Intended to be directly called by user.
170
+ def video_path
171
+ return nil if !@playwright_page || @playwright_page.closed?
172
+
173
+ @playwright_page.video&.path
174
+ end
175
+
176
+ # Not used by Capybara::Session.
177
+ # Intended to be directly called by user.
178
+ def raw_screenshot(**options)
179
+ return nil if !@playwright_page || @playwright_page.closed?
180
+
181
+ @playwright_page.screenshot(**options)
182
+ end
183
+
158
184
  def save_screenshot(path, **options)
159
185
  assert_page_alive
160
186
 
@@ -300,7 +326,7 @@ module Capybara
300
326
  [key, wrap_node(value)]
301
327
  end.to_h
302
328
  when ::Playwright::ElementHandle
303
- Node.new(@driver, @puppeteer_page, arg)
329
+ Node.new(@driver, @playwright_page, arg)
304
330
  when ::Playwright::JSHandle
305
331
  arg.json_value
306
332
  else
@@ -310,7 +336,6 @@ module Capybara
310
336
 
311
337
  def with_playwright_page(&block)
312
338
  assert_page_alive
313
- raise ArgumentError.new('block must be given') unless block
314
339
 
315
340
  block.call(@playwright_page)
316
341
  end
@@ -1,7 +1,10 @@
1
+ require_relative './driver_extension'
2
+
1
3
  module Capybara
2
4
  module Playwright
3
5
  class Driver < ::Capybara::Driver::Base
4
6
  extend Forwardable
7
+ include DriverExtension
5
8
 
6
9
  def initialize(app, **options)
7
10
  @playwright_cli_executable_path = options[:playwright_cli_executable_path] || 'npx playwright'
@@ -18,10 +21,20 @@ module Capybara
18
21
  def needs_server?; true; end
19
22
 
20
23
  def browser
21
- @browser ||= create_browser
24
+ @browser ||= ::Capybara::Playwright::Browser.new(
25
+ driver: self,
26
+ playwright_browser: playwright_browser,
27
+ page_options: @page_options.value,
28
+ record_video: callback_on_save_screenrecord?,
29
+ )
22
30
  end
23
31
 
24
- private def create_browser
32
+ private def playwright_browser
33
+ @playwright_browser ||= create_playwright_browser
34
+ end
35
+
36
+ private def create_playwright_browser
37
+ # clean up @playwright_browser and @playwright_execution on exit.
25
38
  main = Process.pid
26
39
  at_exit do
27
40
  # Store the exit status of the test run since it goes away after calling the at_exit proc...
@@ -30,27 +43,45 @@ module Capybara
30
43
  exit @exit_status if @exit_status # Force exit with stored status
31
44
  end
32
45
 
33
- @execution = execute_playwright
34
- ::Capybara::Playwright::Browser.new(
35
- playwright: @execution.playwright,
36
- driver: self,
37
- browser_type: @browser_type,
38
- browser_options: @browser_options.value,
39
- page_options: @page_options.value,
40
- )
46
+ browser_type = playwright_execution.playwright.send(@browser_type)
47
+ browser_options = @browser_options.value
48
+ browser_type.launch(**browser_options)
41
49
  end
42
50
 
43
- private def execute_playwright
44
- ::Playwright.create(playwright_cli_executable_path: @playwright_cli_executable_path)
51
+ private def playwright_execution
52
+ @playwright_execution ||= ::Playwright.create(
53
+ playwright_cli_executable_path: @playwright_cli_executable_path,
54
+ )
45
55
  end
46
56
 
47
57
  private def quit
48
- @browser&.quit
49
- @execution&.stop
58
+ @playwright_browser&.close
59
+ @playwright_browser = nil
60
+ @playwright_execution&.stop
61
+ @playwright_execution = nil
50
62
  end
51
63
 
52
64
  def reset!
53
- quit
65
+ # screenshot is available only before closing page.
66
+ if callback_on_save_screenshot?
67
+ raw_screenshot = @browser&.raw_screenshot
68
+ if raw_screenshot
69
+ callback_on_save_screenshot(raw_screenshot)
70
+ end
71
+ end
72
+
73
+ # video path can be aquired only before closing context.
74
+ # video is completedly saved only after closing context.
75
+ video_path = @browser&.video_path
76
+
77
+ # [NOTE] @playwright_browser should keep alive for better performance.
78
+ # Only `Browser` is disposed.
79
+ @browser&.clear_browser_contexts
80
+
81
+ if video_path
82
+ callback_on_save_screenrecord(video_path)
83
+ end
84
+
54
85
  @browser = nil
55
86
  end
56
87
 
@@ -94,9 +125,6 @@ module Capybara
94
125
  def_delegator(:browser, :switch_to_window)
95
126
  def_delegator(:browser, :accept_modal)
96
127
  def_delegator(:browser, :dismiss_modal)
97
-
98
- # capybara-playwright-driver specific methods
99
- def_delegator(:browser, :with_playwright_page)
100
128
  end
101
129
  end
102
130
  end
@@ -0,0 +1,45 @@
1
+ module Capybara
2
+ module Playwright
3
+ module DriverExtension
4
+ # Register screenshot save process.
5
+ # The callback is called just before page is closed.
6
+ # (just before #reset_session!)
7
+ #
8
+ # The **binary** (String) of the page screenshot is called back into the given block
9
+ def on_save_raw_screenshot_before_reset(&block)
10
+ @callback_on_save_screenshot = block
11
+ end
12
+
13
+ private def callback_on_save_screenshot?
14
+ !!@callback_on_save_screenshot
15
+ end
16
+
17
+ private def callback_on_save_screenshot(raw_screenshot)
18
+ @callback_on_save_screenshot&.call(raw_screenshot)
19
+ end
20
+
21
+ # Register screenrecord save process.
22
+ # The callback is called just after page is closed.
23
+ # (just after #reset_session!)
24
+ #
25
+ # The video path (String) is called back into the given block
26
+ def on_save_screenrecord(&block)
27
+ @callback_on_save_screenrecord = block
28
+ end
29
+
30
+ private def callback_on_save_screenrecord?
31
+ !!@callback_on_save_screenrecord
32
+ end
33
+
34
+ private def callback_on_save_screenrecord(video_path)
35
+ @callback_on_save_screenrecord&.call(video_path)
36
+ end
37
+
38
+ def with_playwright_page(&block)
39
+ raise ArgumentError.new('block must be given') unless block
40
+
41
+ @browser&.with_playwright_page(&block)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -406,8 +406,11 @@ module Capybara
406
406
 
407
407
  MODIFIERS = {
408
408
  alt: 'Alt',
409
+ ctrl: 'Control',
409
410
  control: 'Control',
410
411
  meta: 'Meta',
412
+ command: 'Meta',
413
+ cmd: 'Meta',
411
414
  shift: 'Shift',
412
415
  }.freeze
413
416
 
@@ -440,8 +443,11 @@ module Capybara
440
443
  class SendKeys
441
444
  MODIFIERS = {
442
445
  alt: 'Alt',
446
+ ctrl: 'Control',
443
447
  control: 'Control',
444
448
  meta: 'Meta',
449
+ command: 'Meta',
450
+ cmd: 'Meta',
445
451
  shift: 'Shift',
446
452
  }.freeze
447
453
 
@@ -576,7 +582,7 @@ module Capybara
576
582
 
577
583
  class PressKey
578
584
  def initialize(key:, modifiers:)
579
- puts "PressKey: key=#{key} modifiers: #{modifiers}"
585
+ # puts "PressKey: key=#{key} modifiers: #{modifiers}"
580
586
  if modifiers.empty?
581
587
  @key = key
582
588
  else
@@ -605,7 +611,77 @@ module Capybara
605
611
  end
606
612
 
607
613
  def drag_to(element, **options)
608
- raise NotImplementedError
614
+ DragTo.new(@page, @element, element.element, options).execute
615
+ end
616
+
617
+ class DragTo
618
+ MODIFIERS = {
619
+ alt: 'Alt',
620
+ ctrl: 'Control',
621
+ control: 'Control',
622
+ meta: 'Meta',
623
+ command: 'Meta',
624
+ cmd: 'Meta',
625
+ shift: 'Shift',
626
+ }.freeze
627
+
628
+ # @param page [Playwright::Page]
629
+ # @param source [Playwright::ElementHandle]
630
+ # @param target [Playwright::ElementHandle]
631
+ def initialize(page, source, target, options)
632
+ @page = page
633
+ @source = source
634
+ @target = target
635
+ @options = options
636
+ end
637
+
638
+ def execute
639
+ @source.scroll_into_view_if_needed
640
+
641
+ # down
642
+ position_from = center_of(@source)
643
+ @page.mouse.move(*position_from)
644
+ @page.mouse.down
645
+
646
+ @target.scroll_into_view_if_needed
647
+
648
+ # move and up
649
+ sleep_delay
650
+ position_to = center_of(@target)
651
+ with_key_pressing(drop_modifiers) do
652
+ @page.mouse.move(*position_to, steps: 6)
653
+ sleep_delay
654
+ @page.mouse.up
655
+ end
656
+ sleep_delay
657
+ end
658
+
659
+ # @param element [Playwright::ElementHandle]
660
+ private def center_of(element)
661
+ box = element.bounding_box
662
+ [box["x"] + box["width"] / 2, box["y"] + box["height"] / 2]
663
+ end
664
+
665
+ private def with_key_pressing(keys, &block)
666
+ keys.each { |key| @page.keyboard.down(key) }
667
+ block.call
668
+ keys.each { |key| @page.keyboard.up(key) }
669
+ end
670
+
671
+ # @returns Array<String>
672
+ private def drop_modifiers
673
+ return [] unless @options[:drop_modifiers]
674
+
675
+ Array(@options[:drop_modifiers]).map do |key|
676
+ MODIFIERS[key.to_sym] or raise ArgumentError.new("Unknown modifier key: #{key}")
677
+ end
678
+ end
679
+
680
+ private def sleep_delay
681
+ return unless @options[:delay]
682
+
683
+ sleep @options[:delay]
684
+ end
609
685
  end
610
686
 
611
687
  def drop(*args)
@@ -696,6 +772,8 @@ module Capybara
696
772
  end
697
773
 
698
774
  def visible?
775
+ assert_element_not_stale
776
+
699
777
  # if an area element, check visibility of relevant image
700
778
  @element.evaluate(<<~JAVASCRIPT)
701
779
  function(el) {
@@ -732,10 +810,14 @@ module Capybara
732
810
  end
733
811
 
734
812
  def checked?
813
+ assert_element_not_stale
814
+
735
815
  @element.evaluate('el => !!el.checked')
736
816
  end
737
817
 
738
818
  def selected?
819
+ assert_element_not_stale
820
+
739
821
  @element.evaluate('el => !!el.selected')
740
822
  end
741
823
 
@@ -4,7 +4,11 @@ module Capybara
4
4
  module Playwright
5
5
  module PageExtension
6
6
  def initialize(*args, **kwargs)
7
- super
7
+ if kwargs.empty?
8
+ super(*args)
9
+ else
10
+ super(*args, **kwargs)
11
+ end
8
12
  capybara_initialize
9
13
  end
10
14
 
@@ -0,0 +1,40 @@
1
+ module Capybara
2
+ module Playwright
3
+ module TmpdirOwner
4
+ require 'tmpdir'
5
+
6
+ def tmpdir
7
+ return @tmpdir if @tmpdir
8
+
9
+ dir = Dir.mktmpdir
10
+ ObjectSpace.define_finalizer(self, TmpdirRemover.new(dir))
11
+ @tmpdir = dir
12
+ end
13
+
14
+ def remove_tmpdir
15
+ if @tmpdir
16
+ FileUtils.remove_entry(@tmpdir, true)
17
+ ObjectSpace.undefine_finalizer(self)
18
+ @tmpdir = nil
19
+ end
20
+ end
21
+
22
+ class TmpdirRemover
23
+ def initialize(tmpdir)
24
+ @pid = Process.pid
25
+ @tmpdir = tmpdir
26
+ end
27
+
28
+ def call(*args)
29
+ return if @pid != Process.pid
30
+
31
+ begin
32
+ FileUtils.remove_entry(@tmpdir, true)
33
+ rescue => err
34
+ $stderr.puts err
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Playwright
5
- VERSION = '0.0.0-r1'
5
+ VERSION = '0.1.4'
6
6
  end
7
7
  end
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.0.0.pre.r1
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-17 00:00:00.000000000 Z
11
+ date: 2021-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara
@@ -30,14 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.5.9
33
+ version: 0.6.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 0.5.9
40
+ version: 0.6.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: allure-rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -108,20 +122,6 @@ dependencies:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
124
  version: 3.10.0
111
- - !ruby/object:Gem::Dependency
112
- name: rubocop
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: 1.7.0
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: 1.7.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rubocop-rspec
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -186,9 +186,11 @@ files:
186
186
  - lib/capybara/playwright/browser_options.rb
187
187
  - lib/capybara/playwright/dialog_event_handler.rb
188
188
  - lib/capybara/playwright/driver.rb
189
+ - lib/capybara/playwright/driver_extension.rb
189
190
  - lib/capybara/playwright/node.rb
190
191
  - lib/capybara/playwright/page.rb
191
192
  - lib/capybara/playwright/page_options.rb
193
+ - lib/capybara/playwright/tmpdir_owner.rb
192
194
  - lib/capybara/playwright/version.rb
193
195
  homepage: https://github.com/YusukeIwaki/capybara-playwright-driver
194
196
  licenses:
@@ -202,14 +204,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
202
204
  requirements:
203
205
  - - ">="
204
206
  - !ruby/object:Gem::Version
205
- version: '0'
207
+ version: '2.4'
206
208
  required_rubygems_version: !ruby/object:Gem::Requirement
207
209
  requirements:
208
- - - ">"
210
+ - - ">="
209
211
  - !ruby/object:Gem::Version
210
- version: 1.3.1
212
+ version: '0'
211
213
  requirements: []
212
- rubygems_version: 3.1.4
214
+ rubygems_version: 3.1.6
213
215
  signing_key:
214
216
  specification_version: 4
215
217
  summary: Playwright driver for Capybara