tapestry 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.hound.yml +8 -2
- data/.travis.yml +4 -0
- data/README.md +9 -0
- data/Rakefile +10 -0
- data/examples/tapestry-data-set.rb +24 -0
- data/examples/tapestry-events.rb +42 -0
- data/examples/tapestry-factory.rb +56 -0
- data/examples/tapestry-simple.rb +63 -5
- data/lib/tapestry.rb +40 -2
- data/lib/tapestry/attribute.rb +40 -0
- data/lib/tapestry/element.rb +30 -10
- data/lib/tapestry/errors.rb +16 -0
- data/lib/tapestry/extensions/data_setter.rb +106 -0
- data/lib/tapestry/extensions/dom_observer.js +78 -0
- data/lib/tapestry/extensions/dom_observer.rb +74 -0
- data/lib/tapestry/extensions/watir_elements.rb +16 -0
- data/lib/tapestry/factory.rb +92 -0
- data/lib/tapestry/interface.rb +203 -0
- data/lib/tapestry/ready.rb +93 -0
- data/lib/tapestry/situation.rb +77 -0
- data/lib/tapestry/version.rb +22 -1
- data/tapestry.gemspec +1 -0
- metadata +30 -3
@@ -0,0 +1,16 @@
|
|
1
|
+
module Tapestry
|
2
|
+
module Errors
|
3
|
+
NoUrlForDefinition = Class.new(StandardError)
|
4
|
+
NoUrlMatchForDefinition = Class.new(StandardError)
|
5
|
+
NoTitleForDefinition = Class.new(StandardError)
|
6
|
+
NoUrlMatchPossible = Class.new(StandardError)
|
7
|
+
PageNotValidatedError = Class.new(StandardError)
|
8
|
+
NoBlockForWhenReady = Class.new(StandardError)
|
9
|
+
|
10
|
+
class PageURLFromFactoryNotVerified < StandardError
|
11
|
+
def message
|
12
|
+
'The page URL was not verified during a factory setup.'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class Object
|
2
|
+
# This method is necessary to dynamically chain method calls. The reason
|
3
|
+
# this is necessary the data setter initially has no idea of the actual
|
4
|
+
# object it's going to be dealing with, particularly because part of its
|
5
|
+
# job is to find that object and map a data string to it. Not only this,
|
6
|
+
# but that element will have been called on a specific instance of a
|
7
|
+
# interface class. With the example provide in the comments below for the
|
8
|
+
# `using` method, the following would be the case:
|
9
|
+
#
|
10
|
+
# method_chain: warp_factor.set
|
11
|
+
# o (object): <WarpTravel:0x007f8b23224218>
|
12
|
+
# m (method): warp_factor
|
13
|
+
# data: 1
|
14
|
+
#
|
15
|
+
# Thus what you end up with is:
|
16
|
+
#
|
17
|
+
# <WarpTravel:0x007f8b23224218>.warp_factor.set 1
|
18
|
+
def chain(method_chain, data = nil)
|
19
|
+
return self if method_chain.empty?
|
20
|
+
method_chain.split('.').inject(self) do |o, m|
|
21
|
+
if data.nil?
|
22
|
+
o.send(m.intern)
|
23
|
+
else
|
24
|
+
o.send(m.intern, data)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Tapestry
|
31
|
+
module DataSetter
|
32
|
+
# The `using` method tells Tapestry to match up whatever data is passed
|
33
|
+
# in via the action with element definitions. If those elements are found,
|
34
|
+
# they will be populated with the specified data. Consider the following:
|
35
|
+
#
|
36
|
+
# class WarpTravel
|
37
|
+
# include Tapestry
|
38
|
+
#
|
39
|
+
# text_field :warp_factor, id: 'warpInput'
|
40
|
+
# text_field :velocity, id: 'velocityInput'
|
41
|
+
# text_field :distance, id: 'distInput'
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Assuming an instance of this class called `page`, you could do the
|
45
|
+
# following:
|
46
|
+
#
|
47
|
+
# page.using_data(warp_factor: 1, velocity: 1, distance: 4.3)
|
48
|
+
#
|
49
|
+
# This is based on conventions. The idea is that element definitions are
|
50
|
+
# written in the form of "snake case" -- meaning, underscores between
|
51
|
+
# each separate word. In the above example, "warp_factor: 1" would be
|
52
|
+
# matched to the `warp_factor` element and the value used for that
|
53
|
+
# element would be "1". The default operation for a text field is to
|
54
|
+
# enter the value in. It is also possible to use strings:
|
55
|
+
#
|
56
|
+
# page.using_data("warp factor": 1, velocity: 1, distance: 4.3)
|
57
|
+
#
|
58
|
+
# Here "warp factor" would be converted to "warp_factor".
|
59
|
+
def using(data)
|
60
|
+
data.each do |key, value|
|
61
|
+
use_data_with(key, value) if object_enabled_for(key)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
alias using_data using
|
66
|
+
alias use_data using
|
67
|
+
alias using_values using
|
68
|
+
alias use_values using
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# This is the method that is delegated to in order to make sure that
|
73
|
+
# elements are interacted with appropriately. This will in turn delegate
|
74
|
+
# to `set_and_select` and `check_and_uncheck`, which determines what
|
75
|
+
# actions are viable based on the type of element that is being dealt
|
76
|
+
# with. These aspects are what tie this particular implementation to
|
77
|
+
# Watir.
|
78
|
+
def use_data_with(key, value)
|
79
|
+
element = send(key.to_s.tr(' ', '_'))
|
80
|
+
set_and_select(key, element, value)
|
81
|
+
check_and_uncheck(key, element, value)
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_and_select(key, element, value)
|
85
|
+
key = key.to_s.tr(' ', '_')
|
86
|
+
chain("#{key}.set", value) if element.class == Watir::TextField
|
87
|
+
chain("#{key}.set") if element.class == Watir::Radio
|
88
|
+
chain("#{key}.select", value) if element.class == Watir::Select
|
89
|
+
end
|
90
|
+
|
91
|
+
def check_and_uncheck(key, element, value)
|
92
|
+
key = key.to_s.tr(' ', '_')
|
93
|
+
return chain("#{key}.check") if element.class == Watir::CheckBox && value
|
94
|
+
chain("#{key}.uncheck") if element.class == Watir::CheckBox
|
95
|
+
end
|
96
|
+
|
97
|
+
# This is a sanity check method to make sure that whatever element is
|
98
|
+
# being used as part of the data setting, it exists in the DOM, is
|
99
|
+
# visible (meaning, display is not 'none'), and is capable of accepting
|
100
|
+
# input, thus being enabled.
|
101
|
+
def object_enabled_for(key)
|
102
|
+
web_element = send(key.to_s.tr(' ', '_'))
|
103
|
+
web_element.enabled? && web_element.visible?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
/*
|
2
|
+
This functionality will only work for browsers that support it.
|
3
|
+
See: http://caniuse.com/#feat=mutationobserver
|
4
|
+
*/
|
5
|
+
|
6
|
+
// WebDriver arguments, which are passed to the MutationObserver.
|
7
|
+
var element = arguments[0];
|
8
|
+
var delay = arguments[1] * 1000;
|
9
|
+
var callback = arguments[2];
|
10
|
+
|
11
|
+
/*
|
12
|
+
The two functions below are similar in what they are doing. Both are
|
13
|
+
disconneting the observer. Both are also invoking WebDriver's callback
|
14
|
+
function.
|
15
|
+
|
16
|
+
notStartedUpdating passes true to the callback, which indicates that the
|
17
|
+
DOM has not yet begun updating.
|
18
|
+
|
19
|
+
startedUpdating passes false to the callback, which indicates that the
|
20
|
+
DOM has begun updating.
|
21
|
+
|
22
|
+
The disconnect is important. You only want to be listening (observing) for the
|
23
|
+
period required, removing the listeners when done. Since there be many DOM
|
24
|
+
operations, you want to disconnet when there is interaction with the page by
|
25
|
+
the automated scripts.
|
26
|
+
|
27
|
+
When observing a node for changes, the callback will not be fired until the
|
28
|
+
DOM has finished changing. That is the only granularity that is required for
|
29
|
+
the Tapestry implementation. What specific events occurred is not important
|
30
|
+
because the goal is not to conditionally respond to them; rather just to know
|
31
|
+
when the process has completed.
|
32
|
+
*/
|
33
|
+
var notStartedUpdating = function() {
|
34
|
+
return setTimeout(function() {
|
35
|
+
observer.disconnect();
|
36
|
+
callback(true);
|
37
|
+
}, 1000);
|
38
|
+
};
|
39
|
+
|
40
|
+
var startedUpdating = function() {
|
41
|
+
clearTimeout(notStartedUpdating);
|
42
|
+
observer.disconnect();
|
43
|
+
callback(false);
|
44
|
+
};
|
45
|
+
|
46
|
+
/*
|
47
|
+
Mutation Observer
|
48
|
+
The W3C DOM4 specification initially introduced mutation observers as a
|
49
|
+
replacement for the deprecated mutation events.
|
50
|
+
|
51
|
+
The MutationObserver is a JavaScript native object that allows for observing
|
52
|
+
a change on any node-like DOM Element. "Mutation" means the addition or the
|
53
|
+
removal of a node as well as changes to the node's attribute and data.
|
54
|
+
|
55
|
+
The general approach is to create a MutationObserver object with a defined
|
56
|
+
callback function. The function will execute on every mutation observed by
|
57
|
+
the MutationObserver. The MutationObserver must be bound to a target, which
|
58
|
+
for Tapestry would mean the element whose context it is being called upon.
|
59
|
+
|
60
|
+
A MutationObserver can be provided with a set of options, which indicate
|
61
|
+
what kind of events should be observed.
|
62
|
+
|
63
|
+
The childList option checks for additions and removals of the target node's
|
64
|
+
child elements, including text nodes. This is basically looking for any
|
65
|
+
nodes added or removed from documentElement.
|
66
|
+
|
67
|
+
The subtree option checks for mutations to the target as well the target's
|
68
|
+
descendants. So that means every child node of documentElement.
|
69
|
+
|
70
|
+
The attribute option checks for mutations to the target's attributes.
|
71
|
+
|
72
|
+
The characterData option checks for mutations to the target's data.
|
73
|
+
*/
|
74
|
+
var observer = new MutationObserver(startedUpdating);
|
75
|
+
var config = { attributes: true, childList: true, characterData: true, subtree: true };
|
76
|
+
observer.observe(element, config);
|
77
|
+
|
78
|
+
var notStartedUpdating = notStartedUpdating();
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Watir
|
2
|
+
class Element
|
3
|
+
OBSERVER_FILE = "/dom_observer.js".freeze
|
4
|
+
DOM_OBSERVER = File.read("#{File.dirname(__FILE__)}#{OBSERVER_FILE}").freeze
|
5
|
+
|
6
|
+
# This method makes a call to `execute_async_script` which means that the
|
7
|
+
# DOM observer script must explicitly signal that it is finished by
|
8
|
+
# invoking a callback. In this case, the callback is nothing more than
|
9
|
+
# a delay. The delay is being used to allow the DOM to be updated before
|
10
|
+
# script actions continue.
|
11
|
+
#
|
12
|
+
# The method returns true if the DOM has been changed within the element
|
13
|
+
# context, while false means that the DOM has not yet finished changing.
|
14
|
+
# Note the wording: "has not finished changing." It's known that the DOM
|
15
|
+
# is changing because the observer has recognized that. So the question
|
16
|
+
# this method is helping to answer is "has it finished?"
|
17
|
+
#
|
18
|
+
# Consider the following element definition:
|
19
|
+
#
|
20
|
+
# p :page_list, id: 'navlist'
|
21
|
+
#
|
22
|
+
# You could then do this:
|
23
|
+
#
|
24
|
+
# page_list.dom_updated?
|
25
|
+
#
|
26
|
+
# That would return true if the DOM content for page_list has finished
|
27
|
+
# updating. If the DOM was in the process of being updated, this would
|
28
|
+
# return false. You could also do this:
|
29
|
+
#
|
30
|
+
# page_list.wait_until(&:dom_updated?).click
|
31
|
+
#
|
32
|
+
# This will use Watir's wait until functionality to wait for the DOM to
|
33
|
+
# be updated within the context of the element. Note that the "&:" is
|
34
|
+
# that the object that `dom_updated?` is being called on (in this case
|
35
|
+
# `page_list`) substitutes the ampersand. You can also structure it like
|
36
|
+
# this:
|
37
|
+
#
|
38
|
+
# page_list.wait_until do |element|
|
39
|
+
# element.dom_updated?
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# The default delay of waiting for the DOM to start updating is 1.1
|
43
|
+
# second. However, you can pass a delay value when you call the method
|
44
|
+
# to set your own value, which can be useful for particular sensitivities
|
45
|
+
# in the application you are testing.
|
46
|
+
def dom_updated?(delay: 1.1)
|
47
|
+
driver.manage.timeouts.script_timeout = delay + 1
|
48
|
+
driver.execute_async_script(DOM_OBSERVER, wd, delay)
|
49
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError
|
50
|
+
# This situation can occur when the DOM changes between two calls to
|
51
|
+
# some element or aspect of the page. In this case, we are expecting
|
52
|
+
# the DOM to be different so what's being handled here are those hard
|
53
|
+
# to anticipate race conditions when "weird things happen" and DOM
|
54
|
+
# updating plus script execution get interleaved.
|
55
|
+
retry
|
56
|
+
rescue Selenium::WebDriver::Error::JavascriptError => e
|
57
|
+
# This situation can occur if the script execution has started before
|
58
|
+
# a new page is fully loaded. The specific error being checked for here
|
59
|
+
# is one that occurs when a new page is loaded as that page is trying
|
60
|
+
# to execute a JavaScript function.
|
61
|
+
retry if e.message.include?('document unloaded while waiting for result')
|
62
|
+
raise
|
63
|
+
ensure
|
64
|
+
# Note that this setting here means any user-defined timeout would
|
65
|
+
# effectively be overwritten.
|
66
|
+
driver.manage.timeouts.script_timeout = 1
|
67
|
+
end
|
68
|
+
|
69
|
+
alias dom_has_updated? dom_updated?
|
70
|
+
alias dom_has_changed? dom_updated?
|
71
|
+
alias when_dom_updated dom_updated?
|
72
|
+
alias when_dom_changed dom_updated?
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Tapestry
|
2
|
+
module Factory
|
3
|
+
# Creates a definition context for actions and establishes the context
|
4
|
+
# for execution. Given an interface definition for a page like this:
|
5
|
+
#
|
6
|
+
# class TestPage
|
7
|
+
# include Tapestry
|
8
|
+
#
|
9
|
+
# url_is "http://localhost:9292"
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# You can do the following:
|
13
|
+
#
|
14
|
+
# on_view(TestPage)
|
15
|
+
#
|
16
|
+
# Note that the actual factory creation is handled by `on`. This method
|
17
|
+
# exists as a way to differentiate when an interface needs to be
|
18
|
+
# visited.
|
19
|
+
def on_view(definition, &block)
|
20
|
+
on(definition, true, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias on_visit on_view
|
24
|
+
|
25
|
+
# Creates a definition context for actions. If an existing context
|
26
|
+
# exists, that context will be re-used. You can use this simply to keep
|
27
|
+
# the context for a script clear. For example, say you have the following
|
28
|
+
# interface definitions for pages:
|
29
|
+
#
|
30
|
+
# class Home
|
31
|
+
# include Tapestry
|
32
|
+
# url_is "http://localhost:9292"
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# class Navigation
|
36
|
+
# include Tapestry
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# You could then do this:
|
40
|
+
#
|
41
|
+
# on_view(Home)
|
42
|
+
# on(Navigation)
|
43
|
+
#
|
44
|
+
# The Home definition needs the url_is attribute in order for the on_view
|
45
|
+
# factory to work. But Navigation does not because the `on` method is not
|
46
|
+
# attempting to visit, simply to reference. Note that you can use `on`
|
47
|
+
# to visit, just by doing this:
|
48
|
+
#
|
49
|
+
# on(Home, true)
|
50
|
+
def on(definition, visit = false, &block)
|
51
|
+
unless @context.is_a?(definition)
|
52
|
+
@context = definition.new(@browser) if @browser
|
53
|
+
@context = definition.new unless @browser
|
54
|
+
@context.visit if visit
|
55
|
+
end
|
56
|
+
|
57
|
+
verify_page(@context)
|
58
|
+
|
59
|
+
yield @context if block
|
60
|
+
@context
|
61
|
+
end
|
62
|
+
|
63
|
+
# Creates a definition context for actions. Unlike the `on` factory, the
|
64
|
+
# `on_new` factory will always create a new context and will never re-use
|
65
|
+
# an existing one. The reason for using this factory might be that you
|
66
|
+
# are on the same page, but a given action has changed it so much that
|
67
|
+
# you want to reference it as a new version of that page, meaning a new
|
68
|
+
# context is established.
|
69
|
+
#
|
70
|
+
# It's doubtful that you will want to rely on this factory too much.
|
71
|
+
def on_new(definition, &block)
|
72
|
+
@context = nil
|
73
|
+
on(definition, &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
alias on_page on
|
77
|
+
alias while_on on
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# This method is used to provide a means for checking if a page has been
|
82
|
+
# navigated to correctly as part of a context. This is useful because
|
83
|
+
# the context signature should remain highly readable, and checks for
|
84
|
+
# whether a given page has been reached would make the context definition
|
85
|
+
# look sloppy.
|
86
|
+
def verify_page(context)
|
87
|
+
return if context.url_match_attribute.nil?
|
88
|
+
return if context.has_correct_url?
|
89
|
+
raise Tapestry::Errors::PageURLFromFactoryNotVerified
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require "tapestry/situation"
|
2
|
+
|
3
|
+
module Tapestry
|
4
|
+
module Interface
|
5
|
+
module Page
|
6
|
+
include Situation
|
7
|
+
|
8
|
+
# The `visit` method provides navigation to a specific page by passing
|
9
|
+
# in the URL. If no URL is passed in, this method will attempt to use
|
10
|
+
# the `url_is` attribute from the interface it is being called on.
|
11
|
+
def visit(url = nil)
|
12
|
+
no_url_provided if url.nil? && url_attribute.nil?
|
13
|
+
@browser.goto(url) unless url.nil?
|
14
|
+
@browser.goto(url_attribute) if url.nil?
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
alias view visit
|
19
|
+
alias navigate_to visit
|
20
|
+
alias goto visit
|
21
|
+
alias perform visit
|
22
|
+
|
23
|
+
# A call to `url_attribute` returns what the value of the `url_is`
|
24
|
+
# attribute is for the given interface. It's important to note that
|
25
|
+
# this is not grabbing the URL that is displayed in the browser;
|
26
|
+
# rather it's the one declared in the interface, if any.
|
27
|
+
def url_attribute
|
28
|
+
self.class.url_attribute
|
29
|
+
end
|
30
|
+
|
31
|
+
# A call to `url_match_attribute` returns what the value of the
|
32
|
+
# `url_matches` attribute is for the given interface. It's important
|
33
|
+
# to note that the URL matching mechanism is effectively a regular
|
34
|
+
# expression check.
|
35
|
+
def url_match_attribute
|
36
|
+
value = self.class.url_match_attribute
|
37
|
+
return if value.nil?
|
38
|
+
value = Regexp.new(value) unless value.is_a?(Regexp)
|
39
|
+
value
|
40
|
+
end
|
41
|
+
|
42
|
+
# A call to `title_attribute` returns what the value of the `title_is`
|
43
|
+
# attribute is for the given definition. It's important to note that
|
44
|
+
# this is not grabbing the title that is displayed in the browser;
|
45
|
+
# rather it's the one declared in the interface, if any.
|
46
|
+
def title_attribute
|
47
|
+
self.class.title_attribute
|
48
|
+
end
|
49
|
+
|
50
|
+
# A call to `has_correct_url?`returns true or false if the actual URL
|
51
|
+
# found in the browser matches the `url_matches` assertion. This is
|
52
|
+
# important to note. It's not using the `url_is` attribute nor the URL
|
53
|
+
# displayed in the browser. It's using the `url_matches` attribute.
|
54
|
+
def has_correct_url?
|
55
|
+
if url_attribute.nil? && url_match_attribute.nil?
|
56
|
+
no_url_match_is_possible
|
57
|
+
end
|
58
|
+
!(url =~ url_match_attribute).nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
alias displayed? has_correct_url?
|
62
|
+
|
63
|
+
# A call to `has_correct_title?` returns true or false if the actual
|
64
|
+
# title of the current page in the browser matches the `title_is`
|
65
|
+
# attribute. Notice that this check is done as part of a match rather
|
66
|
+
# than a direct check. This allows for regular expressions to be used.
|
67
|
+
def has_correct_title?
|
68
|
+
no_title_is_provided if title_attribute.nil?
|
69
|
+
!title.match(title_attribute).nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
# A call to `secure?` returns true if the page is secure and false
|
73
|
+
# otherwise. This is a simple check that looks for whether or not the
|
74
|
+
# current URL begins with 'https'.
|
75
|
+
def secure?
|
76
|
+
!url.match(/^https/).nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
# A call to `url` returns the actual URL of the page that is displayed
|
80
|
+
# in the browser.
|
81
|
+
def url
|
82
|
+
@browser.url
|
83
|
+
end
|
84
|
+
|
85
|
+
alias page_url url
|
86
|
+
alias current_url url
|
87
|
+
|
88
|
+
# A call to `title` returns the actual title of the page that is
|
89
|
+
# displayed in the browser.
|
90
|
+
def title
|
91
|
+
@browser.title
|
92
|
+
end
|
93
|
+
|
94
|
+
alias page_title title
|
95
|
+
|
96
|
+
# A call to `markup` returns all markup on a page. Generally you don't
|
97
|
+
# just want the entire markup but rather want to parse the output of
|
98
|
+
# the `markup` call.
|
99
|
+
def markup
|
100
|
+
browser.html
|
101
|
+
end
|
102
|
+
|
103
|
+
alias html markup
|
104
|
+
|
105
|
+
# A call to `text` returns all text on a page. Note that this is text
|
106
|
+
# that is taken out of the markup context. It is unlikely you will just
|
107
|
+
# want the entire text but rather want to parse the output of the
|
108
|
+
# `text` call.
|
109
|
+
def text
|
110
|
+
browser.text
|
111
|
+
end
|
112
|
+
|
113
|
+
alias page_text text
|
114
|
+
|
115
|
+
# This method sends a standard "browser refresh" message to the browser.
|
116
|
+
def refresh
|
117
|
+
browser.refresh
|
118
|
+
end
|
119
|
+
|
120
|
+
alias refresh_page refresh
|
121
|
+
|
122
|
+
# This method provides a call to the browser window to resize that
|
123
|
+
# window to the specified width and height values.
|
124
|
+
def resize(width, height)
|
125
|
+
browser.window.resize_to(width, height)
|
126
|
+
end
|
127
|
+
|
128
|
+
alias resize_to resize
|
129
|
+
|
130
|
+
# This method provides a call to the browser window to move the
|
131
|
+
# window to the specified x and y screen coordinates.
|
132
|
+
def move_to(x, y)
|
133
|
+
browser.window.move_to(x, y)
|
134
|
+
end
|
135
|
+
|
136
|
+
# This method provides a call to the synchronous `execute_script`
|
137
|
+
# action on the browser, passing in JavaScript that you want to have
|
138
|
+
# executed against the current page. For example:
|
139
|
+
#
|
140
|
+
# result = page.run_script("alert('Tapestry ran a script.')")
|
141
|
+
#
|
142
|
+
# You can also run full JavaScript snippets.
|
143
|
+
#
|
144
|
+
# script = <<-JS
|
145
|
+
# return arguments[0].innerHTML
|
146
|
+
# JS
|
147
|
+
#
|
148
|
+
# page.run_script(script, page.account)
|
149
|
+
#
|
150
|
+
# Here you pass two arguments to `run_script`. One is the script itself
|
151
|
+
# and the other are some arguments that you want to pass as part of
|
152
|
+
# of the execution. In this case, an element definition (`account`) is
|
153
|
+
# being passed in.
|
154
|
+
def run_script(script, *args)
|
155
|
+
browser.execute_script(script, *args)
|
156
|
+
end
|
157
|
+
|
158
|
+
alias execute_script run_script
|
159
|
+
|
160
|
+
# A call to `screenshot` saves a screenshot of the current browser
|
161
|
+
# page. Note that this will grab the entire browser page, even portions
|
162
|
+
# of it that are off panel and need to be scrolled to. You can pass in
|
163
|
+
# the path and filename of the image that you want the screenshot
|
164
|
+
# saved to.
|
165
|
+
def screenshot(file)
|
166
|
+
browser.save.screenshot(file)
|
167
|
+
end
|
168
|
+
|
169
|
+
alias save_screenshot screenshot
|
170
|
+
|
171
|
+
# A call to `screen_width` returns the width of the browser screen as
|
172
|
+
# reported by the browser API, using a JavaScript call to the `screen`
|
173
|
+
# object.
|
174
|
+
def screen_width
|
175
|
+
run_script("return screen.width;")
|
176
|
+
end
|
177
|
+
|
178
|
+
# A call to `screen_height` returns the height of the browser screen as
|
179
|
+
# reported by the browser API, using a JavaScript call to the `screen`
|
180
|
+
# object.
|
181
|
+
def screen_height
|
182
|
+
run_script("return screen.height;")
|
183
|
+
end
|
184
|
+
|
185
|
+
# A call to `get_cookie` allows you to specify a particular cookie, by
|
186
|
+
# name, and return the information specified in the cookie.
|
187
|
+
def get_cookie(name)
|
188
|
+
browser.cookies.to_a.each do |cookie|
|
189
|
+
return cookie[:value] if cookie[:name] == name
|
190
|
+
end
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
|
194
|
+
# A call to `clear_cookies` removes all the cookies from the current
|
195
|
+
# instance of the browser that is being controlled by WebDriver.
|
196
|
+
def clear_cookies
|
197
|
+
browser.cookies.clear
|
198
|
+
end
|
199
|
+
|
200
|
+
alias remove_cookies clear_cookies
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|