rutl 0.1.3 → 0.1.4
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.
- checksums.yaml +4 -4
- data/.gitignore +10 -9
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +24 -48
- data/README.md +1 -0
- data/Rakefile +13 -2
- data/lib/rutl/base_page.rb +49 -54
- data/lib/rutl/browser.rb +19 -22
- data/lib/rutl/driver/null_driver.rb +14 -10
- data/lib/rutl/driver/null_driver_page_element.rb +25 -14
- data/lib/rutl/interface/base_interface.rb +16 -37
- data/lib/rutl/interface/chrome_interface.rb +2 -4
- data/lib/rutl/interface/elements/base_element.rb +14 -18
- data/lib/rutl/interface/elements/click_to_change_state_mixin.rb +4 -3
- data/lib/rutl/interface/elements/element_context.rb +29 -0
- data/lib/rutl/interface/elements/string_reader_writer_mixin.rb +66 -0
- data/lib/rutl/interface/elements/text.rb +3 -23
- data/lib/rutl/interface/elements.rb +1 -0
- data/lib/rutl/interface/firefox_interface.rb +2 -4
- data/lib/rutl/interface/null_interface.rb +14 -12
- data/lib/rutl/utilities.rb +2 -1
- data/lib/rutl/version.rb +1 -1
- data/rutl.gemspec +4 -4
- metadata +20 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 44ba7a05dd473a09ac45ecafc02d9e11f0a74e26d0e8eb5909e315226066e0ca
|
|
4
|
+
data.tar.gz: 6a272e4fc1afb2d1111b90604ceb3443cc89fd49c703ff417ab56fd9f609573b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58227d7b2a1dd426ced4d42a826081ad71b638b6f212a16c5905ef57d0fb28ea4689b0240233dbfbb8d42701460c3bf91354cb088414693047836dcf30fb56ce
|
|
7
|
+
data.tar.gz: c4acf6148162ef28b3f7704ea6885a6acd81b9bfb0dbff26fb87a6294f943fa5a1e6bcdeb3aaa8668bf597fa0d452620a90587aadf6cd71ea3b4670f5f3281a1
|
data/.gitignore
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
/.bundle/
|
|
2
|
-
/.yardoc
|
|
3
|
-
/
|
|
4
|
-
/
|
|
5
|
-
/
|
|
6
|
-
/
|
|
7
|
-
/
|
|
8
|
-
/
|
|
9
|
-
/
|
|
1
|
+
/.bundle/
|
|
2
|
+
/.yardoc
|
|
3
|
+
/debug.log
|
|
4
|
+
/Gemfile.lock
|
|
5
|
+
/_yardoc/
|
|
6
|
+
/coverage/
|
|
7
|
+
/doc/
|
|
8
|
+
/pkg/
|
|
9
|
+
/spec/reports/
|
|
10
|
+
/tmp/
|
data/.rubocop.yml
CHANGED
|
@@ -6,7 +6,7 @@ require: rubocop-rspec
|
|
|
6
6
|
# There are also a bunch of warnigs spewed about the RSpec cops listed
|
|
7
7
|
# in the todo file so it looks like we're not playing well with rubocop-rspec
|
|
8
8
|
# either.
|
|
9
|
-
# Check this in for now and TODO: figure this out
|
|
9
|
+
# Check this in for now and TODO: figure this out.
|
|
10
10
|
require: drewcoo-cops
|
|
11
11
|
|
|
12
12
|
AllCops:
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,45 +1,20 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2018-
|
|
3
|
+
# on 2018-06-03 15:06:48 -0700 using RuboCop version 0.56.0.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
|
8
8
|
|
|
9
|
-
# Offense count: 1
|
|
10
|
-
Metrics/AbcSize:
|
|
11
|
-
Max: 16
|
|
12
|
-
|
|
13
9
|
# Offense count: 3
|
|
14
10
|
# Configuration parameters: CountComments, ExcludedMethods.
|
|
15
11
|
Metrics/BlockLength:
|
|
16
|
-
Max:
|
|
12
|
+
Max: 74
|
|
17
13
|
|
|
18
|
-
# Offense count:
|
|
14
|
+
# Offense count: 3
|
|
19
15
|
# Configuration parameters: CountComments.
|
|
20
16
|
Metrics/MethodLength:
|
|
21
|
-
Max:
|
|
22
|
-
|
|
23
|
-
# Offense count: 1
|
|
24
|
-
Naming/AccessorMethodName:
|
|
25
|
-
Exclude:
|
|
26
|
-
- 'lib/rutl/interface/null_interface.rb'
|
|
27
|
-
|
|
28
|
-
# Offense count: 1
|
|
29
|
-
RSpec/ExampleLength:
|
|
30
|
-
Exclude:
|
|
31
|
-
- 'spec/chrome_interface_spec.rb'
|
|
32
|
-
|
|
33
|
-
# Offense count: 2
|
|
34
|
-
RSpec/ExpectActual:
|
|
35
|
-
Exclude:
|
|
36
|
-
- 'spec/null_interface_spec.rb'
|
|
37
|
-
- 'spec/rutl_spec.rb'
|
|
38
|
-
|
|
39
|
-
# Offense count: 1
|
|
40
|
-
RSpec/MultipleExpectations:
|
|
41
|
-
Exclude:
|
|
42
|
-
- 'spec/chrome_interface_spec.rb'
|
|
17
|
+
Max: 13
|
|
43
18
|
|
|
44
19
|
# Offense count: 4
|
|
45
20
|
# Cop supports --auto-correct.
|
|
@@ -50,39 +25,34 @@ Style/BracesAroundHashParameters:
|
|
|
50
25
|
- 'spec/pages/internet_login_page.rb'
|
|
51
26
|
- 'spec/pages/page1.rb'
|
|
52
27
|
|
|
53
|
-
# Offense count:
|
|
28
|
+
# Offense count: 3
|
|
54
29
|
Style/ClassVars:
|
|
55
30
|
Exclude:
|
|
56
31
|
- 'lib/rutl/base_page.rb'
|
|
32
|
+
- 'lib/rutl/driver/null_driver_page_element.rb'
|
|
57
33
|
|
|
58
34
|
# Offense count: 4
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- 'lib/rutl/base_page.rb'
|
|
62
|
-
- 'lib/rutl/browser.rb'
|
|
63
|
-
- 'lib/rutl/interface/base_interface.rb'
|
|
64
|
-
- 'spec/spec_helper.rb'
|
|
65
|
-
|
|
66
|
-
# Offense count: 1
|
|
67
|
-
# Cop supports --auto-correct.
|
|
68
|
-
Style/PerlBackrefs:
|
|
35
|
+
# Configuration parameters: AllowedVariables.
|
|
36
|
+
Style/GlobalVars:
|
|
69
37
|
Exclude:
|
|
70
38
|
- 'lib/rutl/browser.rb'
|
|
39
|
+
- 'lib/rutl/interface/elements/base_element.rb'
|
|
40
|
+
- 'lib/rutl/interface/elements/element_context.rb'
|
|
41
|
+
- 'lib/rutl/interface/null_interface.rb'
|
|
71
42
|
|
|
72
43
|
# Offense count: 1
|
|
73
44
|
# Cop supports --auto-correct.
|
|
74
|
-
|
|
75
|
-
# SupportedStyles: implicit, explicit
|
|
76
|
-
Style/RescueStandardError:
|
|
45
|
+
Style/IfUnlessModifier:
|
|
77
46
|
Exclude:
|
|
78
|
-
- 'lib/rutl/
|
|
47
|
+
- 'lib/rutl/interface/elements/element_context.rb'
|
|
79
48
|
|
|
80
|
-
# Offense count:
|
|
81
|
-
|
|
82
|
-
# Configuration parameters: AllowIfMethodIsEmpty.
|
|
83
|
-
Style/SingleLineMethods:
|
|
49
|
+
# Offense count: 4
|
|
50
|
+
Style/MethodMissingSuper:
|
|
84
51
|
Exclude:
|
|
85
52
|
- 'lib/rutl/base_page.rb'
|
|
53
|
+
- 'lib/rutl/browser.rb'
|
|
54
|
+
- 'lib/rutl/interface/base_interface.rb'
|
|
55
|
+
- 'spec/spec_helper.rb'
|
|
86
56
|
|
|
87
57
|
# Offense count: 1
|
|
88
58
|
# Cop supports --auto-correct.
|
|
@@ -91,3 +61,9 @@ Style/SingleLineMethods:
|
|
|
91
61
|
Style/TrivialAccessors:
|
|
92
62
|
Exclude:
|
|
93
63
|
- 'lib/rutl/base_page.rb'
|
|
64
|
+
|
|
65
|
+
# Offense count: 15
|
|
66
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
|
67
|
+
# URISchemes: http, https
|
|
68
|
+
Metrics/LineLength:
|
|
69
|
+
Max: 223
|
data/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://circleci.com/gh/drewcoo/rutl)
|
|
5
5
|
[](https://coveralls.io/github/drewcoo/rutl?branch=master)
|
|
6
6
|
[](https://badge.fury.io/rb/rutl)
|
|
7
|
+
[](https://www.codacy.com/app/drewcoo/rutl?utm_source=github.com&utm_medium=referral&utm_content=drewcoo/rutl&utm_campaign=Badge_Grade)
|
|
7
8
|
|
|
8
9
|
This is the Ruby Ui Test Library, or RUTL. Not to be confused with [The Rutles](https://www.rutles.org/).
|
|
9
10
|
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
require 'bundler/gem_tasks'
|
|
2
2
|
require 'rspec/core/rake_task'
|
|
3
|
+
require 'rubocop/rake_task'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
RuboCop::RakeTask.new
|
|
5
6
|
|
|
6
|
-
task
|
|
7
|
+
task :fast_first do
|
|
8
|
+
Rake::Task['spec'].invoke('fast')
|
|
9
|
+
Rake::Task['spec'].reenable
|
|
10
|
+
Rake::Task['spec'].invoke('slow')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
RSpec::Core::RakeTask.new(:spec, :tag) do |t, args|
|
|
14
|
+
t.rspec_opts = ["--tag #{args[:tag]}"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
task default: :fast_first
|
data/lib/rutl/base_page.rb
CHANGED
|
@@ -9,73 +9,68 @@ class BasePage
|
|
|
9
9
|
# BUGBUG: Kludgy. What do I really want to do here?
|
|
10
10
|
# Make it easy to define a page's default url and
|
|
11
11
|
# also matchers for page urls for pages with variable urls?
|
|
12
|
-
def self.url
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
def self.url
|
|
13
|
+
@url
|
|
14
|
+
end
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
def url
|
|
17
|
+
self.class.url
|
|
18
|
+
end
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
@@loaded_pages = []
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
return if @@
|
|
22
|
+
def initialize(interface)
|
|
23
|
+
@interface = interface
|
|
24
|
+
# Dirty trick because we're loading all of page classes from files and then
|
|
25
|
+
# initializing them, calling their layout methods to do magic.
|
|
26
|
+
# The base_page class knows what pages are loaded.
|
|
27
|
+
return if @@loaded_pages.include?(self.class)
|
|
27
28
|
layout
|
|
28
|
-
@@
|
|
29
|
+
@@loaded_pages << self.class
|
|
29
30
|
end
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
# Written by Browser and only used internally.
|
|
33
|
+
attr_writer :interface
|
|
34
|
+
|
|
35
|
+
def loaded?(driver)
|
|
36
|
+
url == driver.current_url
|
|
33
37
|
end
|
|
34
38
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
39
|
+
# Dynamically add a method, :<name> (or :<name>= if setter)
|
|
40
|
+
# to the current class where that method creates an instance
|
|
41
|
+
# of klass.
|
|
42
|
+
# context is an ElementContext
|
|
43
|
+
def add_method(context:, klass:, name:, setter: false)
|
|
44
|
+
name = "#{name}_#{klass.downcase}"
|
|
45
|
+
constant = Module.const_get(klass.capitalize)
|
|
46
|
+
self.class.class_exec do
|
|
47
|
+
if setter
|
|
48
|
+
define_method("#{name}=") do |value|
|
|
49
|
+
constant.new(context, value)
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
define_method(name) do
|
|
53
|
+
constant.new(context)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
38
58
|
|
|
59
|
+
# This creates a new element instance whenever it's called.
|
|
60
|
+
# Because of that we can't keep state in any element objects.
|
|
61
|
+
# That seems like a good thing, actually.
|
|
39
62
|
# Called by layout method on pages.
|
|
40
63
|
def method_missing(element, *args, &_block)
|
|
41
|
-
name,
|
|
42
|
-
|
|
43
|
-
|
|
64
|
+
name, selectors, rest = args
|
|
65
|
+
context = ElementContext.new(destinations: rest,
|
|
66
|
+
interface: @interface,
|
|
67
|
+
selectors: selectors)
|
|
44
68
|
case element
|
|
45
69
|
when /button/, /checkbox/, /link/
|
|
46
|
-
|
|
47
|
-
# define_method(name) do
|
|
48
|
-
# Module.const_get(element.capitalize).new(selector, rest)
|
|
49
|
-
# end
|
|
50
|
-
# end
|
|
51
|
-
self.class.class_exec do
|
|
52
|
-
foo = Module.const_get(element.capitalize).new(selector, rest)
|
|
53
|
-
define_method(name) do
|
|
54
|
-
foo
|
|
55
|
-
end
|
|
56
|
-
end
|
|
70
|
+
add_method(name: name, context: context, klass: element)
|
|
57
71
|
when /text/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# define_method("_#{name}") do
|
|
61
|
-
# foo.get
|
|
62
|
-
# end
|
|
63
|
-
define_method(name) do
|
|
64
|
-
Module.const_get(element.capitalize).new(selector, rest)
|
|
65
|
-
end
|
|
66
|
-
# define_method(name.to_s) do
|
|
67
|
-
# foo.get
|
|
68
|
-
# end
|
|
69
|
-
# define_method((name + '=').to_s) do
|
|
70
|
-
# foo.set
|
|
71
|
-
# end
|
|
72
|
-
# foo.define_method(:get) do
|
|
73
|
-
# foo.get
|
|
74
|
-
# end
|
|
75
|
-
# foo.define_method(:set) do
|
|
76
|
-
# foo.set
|
|
77
|
-
# end
|
|
78
|
-
end
|
|
72
|
+
add_method(name: name, context: context, klass: element)
|
|
73
|
+
add_method(name: name, context: context, klass: element, setter: true)
|
|
79
74
|
else
|
|
80
75
|
# TODO: replace with a super call. This is useful for debugging for now.
|
|
81
76
|
raise "#{element} NOT FOUND WITH ARGS #{args}!!!"
|
|
@@ -93,7 +88,7 @@ class BasePage
|
|
|
93
88
|
else
|
|
94
89
|
# I think it's good to raise but change the message.
|
|
95
90
|
raise 'Drew, you hit this most often when checking current page ' \
|
|
96
|
-
|
|
91
|
+
"rather than current page class:\n\n #{args}"
|
|
97
92
|
# I think I want to raise instead of returningn false.
|
|
98
93
|
end
|
|
99
94
|
end
|
data/lib/rutl/browser.rb
CHANGED
|
@@ -9,26 +9,30 @@ require 'rutl/base_page'
|
|
|
9
9
|
class Browser
|
|
10
10
|
include Utilities
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
attr_reader :interface
|
|
13
|
+
|
|
14
|
+
def initialize(interface_type:, page_object_dir: 'spec/pages')
|
|
15
|
+
# This is kind of evil. Figure out how to ditch the $ variable.
|
|
16
|
+
$browser = self
|
|
17
|
+
@interface = nil
|
|
18
|
+
@interface = load_interface(interface_type)
|
|
19
|
+
@interface.pages = load_pages(dir: page_object_dir)
|
|
15
20
|
end
|
|
16
21
|
|
|
17
|
-
def load_interface(type
|
|
18
|
-
pages = @pages
|
|
22
|
+
def load_interface(type)
|
|
19
23
|
require "rutl/interface/#{type}_interface"
|
|
20
24
|
klass = "#{type.to_s.capitalize}Interface"
|
|
21
|
-
|
|
22
|
-
@interface.interface = @interface
|
|
25
|
+
Object.const_get(klass).new
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
# Ugly. Requires files for page objects. Returns array of class names to load.
|
|
26
|
-
def require_pages(
|
|
29
|
+
def require_pages(dir: 'spec/pages')
|
|
27
30
|
names = []
|
|
28
|
-
Dir["#{
|
|
31
|
+
Dir["#{dir}/*"].each do |file|
|
|
29
32
|
require "rutl/../../#{file}"
|
|
30
33
|
File.open(file).each do |line|
|
|
31
|
-
|
|
34
|
+
bingo = line.match(/class (.*) < BasePage/)
|
|
35
|
+
names << bingo[1] if bingo && bingo[1]
|
|
32
36
|
end
|
|
33
37
|
end
|
|
34
38
|
names
|
|
@@ -36,26 +40,19 @@ class Browser
|
|
|
36
40
|
|
|
37
41
|
def load_pages(*)
|
|
38
42
|
pages = []
|
|
39
|
-
|
|
40
|
-
names.each do |klass|
|
|
43
|
+
require_pages.each do |klass|
|
|
41
44
|
# Don't have @interface set yet.
|
|
42
45
|
# That would have been the param to new, :interface.
|
|
43
|
-
pages << Object.const_get(klass).new
|
|
46
|
+
pages << Object.const_get(klass).new(@interface)
|
|
44
47
|
end
|
|
45
48
|
pages
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
def method_missing(method, *args, &block)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
else
|
|
52
|
-
@interface.send(method, *args, &block)
|
|
53
|
-
end
|
|
54
|
-
if result.class == Array && (result[0].class.ancestors.include?(BasePage) ||
|
|
55
|
-
result[0].class == Exception)
|
|
56
|
-
wait_for_transition(result)
|
|
52
|
+
if args.empty?
|
|
53
|
+
@interface.send(method)
|
|
57
54
|
else
|
|
58
|
-
|
|
55
|
+
@interface.send(method, *args, &block)
|
|
59
56
|
end
|
|
60
57
|
end
|
|
61
58
|
|
|
@@ -4,31 +4,35 @@ require 'rutl/driver/null_driver_page_element'
|
|
|
4
4
|
# This is at a peer level to the webdrivers but it's for a fake brwoser.
|
|
5
5
|
#
|
|
6
6
|
class NullDriver
|
|
7
|
-
attr_accessor :
|
|
7
|
+
attr_accessor :context
|
|
8
|
+
|
|
9
|
+
def initialize(context)
|
|
10
|
+
raise 'no context' unless context.is_a?(ElementContext)
|
|
11
|
+
@context = context
|
|
12
|
+
end
|
|
8
13
|
|
|
9
14
|
def find_element(type, location)
|
|
10
15
|
# Return a new one of these so that it can be clicked ar written
|
|
11
16
|
# to or whatever.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
element
|
|
17
|
+
context = ElementContext.new(interface: @context.interface)
|
|
18
|
+
NullDriverPageElement.new(context, type, location)
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
# Cheap way to handle browser.navigate.to(url)
|
|
18
22
|
# TODO: Until I care about the url and then I should ????
|
|
19
23
|
def navigate
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
result
|
|
24
|
+
context = ElementContext.new(interface: @context.interface)
|
|
25
|
+
NullDriver.new(context)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def to(url)
|
|
26
|
-
result = @interface.find_page(url)
|
|
27
|
-
@interface.
|
|
29
|
+
result = @context.interface.find_page(url)
|
|
30
|
+
@context.interface.current_page = result
|
|
28
31
|
result.url
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def quit
|
|
32
|
-
|
|
35
|
+
# Clean out the @@variables.
|
|
36
|
+
NullDriverPageElement.clear_variables
|
|
33
37
|
end
|
|
34
38
|
end
|
|
@@ -1,40 +1,51 @@
|
|
|
1
|
+
require 'rutl/interface/elements/element_context'
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# This fakes all page elements when used with the null driver.
|
|
3
5
|
# It's a dirty way to avoid modeling all of what a driver talks to.
|
|
4
6
|
#
|
|
5
7
|
class NullDriverPageElement
|
|
6
|
-
attr_accessor :
|
|
7
|
-
attr_reader :selector_type, :selector
|
|
8
|
+
attr_accessor :context
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
def self.clear_variables
|
|
11
|
+
@@variables = {}
|
|
12
|
+
end
|
|
11
13
|
|
|
12
|
-
def initialize(
|
|
13
|
-
|
|
14
|
-
@
|
|
15
|
-
@
|
|
14
|
+
def initialize(context, _type, location)
|
|
15
|
+
@@variables ||= {}
|
|
16
|
+
@context = context
|
|
17
|
+
@location = location
|
|
16
18
|
end
|
|
17
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.
|
|
18
25
|
def send_keys(string)
|
|
19
|
-
|
|
26
|
+
init = @@variables[@location] || ''
|
|
27
|
+
@@variables[@location] = init + string
|
|
20
28
|
end
|
|
21
29
|
|
|
22
30
|
def attribute(attr)
|
|
23
31
|
case attr.to_sym
|
|
24
32
|
when :value
|
|
25
|
-
@
|
|
33
|
+
@@variables[@location]
|
|
26
34
|
else
|
|
27
35
|
raise ArgumentError, "Attribute unknown: #{attr}"
|
|
28
36
|
end
|
|
29
37
|
end
|
|
30
38
|
|
|
31
|
-
# Return simple strings for checks against the NullDriver instead of
|
|
32
|
-
# having to use some heavyweight UI.
|
|
33
39
|
def clear
|
|
34
|
-
'
|
|
40
|
+
@@variables[@location] = ''
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def this_css
|
|
44
|
+
self
|
|
35
45
|
end
|
|
36
46
|
|
|
37
47
|
def click
|
|
38
|
-
|
|
48
|
+
# nop
|
|
49
|
+
# Called by ClickToChangeStateMixin like Selenium driver.click
|
|
39
50
|
end
|
|
40
51
|
end
|
|
@@ -7,46 +7,27 @@ require 'rutl/utilities'
|
|
|
7
7
|
class BaseInterface
|
|
8
8
|
include Utilities
|
|
9
9
|
|
|
10
|
-
def current_url
|
|
11
|
-
current_page.url
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
attr_reader :pages
|
|
15
10
|
attr_accessor :driver
|
|
16
|
-
attr_accessor :
|
|
11
|
+
attr_accessor :pages
|
|
17
12
|
|
|
18
|
-
def initialize
|
|
19
|
-
@
|
|
20
|
-
raise 'Child classes must implement @driver.' unless defined? @driver
|
|
21
|
-
@pages.each { |p| p.driver = @driver }
|
|
13
|
+
def initialize
|
|
14
|
+
raise 'Child interface class must set @driver.' if @driver.nil?
|
|
22
15
|
end
|
|
23
16
|
|
|
24
|
-
def goto(
|
|
25
|
-
|
|
26
|
-
@driver.interface = @interface if @interface.class.to_s == 'NullInterface'
|
|
27
|
-
input = find_page(input) unless input.methods.include?(:url)
|
|
28
|
-
|
|
29
|
-
@driver.navigate.to input.url
|
|
17
|
+
def goto(page)
|
|
18
|
+
@driver.navigate.to find_page(page).url
|
|
30
19
|
end
|
|
31
20
|
|
|
32
21
|
def current_page
|
|
33
|
-
raise '
|
|
22
|
+
raise 'define in child classes'
|
|
34
23
|
end
|
|
35
24
|
|
|
36
25
|
def method_missing(method, *args, &block)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
end
|
|
42
|
-
raise interface.to_s if interface.nil?
|
|
43
|
-
raise result.to_s unless defined? result # result.interface
|
|
44
|
-
begin
|
|
45
|
-
result.interface = interface
|
|
46
|
-
rescue NoMethodError
|
|
47
|
-
raise NoMethodError, "METHOD NOT FOUND: #{method}"
|
|
26
|
+
if args.empty?
|
|
27
|
+
current_page.send(method)
|
|
28
|
+
else
|
|
29
|
+
current_page.send(method, *args, &block)
|
|
48
30
|
end
|
|
49
|
-
result
|
|
50
31
|
end
|
|
51
32
|
|
|
52
33
|
# TODO: Is this needed? I not only find the page but also make sure the
|
|
@@ -54,21 +35,18 @@ class BaseInterface
|
|
|
54
35
|
def find_state(target_states)
|
|
55
36
|
target_states.each do |state|
|
|
56
37
|
next unless state.url == current_page.url
|
|
57
|
-
page = find_page(state
|
|
58
|
-
return page if page.loaded?
|
|
38
|
+
page = find_page(state)
|
|
39
|
+
return page if page.loaded?(@driver)
|
|
59
40
|
end
|
|
60
|
-
raise current_page.to_s if current_page.class == InternetLoggedInPage
|
|
61
41
|
false
|
|
62
42
|
end
|
|
63
43
|
|
|
64
|
-
def find_page(page
|
|
44
|
+
def find_page(page)
|
|
65
45
|
@pages.each do |p|
|
|
66
|
-
# page is a Page class
|
|
67
46
|
return p if page?(page) && p.class == page
|
|
68
|
-
# or a String, possibly URL
|
|
69
47
|
return p if String == page.class && page == p.url
|
|
70
48
|
end
|
|
71
|
-
raise "Page \"#{page}\" not found in pages #{@pages}"
|
|
49
|
+
raise "Page \"#{page}\" not found in pages #{@pages}"
|
|
72
50
|
end
|
|
73
51
|
|
|
74
52
|
def wait_for_transition(target_states)
|
|
@@ -86,6 +64,7 @@ class BaseInterface
|
|
|
86
64
|
|
|
87
65
|
def quit
|
|
88
66
|
@driver.quit
|
|
89
|
-
|
|
67
|
+
# Maybe I'm reusing pages from @pages?
|
|
68
|
+
# @pages = []
|
|
90
69
|
end
|
|
91
70
|
end
|
|
@@ -4,10 +4,8 @@ require 'rutl/interface/base_interface'
|
|
|
4
4
|
#
|
|
5
5
|
# Small interface for Chrome browser.
|
|
6
6
|
#
|
|
7
|
-
# TODO: Probably the current_page() implementation should move up to base.
|
|
8
|
-
#
|
|
9
7
|
class ChromeInterface < BaseInterface
|
|
10
|
-
def initialize
|
|
8
|
+
def initialize
|
|
11
9
|
@logged_in = true
|
|
12
10
|
options = Selenium::WebDriver::Chrome::Options.new
|
|
13
11
|
options.add_argument('--ignore-certificate-errors')
|
|
@@ -25,7 +23,7 @@ class ChromeInterface < BaseInterface
|
|
|
25
23
|
|
|
26
24
|
def current_page
|
|
27
25
|
url = @driver.current_url
|
|
28
|
-
page = find_page(url
|
|
26
|
+
page = find_page(url)
|
|
29
27
|
raise "PAGE NOT FOUND: #{url}, PAGES: #{@pages}" unless page
|
|
30
28
|
page
|
|
31
29
|
end
|
|
@@ -2,27 +2,23 @@
|
|
|
2
2
|
# Page elements. Base class.
|
|
3
3
|
#
|
|
4
4
|
class BaseElement
|
|
5
|
-
attr_accessor :
|
|
6
|
-
def driver
|
|
7
|
-
@interface.driver
|
|
8
|
-
end
|
|
9
|
-
attr_accessor :selectors
|
|
10
|
-
attr_accessor :destinations
|
|
11
|
-
|
|
12
|
-
def initialize(selectors = {}, destinations = [])
|
|
13
|
-
@selectors = selectors
|
|
14
|
-
@destinations = destinations
|
|
15
|
-
end
|
|
5
|
+
attr_accessor :context
|
|
16
6
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
23
19
|
end
|
|
24
20
|
|
|
25
21
|
def this_css
|
|
26
|
-
|
|
22
|
+
@context.find_element(:css)
|
|
27
23
|
end
|
|
28
24
|
end
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
module ClickToChangeStateMixin
|
|
7
7
|
def click
|
|
8
8
|
this_css.click
|
|
9
|
-
|
|
10
|
-
# TODO:
|
|
11
|
-
|
|
9
|
+
|
|
10
|
+
# TODO: Is this part of the instance-stamping problem???
|
|
11
|
+
# returns the page it found
|
|
12
|
+
@context.interface.wait_for_transition(@context.destinations)
|
|
12
13
|
end
|
|
13
14
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
|
@@ -0,0 +1,66 @@
|
|
|
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,30 +1,10 @@
|
|
|
1
1
|
require 'rutl/interface/elements/base_element'
|
|
2
|
+
require 'rutl/interface/elements/string_reader_writer_mixin.rb'
|
|
2
3
|
|
|
3
4
|
#
|
|
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?
|
|
5
7
|
#
|
|
6
8
|
class Text < BaseElement
|
|
7
|
-
|
|
8
|
-
super
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def clear
|
|
12
|
-
this_css.clear
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def text
|
|
16
|
-
get
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def get
|
|
20
|
-
this_css.attribute(:value)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def text=(string)
|
|
24
|
-
set(string)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def set(string)
|
|
28
|
-
this_css.send_keys(string)
|
|
29
|
-
end
|
|
9
|
+
include StringReaderWriterMixin
|
|
30
10
|
end
|
|
@@ -4,10 +4,8 @@ require 'rutl/interface/base_interface'
|
|
|
4
4
|
#
|
|
5
5
|
# Small interface for Chrome browser.
|
|
6
6
|
#
|
|
7
|
-
# TODO: Probably the current_page() implementation should move up to base.
|
|
8
|
-
#
|
|
9
7
|
class FirefoxInterface < BaseInterface
|
|
10
|
-
def initialize
|
|
8
|
+
def initialize
|
|
11
9
|
@logged_in = true
|
|
12
10
|
options = Selenium::WebDriver::Firefox::Options.new
|
|
13
11
|
options.add_argument('--ignore-certificate-errors')
|
|
@@ -20,7 +18,7 @@ class FirefoxInterface < BaseInterface
|
|
|
20
18
|
|
|
21
19
|
def current_page
|
|
22
20
|
url = @driver.current_url
|
|
23
|
-
page = find_page(url
|
|
21
|
+
page = find_page(url)
|
|
24
22
|
raise "PAGE NOT FOUND: #{url}, PAGES: #{@pages}" unless page
|
|
25
23
|
page
|
|
26
24
|
end
|
|
@@ -4,26 +4,28 @@ require 'rutl/interface/base_interface'
|
|
|
4
4
|
# Interface-level code for fake browser.
|
|
5
5
|
#
|
|
6
6
|
class NullInterface < BaseInterface
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@driver = NullDriver.new
|
|
12
|
-
@driver.interface = @interface
|
|
7
|
+
def initialize
|
|
8
|
+
context = ElementContext.new(destinations: nil,
|
|
9
|
+
interface: self,
|
|
10
|
+
selectors: [])
|
|
11
|
+
@driver = NullDriver.new(context)
|
|
13
12
|
super
|
|
14
13
|
end
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
# The null driver needs to talk to the null interface.
|
|
16
|
+
# Other driver/interface relations are not like this.
|
|
17
|
+
attr_writer :current_page
|
|
19
18
|
|
|
20
19
|
def current_page
|
|
21
20
|
# Default to @pages.first if not set?
|
|
22
|
-
# A browser can
|
|
23
|
-
@current_page
|
|
21
|
+
# A browser can always check its current URL but the null driver can't.
|
|
22
|
+
@current_page ||= @pages.first
|
|
24
23
|
end
|
|
25
24
|
|
|
26
25
|
def wait_for_transition(destinations)
|
|
27
|
-
@
|
|
26
|
+
# TODO: Setting @current page didn't do it beacause that set
|
|
27
|
+
# context.interface.current_page and we wanted this in the browser.
|
|
28
|
+
@current_page = destinations.first.new(self)
|
|
29
|
+
$browser.current_page = @current_page
|
|
28
30
|
end
|
|
29
31
|
end
|
data/lib/rutl/utilities.rb
CHANGED
data/lib/rutl/version.rb
CHANGED
data/rutl.gemspec
CHANGED
|
@@ -30,14 +30,14 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
spec.require_paths = ['lib']
|
|
31
31
|
|
|
32
32
|
spec.add_development_dependency 'bundler', '~> 1.15'
|
|
33
|
-
spec.add_development_dependency 'coveralls'
|
|
34
|
-
spec.add_development_dependency 'drewcoo-cops'
|
|
35
|
-
spec.add_development_dependency 'gem-release'
|
|
33
|
+
spec.add_development_dependency 'coveralls', '~> 0.8'
|
|
34
|
+
spec.add_development_dependency 'drewcoo-cops', '~> 0.1'
|
|
35
|
+
spec.add_development_dependency 'gem-release', '~> 1.0'
|
|
36
36
|
spec.add_development_dependency 'rake', '~> 12.3'
|
|
37
37
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
38
38
|
spec.add_development_dependency 'rspec_junit_formatter', '~> 0.3'
|
|
39
39
|
spec.add_development_dependency 'rubocop', '~> 0.55'
|
|
40
|
-
spec.add_development_dependency 'rubocop-rspec'
|
|
40
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.25'
|
|
41
41
|
spec.add_development_dependency 'selenium-webdriver', '~> 3.12'
|
|
42
42
|
spec.add_development_dependency 'webdrivers', '~> 3.0'
|
|
43
43
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rutl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Drew Cooper
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-
|
|
11
|
+
date: 2018-06-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -28,44 +28,44 @@ dependencies:
|
|
|
28
28
|
name: coveralls
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
33
|
+
version: '0.8'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
40
|
+
version: '0.8'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: drewcoo-cops
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
47
|
+
version: '0.1'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
54
|
+
version: '0.1'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: gem-release
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- - "
|
|
59
|
+
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
61
|
+
version: '1.0'
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
|
-
- - "
|
|
66
|
+
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
68
|
+
version: '1.0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: rake
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -126,16 +126,16 @@ dependencies:
|
|
|
126
126
|
name: rubocop-rspec
|
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
|
128
128
|
requirements:
|
|
129
|
-
- - "
|
|
129
|
+
- - "~>"
|
|
130
130
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: '
|
|
131
|
+
version: '1.25'
|
|
132
132
|
type: :development
|
|
133
133
|
prerelease: false
|
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
135
|
requirements:
|
|
136
|
-
- - "
|
|
136
|
+
- - "~>"
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: '
|
|
138
|
+
version: '1.25'
|
|
139
139
|
- !ruby/object:Gem::Dependency
|
|
140
140
|
name: selenium-webdriver
|
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -197,7 +197,9 @@ files:
|
|
|
197
197
|
- lib/rutl/interface/elements/button.rb
|
|
198
198
|
- lib/rutl/interface/elements/checkbox.rb
|
|
199
199
|
- lib/rutl/interface/elements/click_to_change_state_mixin.rb
|
|
200
|
+
- lib/rutl/interface/elements/element_context.rb
|
|
200
201
|
- lib/rutl/interface/elements/link.rb
|
|
202
|
+
- lib/rutl/interface/elements/string_reader_writer_mixin.rb
|
|
201
203
|
- lib/rutl/interface/elements/text.rb
|
|
202
204
|
- lib/rutl/interface/firefox_interface.rb
|
|
203
205
|
- lib/rutl/interface/null_interface.rb
|