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

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: 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