rutl 0.3.0 → 0.4.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.
@@ -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