capybara-webkit 1.14.0 → 1.15.0

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.
@@ -26,15 +26,23 @@ module Capybara
26
26
  discover_port
27
27
  discover_pid
28
28
  forward_output_in_background_thread
29
+ register_shutdown_hook
29
30
  end
30
31
 
31
32
  private
32
33
 
33
34
  def open_pipe
34
- @pipe_stdin,
35
- @pipe_stdout,
36
- @pipe_stderr,
37
- @wait_thr = Open3.popen3(SERVER_PATH)
35
+ if IO.respond_to?(:popen4)
36
+ @pid,
37
+ @pipe_stdin,
38
+ @pipe_stdout,
39
+ @pipe_stderr = IO.popen4(SERVER_PATH)
40
+ else
41
+ @pipe_stdin,
42
+ @pipe_stdout,
43
+ @pipe_stderr,
44
+ @wait_thr = Open3.popen3(SERVER_PATH)
45
+ end
38
46
  end
39
47
 
40
48
  def discover_port
@@ -58,7 +66,7 @@ module Capybara
58
66
  end
59
67
 
60
68
  def discover_pid
61
- @pid = @wait_thr[:pid]
69
+ @pid ||= @wait_thr[:pid]
62
70
  end
63
71
 
64
72
  def forward_output_in_background_thread
@@ -67,6 +75,31 @@ module Capybara
67
75
  IO.copy_stream(@pipe_stderr, @output_target) if @output_target
68
76
  end
69
77
  end
78
+
79
+ def register_shutdown_hook
80
+ @owner_pid = Process.pid
81
+ at_exit do
82
+ if Process.pid == @owner_pid
83
+ kill_process
84
+ end
85
+ end
86
+ end
87
+
88
+ def kill_process
89
+ if @pid
90
+ if RUBY_PLATFORM =~ /mingw32/
91
+ Process.kill(9, @pid)
92
+ else
93
+ Process.kill("INT", @pid)
94
+ end
95
+ Process.wait(@pid)
96
+ @wait_thr.join if @wait_thr
97
+ end
98
+ rescue Errno::ESRCH, Errno::ECHILD
99
+ # This just means that the webkit_server process has already ended
100
+ ensure
101
+ @pid = @wait_thr = nil
102
+ end
70
103
  end
71
104
  end
72
105
  end
@@ -1,7 +1,7 @@
1
1
  module Capybara
2
2
  module Driver
3
3
  class Webkit
4
- VERSION = "1.14.0".freeze
4
+ VERSION = "1.15.0".freeze
5
5
  end
6
6
  end
7
7
  end
@@ -1,3 +1,4 @@
1
+ require 'etc'
1
2
  require "fileutils"
2
3
  require "rbconfig"
3
4
  require "shellwords"
@@ -90,7 +91,8 @@ module CapybaraWebkitBuilder
90
91
  end
91
92
 
92
93
  def make(target = "")
93
- env_hide("CDPATH") { sh("#{make_bin} #{target}") }
94
+ job_count = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 1
95
+ env_hide('CDPATH') { sh("#{make_bin} #{target} --jobs=#{job_count}") }
94
96
  end
95
97
 
96
98
  def env_hide(name)
@@ -554,7 +554,13 @@ describe Capybara::Webkit::Driver do
554
554
 
555
555
  it "evaluates Javascript and returns an object" do
556
556
  result = driver.evaluate_script(%<({ 'one' : 1 })>)
557
- expect(result).to eq 'one' => 1
557
+ expect(result).to eq "one" => 1
558
+ end
559
+
560
+ it "evaluate Javascript and returns an object when the original was readonly" do
561
+ result = driver.evaluate_script(%<window.getComputedStyle(document.getElementById('greeting'))>)
562
+ expect(result).to be_a Hash
563
+ expect(result["zIndex"]).to eq "auto"
558
564
  end
559
565
 
560
566
  it "evaluates Javascript and returns true" do
@@ -577,6 +583,27 @@ describe Capybara::Webkit::Driver do
577
583
  expect(result).to eq [1, 2]
578
584
  end
579
585
 
