capybara-webkit 1.14.0 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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