rutl 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,111 +0,0 @@
1
- require 'rutl/interface/elements'
2
- require 'rutl/driver/null_driver'
3
-
4
- #
5
- # Base page class. It's used to call the magical method_messing
6
- # stuff to parse the page object files into actual objects.
7
- #
8
- class BasePage
9
- # BUGBUG: Kludgy. What do I really want to do here?
10
- # Make it easy to define a page's default url and
11
- # also matchers for page urls for pages with variable urls?
12
- # rubocop:disable Style/TrivialAccessors
13
- def self.url
14
- @url
15
- end
16
-
17
- def url
18
- self.class.url
19
- end
20
- # rubocop:enable Style/TrivialAccessors
21
-
22
- @@loaded_pages = []
23
-
24
- def initialize(interface)
25
- @interface = interface
26
- # Dirty trick because we're loading all of page classes from files and then
27
- # initializing them, calling their layout methods to do magic.
28
- # The base_page class knows what pages are loaded.
29
- return if @@loaded_pages.include?(self.class)
30
- layout
31
- @@loaded_pages << self.class
32
- end
33
-
34
- def go_to_here
35
- # Ovveride this in base page to have something more
36
- # complicated than this.
37
- @interface.driver.navigate.to(url)
38
- end
39
-
40
- # Written by Browser and only used internally.
41
- attr_writer :interface
42
-
43
- def loaded?(driver)
44
- url == driver.current_url
45
- end
46
-
47
- # Dynamically add a method, :<name> (or :<name>= if setter)
48
- # to the current class where that method creates an instance
49
- # of klass.
50
- # context is an ElementContext
51
- #
52
- # As it is, this seems silly to break into pieces for Rubocop.
53
- # rubocop:disable Metrics/MethodLength
54
- def add_method(context:, klass:, name:, setter: false)
55
- name = "#{name}_#{klass.downcase}"
56
- constant = Module.const_get(klass.capitalize)
57
- self.class.class_exec do
58
- if setter
59
- define_method("#{name}=") do |value|
60
- constant.new(context, value)
61
- end
62
- else
63
- define_method(name) do
64
- constant.new(context)
65
- end
66
- end
67
- end
68
- end
69
- # rubocop:enable Metrics/MethodLength
70
-
71
- # This creates a new element instance whenever it's called.
72
- # Because of that we can't keep state in any element objects.
73
- # That seems like a good thing, actually.
74
- # Called by layout method on pages.
75
- #
76
- # Hard to make shorter.
77
- # rubocop:disable Metrics/MethodLength
78
- def method_missing(element, *args, &_block)
79
- name, selectors, rest = args
80
- context = ElementContext.new(destinations: rest,
81
- interface: @interface,
82
- selectors: selectors)
83
- case element
84
- when /button/, /checkbox/, /link/
85
- add_method(name: name, context: context, klass: element)
86
- when /text/
87
- add_method(name: name, context: context, klass: element)
88
- add_method(name: name, context: context, klass: element, setter: true)
89
- else
90
- # TODO: replace with a super call. This is useful for debugging for now.
91
- raise "#{element} NOT FOUND WITH ARGS #{args}!!!"
92
- end
93
- end
94
- # rubocop:enable Metrics/MethodLength
95
-
96
- def respond_to_missing?(*args)
97
- # Is this right at all???
98
- case args[0].to_s
99
- when /button/, /checkbox/, /link/, /text/,
100
- 'driver', 'url', 'children', 'loaded?'
101
- true
102
- when 'ok_link'
103
- raise 'OK LINK WAY DOWN HERE IN BASE PAGE!!!'
104
- else
105
- # I think it's good to raise but change the message.
106
- raise 'Drew, you hit this most often when checking current page ' \
107
- "rather than current page class:\n\n #{args}"
108
- # I think I want to raise instead of returningn false.
109
- end
110
- end
111
- end
@@ -1,38 +0,0 @@
1
- require 'rutl/driver/null_driver_page_element'
2
-
3
- #
4
- # This is at a peer level to the webdrivers but it's for a fake brwoser.
5
- #
6
- class NullDriver
7
- attr_accessor :context
8
-
9
- def initialize(context)
10
- raise 'no context' unless context.is_a?(ElementContext)
11
- @context = context
12
- end
13
-
14
- def find_element(type, location)
15
- # Return a new one of these so that it can be clicked ar written
16
- # to or whatever.
17
- context = ElementContext.new(interface: @context.interface)
18
- NullDriverPageElement.new(context, type, location)
19
- end
20
-
21
- # Cheap way to handle browser.navigate.to(url)
22
- # TODO: Until I care about the url and then I should ????
23
- def navigate
24
- context = ElementContext.new(interface: @context.interface)
25
- NullDriver.new(context)
26
- end
27
-
28
- def to(url)
29
- result = @context.interface.find_page(url)
30
- @context.interface.current_page = result
31
- result.url
32
- end
33
-
34
- def quit
35
- # Clean out the @@variables.
36
- NullDriverPageElement.clear_variables
37
- end
38
- end
@@ -1,51 +0,0 @@
1
- require 'rutl/interface/elements/element_context'
2
-
3
- #
4
- # This fakes all page elements when used with the null driver.
5
- # It's a dirty way to avoid modeling all of what a driver talks to.
6
- #
7
- class NullDriverPageElement
8
- attr_accessor :context
9
-
10
- def self.clear_variables
11
- @@variables = {}
12
- end
13
-
14
- def initialize(context, _type, location)
15
- @@variables ||= {}
16
- @context = context
17
- @location = location
18
- end
19
-
20
- # @@string is a class variable because this framework creates new instances
21
- # of each element every time it accesses them. This is good behavior by
22
- # default because pages could change underneath us.
23
- # For text fields in the null browser, though, we want to preserve the values
24
- # across calls, letting us write and then read.
25
- def send_keys(string)
26
- init = @@variables[@location] || ''
27
- @@variables[@location] = init + string
28
- end
29
-
30
- def attribute(attr)
31
- case attr.to_sym
32
- when :value
33
- @@variables[@location] || ''
34
- else
35
- raise ArgumentError, "Attribute unknown: #{attr}"
36
- end
37
- end
38
-
39
- def clear
40
- @@variables[@location] = ''
41
- end
42
-
43
- def this_css
44
- self
45
- end
46
-
47
- def click
48
- # nop
49
- # Called by ClickToChangeStateMixin like Selenium driver.click
50
- end
51
- end
@@ -1,5 +0,0 @@
1
- require 'rutl/interface/elements/button'
2
- require 'rutl/interface/elements/checkbox'
3
- require 'rutl/interface/elements/link'
4
- require 'rutl/interface/elements/text'
5
- require 'rutl/interface/elements/element_context'
@@ -1,24 +0,0 @@
1
- #
2
- # Page elements. Base class.
3
- #
4
- class BaseElement
5
- attr_accessor :context
6
-
7
- def initialize(element_context)
8
- raise element_context.to_s unless element_context.is_a? ElementContext
9
- @context = element_context
10
- # Not sure why, but I'm seeing Chrome fail becase the context interface
11
- # passed in isn't the same as the browser's interface.
12
- # This only happens with click test cases, before the click, and
13
- # only if that case isn't run first.
14
- # The context we're passed is also an instance from as ChromeInterface,
15
- # but a different instance.
16
- #
17
- # Here's the kludge workaround line:
18
- @context.interface = $browser.interface
19
- end
20
-
21
- def this_css
22
- @context.find_element(:css)
23
- end
24
- end
@@ -1,10 +0,0 @@
1
- require 'rutl/interface/elements/base_element'
2
- require 'rutl/interface/elements/click_to_change_state_mixin'
3
-
4
- #
5
- # It's a button!
6
- #
7
- class Button < BaseElement
8
- include ClickToChangeStateMixin
9
- # def get, text - return button text; useful for text-changing buttons
10
- end
@@ -1,8 +0,0 @@
1
- require 'rutl/interface/elements/base_element'
2
-
3
- #
4
- # Yes, a checkbox.
5
- #
6
- class Checkbox < BaseElement
7
- # get and set? toggle?
8
- end
@@ -1,18 +0,0 @@
1
- #
2
- # Mix this in for things that change state when clicked.
3
- # The only things that wouldn't change state when clicked either
4
- # shouldn't be clicked or are just annoying.
5
- #
6
- module ClickToChangeStateMixin
7
- def click
8
- # Screenshot before clicking. Is this really necessary?
9
- @context.interface.camera.screenshot
10
- this_css.click
11
- # returns the page it found
12
- result = @context.interface.wait_for_transition(@context.destinations)
13
- # And after clicking and going to new state. This seems more needed
14
- # because we want to see where we went.
15
- @context.interface.camera.screenshot
16
- result
17
- end
18
- end
@@ -1,29 +0,0 @@
1
- #
2
- # The context passed around to all elements.
3
- # What they need to know outside of themselves to function.
4
- #
5
- class ElementContext
6
- attr_accessor :destinations
7
- attr_accessor :interface
8
- attr_accessor :selectors
9
-
10
- def initialize(destinations: nil, interface: nil, selectors: [])
11
- unless destinations.nil? || destinations.class == Array
12
- # Should check each destination to make sure it's a Page or a _____, too.
13
- raise 'destination must be an Array of destinations or nil.'
14
- end
15
- @destinations = destinations || []
16
- unless interface.nil? || interface.class.ancestors.include?(BaseInterface)
17
- raise "#{interface.class}: #{interface} must be a *Interface class."
18
- end
19
- @interface = interface
20
- @selectors = selectors
21
- end
22
-
23
- def find_element(type)
24
- # @interface.driver.find_element(type, @selectors[type])
25
- # Should be this, but apparently @interface.driver is being overwritten
26
- # (or not written to) and it doesn't work. Using $browser does. :-(
27
- $browser.interface.driver.find_element(type, @selectors[type])
28
- end
29
- end
@@ -1,11 +0,0 @@
1
- require 'rutl/interface/elements/base_element'
2
- require 'rutl/interface/elements/click_to_change_state_mixin'
3
-
4
- #
5
- # Link, of course.
6
- #
7
- class Link < BaseElement
8
- include ClickToChangeStateMixin
9
- # text, url - get what they say
10
- # should there be a 'get' - what would it get?
11
- end
@@ -1,66 +0,0 @@
1
- #
2
- # Implement String stuff in a mixin.
3
- # TODO: Not finished yet. Must be able to
4
- #
5
- module StringReaderWriterMixin
6
- # Override BaseElement's normal initialize method.
7
- def initialize(element_context, input_value = nil)
8
- raise element_context.to_s unless element_context.is_a? ElementContext
9
- @context = element_context
10
- set input_value unless input_value.nil?
11
- end
12
-
13
- # I could cut set() and only foo_text= if I change this.
14
- # The problem I'm running into is not having the driver in
15
- # base element to do this_css calls. So I have to change the way
16
- # drivers are passed into everything or initially have them everywhere,
17
- # which means rewriting chosen drivers or changing page load.
18
- # Ick.
19
- def set(string)
20
- clear
21
- this_css.send_keys(string)
22
- end
23
- alias text= set
24
- alias value= set
25
-
26
- def get
27
- this_css.attribute(:value)
28
- end
29
- alias text get
30
- alias value get
31
- alias to_s get
32
-
33
- def clear
34
- this_css.clear
35
- get
36
- end
37
-
38
- def eql?(other)
39
- other == get
40
- end
41
- alias == eql?
42
-
43
- def send_keys(string)
44
- this_css.send_keys(string)
45
- get
46
- end
47
-
48
- def method_missing(method, *args, &block)
49
- # RuboCop complains unless I fall back to super here
50
- # even though that's pretty meaningless. Oh, well, it's harmless.
51
- super unless get.respond_to?(method)
52
- if args.empty?
53
- get.send(method)
54
- else
55
- get.send(method, *args, &block)
56
- end
57
- end
58
-
59
- def respond_to_missing?(method, flag)
60
- get.respond_to?(method, flag)
61
- end
62
-
63
- #
64
- # TODO: Fall through to String methods?
65
- #
66
- end
@@ -1,10 +0,0 @@
1
- require 'rutl/interface/elements/base_element'
2
- require 'rutl/interface/elements/string_reader_writer_mixin.rb'
3
-
4
- #
5
- # I'm using the text element for all text-like things. Passowrds, too.
6
- # TODO: Also have a reader only class with StringReaderMixin for labels?
7
- #
8
- class Text < BaseElement
9
- include StringReaderWriterMixin
10
- end
@@ -1,71 +0,0 @@
1
- require 'fileutils'
2
- #
3
- # class to take photos of the screen (and diff them?)
4
- #
5
- class ScreenCam
6
- def guard
7
- # When running headless, Selenium seems not to drop screenshots.
8
- # So that makes this safe in places like Travis.
9
- #
10
- # We still need to guard against NullDriver or we'll to to screencap
11
- # it when we're running head-fully.
12
- #
13
- # Will there be others?
14
- @driver.is_a? NullDriver
15
- end
16
-
17
- def initialize(driver, base_name: '')
18
- @counter = 0
19
- @driver = driver
20
- return if guard
21
- @base_name = base_name
22
- @dir = File.join(RUTL::SCREENSHOTS, base_name)
23
- FileUtils.mkdir_p @dir
24
- end
25
-
26
- def shoot(path = nil)
27
- return if guard
28
- # Magic path is used for all auto-screenshots.
29
- name = path || magic_path
30
-
31
- FileUtils.mkdir_p @dir
32
- file = File.join(@dir, pathify(name))
33
- @driver.save_screenshot(file)
34
- end
35
- alias screenshot shoot
36
-
37
- def clean_dir(dir)
38
- FileUtils.rm_rf dir
39
- FileUtils.mkdir_p dir
40
- end
41
-
42
- def counter
43
- @counter += 1
44
- # In the unlikely even that we have > 9 screenshots in a test case,
45
- # format the counter to be two digits, zero padded.
46
- format('%02d', @counter)
47
- end
48
-
49
- def magic_path
50
- if defined? RSpec
51
- RSpec.current_example.metadata[:full_description].to_s
52
- else
53
- # TODO: The behavior for non-RSpec users is ugly and broken.
54
- # Each new test case will start taking numbered "auto-screenshot" pngs.
55
- # And the next test case will overwrite them. Even if they didn't
56
- # overwrite, I don't know how to correllate tests w/ scrrenshots. I'm
57
- # leaving this broken for now.
58
- # You can still tell it to take your own named screenshots whenever you
59
- # like, of course.
60
- 'auto-screenshot'
61
- end
62
- end
63
-
64
- def pathify(path)
65
- # Replace any of these with an underscore:
66
- # space, octothorpe, slash, backslash, colon, period
67
- name = path.gsub(%r{[ \#\/\\\:\.]}, '_')
68
- # Also insert a counter and make sure we end with .png.
69
- name.sub(/.png$/, '') + '_' + counter + '.png'
70
- end
71
- end