586
+ it "evaluates asynchronous JS which isn't" do
587
+ result = driver.evaluate_async_script("arguments[0](4)")
588
+ expect(result).to eq 4
589
+ end
590
+
591
+ it "evaluates asynchronous JS" do
592
+ result = driver.evaluate_async_script("setTimeout(function(callback){ callback('jaguar') }, 100, arguments[0])")
593
+ expect(result).to eq "jaguar"
594
+ end
595
+
596
+ it "evaluates asynchronous JS and returns an object" do
597
+ result = driver.evaluate_async_script(%<setTimeout(function(callback){ callback({ 'one' : 1 }) }, 100, arguments[0])>)
598
+ expect(result).to eq "one" => 1
599
+ end
600
+
601
+ it "evaluates asynchronous JS and returns an object when the original was readonly" do
602
+ result = driver.evaluate_async_script(%<setTimeout(function(callback){ callback(window.getComputedStyle(document.getElementById('greeting'))) }, 100, arguments[0])>)
603
+ expect(result).to be_a Hash
604
+ expect(result["zIndex"]).to eq "auto"
605
+ end
606
+
580
607
  it "executes Javascript" do
581
608
  driver.execute_script(%<document.getElementById('greeting').innerHTML = 'yo'>)
582
609
  expect(driver.find_xpath("//p[contains(., 'yo')]")).not_to be_empty
@@ -605,6 +632,12 @@ describe Capybara::Webkit::Driver do
605
632
  expect(driver.find_xpath("//p[@id='greeting'][contains(., 'new content')]")).not_to be_empty
606
633
  end
607
634
 
635
+ it "passes elements as arguments to asynchronous script" do
636
+ greeting = driver.find_xpath("//p[@id='greeting']").first
637
+ result = driver.evaluate_async_script(%<arguments[2]([arguments[1], arguments[0]])>, greeting, "a string")
638
+ expect(result).to eq ["a string", greeting]
639
+ end
640
+
608
641
  it "passes arguments to evaaluated Javascript" do
609
642
  expect(driver.evaluate_script(%<arguments[0]>, 3)).to eq 3
610
643
  end
@@ -1406,7 +1439,7 @@ describe Capybara::Webkit::Driver do
1406
1439
  end
1407
1440
 
1408
1441
  it "does not modify the selected attribute of a new selection" do
1409
- expect(monkey_option['selected']).to be_nil
1442
+ expect(driver.evaluate_script("arguments[0].getAttribute('selected')", monkey_option)).to be_nil
1410
1443
  end
1411
1444
 
1412
1445
  it "returns the old value when a reset button is clicked" do
@@ -1586,35 +1619,49 @@ describe Capybara::Webkit::Driver do
1586
1619
  <textarea class="watch"></textarea>
1587
1620
  <input class="watch" type="checkbox"/>
1588
1621
  <input class="watch" type="radio"/>
1622
+ <select id="single" class="watch">
1623
+ <option>Single Option 1</option>
1624
+ <option>Single Option 2</option>
1625
+ </select>
1626
+ <select id="multiple" class="watch" multiple="multiple">
1627
+ <option class="watch" selected="selected">Multiple Option 1</option>
1628
+ <option class="watch" selected="selected">Multiple Option 2</option>
1629
+ <option class="watch">Multiple Option 3</option>
1630
+ <optgroup>
1631
+ <option class="watch">Multiple Option 4</option>
1632
+ </optgroup>
1633
+ </select>
1589
1634
  </form>
1590
1635
  <ul id="events"></ul>
1591
1636
  <script type="text/javascript">
1592
1637
  var events = document.getElementById("events");
1593
1638
  var recordEvent = function (event) {
1594
1639
  var element = document.createElement("li");
1595
- element.innerHTML = event.type;
1640
+ var event_description = "";
1641
+ if (event.target.id)
1642
+ event_description += event.target.id + '.';
1643
+ event_description += event.type;
1644
+ element.innerHTML = event_description;
1596
1645
  events.appendChild(element);
1597
1646
  };
1598
1647
 
1599
1648
  var elements = document.getElementsByClassName("watch");
1649
+ var watched_events = ["focus", "keydown", "keypress", "keyup", "input", "change",
1650
+ "blur", "mousedown", "mouseup", "click"];
1600
1651
  for (var i = 0; i < elements.length; i++) {
1601
- var element = elements[i];
1602
- element.addEventListener("focus", recordEvent);
1603
- element.addEventListener("keydown", recordEvent);
1604
- element.addEventListener("keypress", recordEvent);
1605
- element.addEventListener("keyup", recordEvent);
1606
- element.addEventListener("input", recordEvent);
1607
- element.addEventListener("change", recordEvent);
1608
- element.addEventListener("blur", recordEvent);
1609
- element.addEventListener("mousedown", recordEvent);
1610
- element.addEventListener("mouseup", recordEvent);
1611
- element.addEventListener("click", recordEvent);
1652
+ for (var j = 0; j < watched_events.length; j++) {
1653
+ elements[i].addEventListener(watched_events[j], recordEvent);
1654
+ }
1612
1655
  }
1613
1656
  </script>
1614
1657
  </body></html>
1615
1658
  HTML
1616
1659
  end
1617
1660
 
1661
+ def logged_events
1662
+ driver.find_xpath("//li").map(&:visible_text)
1663
+ end
1664
+
1618
1665
  before { visit("/") }
1619
1666
 
1620
1667
  let(:newtext) { '12345' }
@@ -1631,29 +1678,49 @@ describe Capybara::Webkit::Driver do
1631
1678
  it "triggers text input events on inputs of type #{field_type}" do
1632
1679
  driver.find_xpath("//input[@type='#{field_type}']").first.set(newtext)
1633
1680
  driver.find_xpath("//body").first.click
1634
- expect(driver.find_xpath("//li").map(&:visible_text)).to eq textevents
1681
+ expect(logged_events).to eq textevents
1635
1682
  end
1636
1683
  end
1637
1684
 
1638
1685
  it "triggers events for cleared inputs" do
1639
1686
  driver.find_xpath("//input[@type='text']").first.set('')
1640
1687
  driver.find_xpath("//body").first.click
1641
- expect(driver.find_xpath("//li").map(&:visible_text)).to include('change')
1688
+ expect(logged_events).to include("change")
1642
1689
  end
1643
1690
 
1644
1691
  it "triggers textarea input events" do
1645
1692
  driver.find_xpath("//textarea").first.set(newtext)
1646
- expect(driver.find_xpath("//li").map(&:visible_text)).to eq keyevents
1693
+ expect(logged_events).to eq keyevents
1647
1694
  end
1648
1695
 
1649
1696
  it "triggers radio input events" do
1650
1697
  driver.find_xpath("//input[@type='radio']").first.set(true)
1651
- expect(driver.find_xpath("//li").map(&:visible_text)).to eq %w(mousedown focus mouseup change click)
1698
+ expect(logged_events).to eq %w(mousedown focus mouseup change click)
1652
1699
  end
1653
1700
 
1654
1701
  it "triggers checkbox events" do
1655
1702
  driver.find_xpath("//input[@type='checkbox']").first.set(true)
1656
- expect(driver.find_xpath("//li").map(&:visible_text)).to eq %w(mousedown focus mouseup change click)
1703
+ expect(logged_events).to eq %w(mousedown focus mouseup change click)
1704
+ end
1705
+
1706
+ it "triggers select events" do
1707
+ driver.find_xpath("//option[text() = 'Single Option 2']").first.select_option
1708
+ expect(logged_events).to eq %w[single.mousedown single.focus single.input single.change single.mouseup single.click]
1709
+ end
1710
+
1711
+ it "triggers correct unselect events for multiple selects" do
1712
+ driver.find_xpath("//option[text() = 'Multiple Option 2']").first.unselect_option
1713
+ expect(logged_events).to eq %w[multiple.mousedown multiple.focus multiple.input multiple.change multiple.mouseup multiple.click]
1714
+ end
1715
+
1716
+ it "triggers correct unselect events for multiple selects when already unselected" do
1717
+ driver.find_xpath("//option[text() = 'Multiple Option 3']").first.unselect_option
1718
+ expect(logged_events).to eq %w[multiple.mousedown multiple.focus multiple.input multiple.mouseup multiple.click]
1719
+ end
1720
+
1721
+ it "triggers correct select events for multiple selects when additional option is selected" do
1722
+ driver.find_xpath("//option[text() = 'Multiple Option 4']").first.select_option
1723
+ expect(logged_events).to eq %w[multiple.mousedown multiple.focus multiple.input multiple.change multiple.mouseup multiple.click]
1657
1724
  end
1658
1725
  end
1659
1726
 
@@ -1687,12 +1754,22 @@ describe Capybara::Webkit::Driver do
1687
1754
  <option value="1" id="option-1" selected="selected">one</option>
1688
1755
  <option value="2" id="option-2">two</option>
1689
1756
  </select>
1757
+ <select id="change_select_in_optgroup" name="change_select_in_optgroup">
1758
+ <optgroup>
1759
+ <option value="1" id="option-in-optgroup-1" selected="selected">one</option>
1760
+ <option value="2" id="option-in-optgroup-2">two</option>
1761
+ </optgroup>
1762
+ </select>
1690
1763
  </form>
1691
1764
  <script type="text/javascript">
1692
1765
  document.getElementById("change_select").
1693
1766
  addEventListener("change", function () {
1694
1767
  this.className = "triggered";
1695
1768
  });
1769
+ document.getElementById("change_select_in_optgroup").
1770
+ addEventListener("change", function () {
1771
+ this.className = "triggered";
1772
+ });
1696
1773
  document.getElementById("change").
1697
1774
  addEventListener("change", function () {
1698
1775
  this.className = "triggered";
@@ -1754,12 +1831,21 @@ describe Capybara::Webkit::Driver do
1754
1831
  end
1755
1832
 
1756
1833
  it "fires a change on select" do
1757
- select = driver.find_xpath("//select").first
1834
+ select = driver.find_xpath("//select[@id='change_select']").first
1758
1835
  expect(select.value).to eq "1"
1759
1836
  option = driver.find_xpath("//option[@id='option-2']").first
1760
1837
  option.select_option
1761
1838
  expect(select.value).to eq "2"
1762
- expect(driver.find_xpath("//select[@class='triggered']")).not_to be_empty
1839
+ expect(driver.find_xpath("//select[@id='change_select'][@class='triggered']")).not_to be_empty
1840
+ end
1841
+
1842
+ it "fires a change on select when using an optgroup" do
1843
+ select = driver.find_xpath("//select[@id='change_select_in_optgroup']").first
1844
+ expect(select.value).to eq "1"
1845
+ option = driver.find_xpath("//option[@id='option-in-optgroup-2']").first
1846
+ option.select_option
1847
+ expect(select.value).to eq "2"
1848
+ expect(driver.find_xpath("//select[@id='change_select_in_optgroup'][@class='triggered']")).not_to be_empty
1763
1849
  end
1764
1850
 
1765
1851
  it "fires drag events" do
@@ -2017,14 +2103,17 @@ describe Capybara::Webkit::Driver do
2017
2103
  end
2018
2104
 
2019
2105
  it "ignores custom fonts" do
2106
+ skip "font replacement not needed for QT >= 5" unless driver.version =~ /Qt: 4/
2020
2107
  expect(font_family).to eq "Arial"
2021
2108
  end
2022
2109
 
2023
2110
  it "ignores custom fonts before an element" do
2111
+ skip "font replacement not needed for QT >= 5" unless driver.version =~ /Qt: 4/
2024
2112
  expect(font_family).to eq "Arial"
2025
2113
  end
2026
2114
 
2027
2115
  it "ignores custom fonts after an element" do
2116
+ skip "font replacement not needed for QT >= 5" unless driver.version =~ /Qt: 4/
2028
2117
  expect(font_family).to eq "Arial"
2029
2118
  end
2030
2119
  end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+ # require 'selenium-webdriver'
3
+
4
+ RSpec.describe Capybara::Webkit::Driver do
5
+ it "should exit with a zero exit status" do
6
+ browser = Capybara::Webkit::Driver.new(TestApp).browser
7
+ expect(true).to eq(true)
8
+ end
9
+ end
@@ -337,6 +337,33 @@ describe Capybara::Session do
337
337
  end
338
338
  end
339
339
 
340
+ context "text" do
341
+ before(:all) do
342
+ @app = Class.new(ExampleApp) do
343
+ get "/" do
344
+ <<-HTML
345
+ <!DOCTYPE html>
346
+ <html>
347
+ <head></head>
348
+ <body>
349
+ <form>
350
+ This is my form
351
+ <input name="type"/>
352
+ <input name="tagName"/>
353
+ </form>
354
+ </body>
355
+ </html>
356
+ HTML
357
+ end
358
+ end
359
+ end
360
+
361
+ it "gets a forms text when inputs have conflicting names" do
362
+ subject.visit("/")
363
+ expect(subject.find(:css, "form").text).to eq("This is my form")
364
+ end
365
+ end
366
+
340
367
  context 'click tests' do
341
368
  before(:all) do
342
369
  @app = Class.new(ExampleApp) do
@@ -551,4 +578,46 @@ describe Capybara::Session do
551
578
  expect(session).to have_text('Hello')
552
579
  end
553
580
  end
581
+
582
+ if Capybara.respond_to?(:threadsafe)
583
+ context "threadsafe/per-session config mode" do
584
+ before do
585
+ Capybara::SpecHelper.reset_threadsafe(true, subject)
586
+ end
587
+
588
+ after do
589
+ Capybara::SpecHelper.reset_threadsafe(false, subject)
590
+ end
591
+
592
+ it "can allow reload in one session but not in another" do
593
+ session1, session2 = 2.times.collect do
594
+ session_for_app do
595
+ get '/' do
596
+ <<-HTML
597
+ <html>
598
+ <div id="parent">
599
+ <p id="removeMe">Hello</p>
600
+ </div>
601
+ </html>
602
+ HTML
603
+ end
604
+ end
605
+ end
606
+
607
+ session1.config.automatic_reload = false
608
+ session2.config.automatic_reload = true
609
+
610
+ node1, node2 = [session1, session2].map do |session|
611
+ session.visit('/')
612
+
613
+ node = session.find(:xpath, "//p[@id='removeMe']")
614
+ session.execute_script("document.getElementById('parent').innerHTML = 'Magic'")
615
+ node
616
+ end
617
+
618
+ expect(node1.text).to eq 'Hello'
619
+ expect{ node2.text }.to raise_error(Capybara::Webkit::NodeNotAttachedError)
620
+ end
621
+ end
622
+ end
554
623
  end
@@ -0,0 +1,90 @@
1
+ # -*- encoding: UTF-8 -*-
2
+
3
+ require "spec_helper"
4
+ require "capybara/webkit/driver"
5
+ require "base64"
6
+ require "self_signed_ssl_cert"
7
+
8
+ describe Capybara::Webkit::Node do
9
+ include AppRunner
10
+
11
+ def visit(path, driver = self.driver)
12
+ driver.visit(url(path))
13
+ end
14
+
15
+ def url(path)
16
+ "#{AppRunner.app_host}#{path}"
17
+ end
18
+
19
+ context "html app" do
20
+ let(:driver) do
21
+ driver_for_html(<<-HTML)
22
+ <html>
23
+ <head>
24
+ <title>Hello HTML</title>
25
+ </head>
26
+ <body>
27
+ <form action="/" method="GET">
28
+ <input type="text" name="foo" value="bar"/>
29
+ <input type="checkbox" name="checkedbox" value="1" checked="checked"/>
30
+ <input type="checkbox" name="uncheckedbox" value="2"/>
31
+ <input type="checkbox" name="falsecheckedbox" value="3" checked="false"/>
32
+ <input type="text" name="styled" style="font-size: 150%;"/>
33
+ </form>
34
+
35
+ <div id="visibility_wrapper" style="visibility: hidden">
36
+ <div id="hidden">Hidden</div>
37
+ <div id="visible" style="visibility: visible">Visibile</div>
38
+ <div style="visibility: visible">
39
+ <div id="nested_visible">Nested Visibile</div>
40
+ </div>
41
+ </div>
42
+
43
+ <div id="display_none" style="display: none">
44
+ <div id="not_displayed" style="visibility: visible; display: block;">Should not be displayed</div>
45
+ </div>
46
+ </body>
47
+ </html>
48
+ HTML
49
+ end
50
+
51
+ before { visit("/") }
52
+
53
+ context "Node#[]" do
54
+ it "gets boolean properties" do
55
+ box1 = driver.find_css('input[name="checkedbox"]').first
56
+ box2 = driver.find_css('input[name="uncheckedbox"]').first
57
+ box3 = driver.find_css('input[name="falsecheckedbox"]').first
58
+ expect(box1["checked"]).to eq true
59
+ expect(box2["checked"]).to eq false
60
+ expect(box3["checked"]).to eq true
61
+ box1.set(false)
62
+ expect(box1["checked"]).to eq false
63
+ end
64
+
65
+ it "prefers property over attribute" do
66
+ input = driver.find_css('input[name="foo"]').first
67
+ expect(input["value"]).to eq "bar"
68
+ input.set("new value")
69
+ expect(input["value"]).to eq "new value"
70
+ end
71
+
72
+ it "returns attribute when property is an object" do
73
+ input = driver.find_css('input[name="styled"]').first
74
+ expect(input["style"]).to eq "font-size: 150%;"
75
+ end
76
+ end
77
+
78
+ context "Node#visible" do
79
+ it "correctly analyzes visibility CSS" do
80
+ expect(driver.find_css('#hidden').first.visible?).to be false
81
+ expect(driver.find_css('#visible').first.visible?).to be true
82
+ expect(driver.find_css('#nested_visible').first.visible?).to be true
83
+ end
84
+
85
+ it "correctly analyzes display: none CSS" do
86
+ expect(driver.find_css('#not_displayed').first.visible?).to be false
87
+ end
88
+ end
89
+ end
90
+ end