rutl 0.6.0 → 0.8.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.
- checksums.yaml +4 -4
- data/.bundle/config +2 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +37 -10
- data/.travis.yml +1 -0
- data/README.md +75 -38
- data/appveyor.yml +48 -0
- data/bin/ie.reg +0 -0
- data/bin/window_data.rb +27 -0
- data/lib/rspec/default_method_object_to_app.rb +28 -0
- data/lib/rspec/rutl_matchers.rb +7 -5
- data/lib/rutl.rb +26 -6
- data/lib/rutl/appium/appium_extension.rb +27 -0
- data/lib/rutl/appium/appium_server.rb +36 -0
- data/lib/rutl/appium/windows_test_app_wrapper.rb +40 -0
- data/lib/rutl/application.rb +70 -0
- data/lib/rutl/camera.rb +20 -4
- data/lib/rutl/element/click_to_change_state_mixin.rb +1 -1
- data/lib/rutl/element/element.rb +9 -10
- data/lib/rutl/element/element_context.rb +5 -4
- data/lib/rutl/element/string_reader_writer_mixin.rb +11 -7
- data/lib/rutl/interface/base.rb +30 -28
- data/lib/rutl/interface/browser/browser.rb +22 -0
- data/lib/rutl/interface/{chrome.rb → browser/chrome.rb} +3 -10
- data/lib/rutl/interface/{firefox.rb → browser/firefox.rb} +3 -10
- data/lib/rutl/interface/browser/internet_explorer.rb +23 -0
- data/lib/rutl/interface/browser/null.rb +36 -0
- data/lib/rutl/interface/windows/hello.rb +36 -0
- data/lib/rutl/interface/windows/notepad.rb +26 -0
- data/lib/rutl/interface/windows/windows_app.rb +35 -0
- data/lib/rutl/null_driver/null_driver.rb +4 -4
- data/lib/rutl/null_driver/null_element.rb +4 -4
- data/lib/rutl/version.rb +1 -1
- data/lib/rutl/{page.rb → view.rb} +37 -28
- data/lib/utilities/check_view.rb +12 -0
- data/lib/utilities/string.rb +12 -0
- data/lib/utilities/waiter.rb +23 -0
- data/rutl.gemspec +13 -0
- metadata +94 -10
- data/lib/rspec/default_rspec_to_browser.rb +0 -22
- data/lib/rutl/browser.rb +0 -70
- data/lib/rutl/interface/null.rb +0 -35
- data/lib/utilities.rb +0 -41
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
require 'rutl/interface/base'
|
3
|
+
|
4
|
+
module RUTL
|
5
|
+
module Interface
|
6
|
+
#
|
7
|
+
# Small interface for Chrome browser.
|
8
|
+
#
|
9
|
+
class Browser < Base
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def current_view
|
15
|
+
url = @driver.current_url
|
16
|
+
view = find_view(url)
|
17
|
+
raise "NOT FOUND: #{url}, VIEWS: #{@views}" unless view
|
18
|
+
view
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'selenium-webdriver'
|
2
|
-
require 'rutl/interface/
|
2
|
+
require 'rutl/interface/browser/browser'
|
3
3
|
|
4
4
|
module RUTL
|
5
5
|
module Interface
|
6
6
|
#
|
7
7
|
# Small interface for Chrome browser.
|
8
8
|
#
|
9
|
-
class Chrome <
|
9
|
+
class Chrome < Browser
|
10
10
|
# rubocop:disable Metrics/MethodLength
|
11
11
|
def initialize
|
12
12
|
@logged_in = true
|
@@ -15,7 +15,7 @@ module RUTL
|
|
15
15
|
options.add_argument('--disable-popup-blocking')
|
16
16
|
options.add_argument('--disable-translate')
|
17
17
|
# Run headless on TravisCI
|
18
|
-
if ENV['TRAVIS']
|
18
|
+
if ENV['TRAVIS']
|
19
19
|
options.add_argument('--disable-gpu')
|
20
20
|
options.add_argument('--headless ')
|
21
21
|
options.add_argument('--no-sandbox')
|
@@ -24,13 +24,6 @@ module RUTL
|
|
24
24
|
super
|
25
25
|
end
|
26
26
|
# rubocop:enable Metrics/MethodLength
|
27
|
-
|
28
|
-
def current_page
|
29
|
-
url = @driver.current_url
|
30
|
-
page = find_page(url)
|
31
|
-
raise "PAGE NOT FOUND: #{url}, PAGES: #{@pages}" unless page
|
32
|
-
page
|
33
|
-
end
|
34
27
|
end
|
35
28
|
end
|
36
29
|
end
|
@@ -1,29 +1,22 @@
|
|
1
1
|
require 'selenium-webdriver'
|
2
|
-
require 'rutl/interface/
|
2
|
+
require 'rutl/interface/browser/browser'
|
3
3
|
|
4
4
|
module RUTL
|
5
5
|
module Interface
|
6
6
|
#
|
7
7
|
# Small interface for Chrome browser.
|
8
8
|
#
|
9
|
-
class Firefox <
|
9
|
+
class Firefox < Browser
|
10
10
|
def initialize
|
11
11
|
@logged_in = true
|
12
12
|
options = Selenium::WebDriver::Firefox::Options.new
|
13
13
|
options.add_argument('--ignore-certificate-errors')
|
14
14
|
options.add_argument('--disable-popup-blocking')
|
15
15
|
options.add_argument('--disable-translate')
|
16
|
-
options.add_argument('--headless') if ENV['TRAVIS']
|
16
|
+
options.add_argument('--headless') if ENV['TRAVIS'] || ENV['CIRCLECI']
|
17
17
|
@driver = Selenium::WebDriver.for :firefox, options: options
|
18
18
|
super
|
19
19
|
end
|
20
|
-
|
21
|
-
def current_page
|
22
|
-
url = @driver.current_url
|
23
|
-
page = find_page(url)
|
24
|
-
raise "PAGE NOT FOUND: #{url}, PAGES: #{@pages}" unless page
|
25
|
-
page
|
26
|
-
end
|
27
20
|
end
|
28
21
|
end
|
29
22
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
require 'rutl/interface/browser/browser'
|
3
|
+
|
4
|
+
module RUTL
|
5
|
+
module Interface
|
6
|
+
#
|
7
|
+
# Small interface for Chrome browser.
|
8
|
+
#
|
9
|
+
class InternetExplorer < Browser
|
10
|
+
def initialize
|
11
|
+
@logged_in = true
|
12
|
+
options = Selenium::WebDriver::IE::Options.new
|
13
|
+
options.add_argument('--ignore-certificate-errors')
|
14
|
+
options.add_argument('--disable-popup-blocking')
|
15
|
+
options.add_argument('--disable-translate')
|
16
|
+
options.ignore_zoom_level = true
|
17
|
+
options.initial_browser_url = 'https://www.google.com/'
|
18
|
+
@driver = Selenium::WebDriver.for :internet_explorer, options: options
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rutl/interface/browser/browser'
|
2
|
+
|
3
|
+
module RUTL
|
4
|
+
module Interface
|
5
|
+
#
|
6
|
+
# Interface-level code for fake application.
|
7
|
+
#
|
8
|
+
class Null < Browser
|
9
|
+
def initialize
|
10
|
+
context = RUTL::Element::ElementContext.new(destinations: nil,
|
11
|
+
interface: self,
|
12
|
+
selectors: [])
|
13
|
+
@driver = NullDriver.new(context)
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
# The null driver needs to talk to the null interface.
|
18
|
+
# Other driver/interface relations are not like this.
|
19
|
+
attr_writer :current_view
|
20
|
+
|
21
|
+
def current_view
|
22
|
+
# Default to @view.first if not set?
|
23
|
+
# An application can always check its current URL but
|
24
|
+
# the null driver can't.
|
25
|
+
@current_view ||= @views.first
|
26
|
+
end
|
27
|
+
|
28
|
+
def wait_for_transition(destinations)
|
29
|
+
# TODO: Setting @current view didn't do it beacause that set
|
30
|
+
# context.interface.current_view and we wanted this in the application.
|
31
|
+
@current_view = destinations.first.new(self)
|
32
|
+
$application.current_view = @current_view
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
require 'rutl/interface/windows/windows_app'
|
3
|
+
|
4
|
+
module RUTL
|
5
|
+
module Interface
|
6
|
+
#
|
7
|
+
# The hello world app with an exit button.
|
8
|
+
#
|
9
|
+
class Hello < WindowsApp
|
10
|
+
def file_name
|
11
|
+
File.expand_path('../../../../spec/hello.rb', __dir__)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@app = WindowsTestApp.new(name: "ruby #{file_name}",
|
16
|
+
title: /hello world/i)
|
17
|
+
@app.start
|
18
|
+
driver_opts = base_opts
|
19
|
+
# Have to start app then attach winappdriver because these both fail:
|
20
|
+
# 1. passing hello.rb path as [:caps][:app]
|
21
|
+
# 2. passing ruby.exe path as [:caps][:app] and passing hello.rb
|
22
|
+
# path as [:caps][:appArguments]
|
23
|
+
driver_opts[:caps][:appTopLevelWindow] = @app.window_handle_string
|
24
|
+
@driver = Appium::Driver.new(driver_opts, false)
|
25
|
+
@driver.start
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def current_view
|
30
|
+
# This only works because I only have one view.
|
31
|
+
# Should I? What about dialogs?
|
32
|
+
@views.first
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
require 'rutl/interface/windows/windows_app'
|
3
|
+
|
4
|
+
module RUTL
|
5
|
+
module Interface
|
6
|
+
#
|
7
|
+
# Notepad.
|
8
|
+
#
|
9
|
+
class Notepad < WindowsApp
|
10
|
+
def initialize
|
11
|
+
@app_name = 'notepad.exe'
|
12
|
+
driver_opts = base_opts
|
13
|
+
driver_opts[:caps][:app] = 'C:\Windows\System32\notepad.exe'
|
14
|
+
@driver = Appium::Driver.new(driver_opts, false)
|
15
|
+
@driver.start
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def current_view
|
20
|
+
# This only works because I only have one view.
|
21
|
+
# Should I? What about dialogs?
|
22
|
+
@views.first
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
require 'rutl/interface/base'
|
3
|
+
|
4
|
+
module RUTL
|
5
|
+
module Interface
|
6
|
+
#
|
7
|
+
# Parent class for all Windows apps.
|
8
|
+
#
|
9
|
+
class WindowsApp < Base
|
10
|
+
def base_opts
|
11
|
+
{ caps: { platformName: 'WINDOWS',
|
12
|
+
platform: 'WINDOWS',
|
13
|
+
deviceName: 'WindowsPC' },
|
14
|
+
appium_lib: { wait_timeout: 2,
|
15
|
+
wait_interval: 0.01 } }
|
16
|
+
end
|
17
|
+
|
18
|
+
def kill
|
19
|
+
system "taskkill /f /im #{@app_name} /t 1>nul 2>&1"
|
20
|
+
end
|
21
|
+
|
22
|
+
def open?
|
23
|
+
@driver.find_elements(:id, 0)
|
24
|
+
true
|
25
|
+
rescue Selenium::WebDriver::Error::NoSuchWindowError
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def quit
|
30
|
+
@driver.driver_quit
|
31
|
+
kill
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -2,7 +2,7 @@ require 'rutl/null_driver/null_element'
|
|
2
2
|
|
3
3
|
module RUTL
|
4
4
|
#
|
5
|
-
# This is at a peer level to the webdrivers but it's for a fake
|
5
|
+
# This is at a peer level to the webdrivers but it's for a fake application.
|
6
6
|
#
|
7
7
|
class NullDriver
|
8
8
|
attr_accessor :context
|
@@ -19,7 +19,7 @@ module RUTL
|
|
19
19
|
RUTL::Element::NullElement.new(context, type, location)
|
20
20
|
end
|
21
21
|
|
22
|
-
# Cheap way to handle
|
22
|
+
# Cheap way to handle application.navigate.to(url)
|
23
23
|
# TODO: Until I care about the url and then I should ????
|
24
24
|
def navigate
|
25
25
|
context = RUTL::Element::ElementContext.new(interface: @context.interface)
|
@@ -28,8 +28,8 @@ module RUTL
|
|
28
28
|
|
29
29
|
# Cheap second part to naviate.to(url) calls to look like real drivers.
|
30
30
|
def to(url)
|
31
|
-
result = @context.interface.
|
32
|
-
@context.interface.
|
31
|
+
result = @context.interface.find_view(url)
|
32
|
+
@context.interface.current_view = result
|
33
33
|
result.url
|
34
34
|
end
|
35
35
|
|
@@ -3,7 +3,7 @@ require 'rutl/element/element_context'
|
|
3
3
|
module RUTL
|
4
4
|
module Element
|
5
5
|
#
|
6
|
-
# This fakes all
|
6
|
+
# This fakes all view elements when used with the null driver.
|
7
7
|
# It's a dirty way to avoid modeling all of what a driver talks to.
|
8
8
|
#
|
9
9
|
class NullElement
|
@@ -21,8 +21,8 @@ module RUTL
|
|
21
21
|
|
22
22
|
# @@string is a class variable because this framework creates new
|
23
23
|
# instances of each element every time it accesses them. This is good
|
24
|
-
# behavior by default because
|
25
|
-
# For text fields in the null
|
24
|
+
# behavior by default because views could change underneath us.
|
25
|
+
# For text fields in the null application, though, we want to preserve the
|
26
26
|
# values across calls, letting us write and then read.
|
27
27
|
def send_keys(string)
|
28
28
|
init = @@variables[@location] || ''
|
@@ -42,7 +42,7 @@ module RUTL
|
|
42
42
|
@@variables[@location] = ''
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
45
|
+
def find_element
|
46
46
|
self
|
47
47
|
end
|
48
48
|
|
data/lib/rutl/version.rb
CHANGED
@@ -3,13 +3,16 @@ require 'rutl/null_driver/null_driver'
|
|
3
3
|
|
4
4
|
module RUTL
|
5
5
|
#
|
6
|
-
# Base
|
7
|
-
# stuff to parse the
|
6
|
+
# Base view class. It's used to call the magical method_messing
|
7
|
+
# stuff to parse the view object files into actual objects.
|
8
8
|
#
|
9
|
-
class
|
9
|
+
class View
|
10
|
+
#
|
11
|
+
# BUGBUG #1: Some view in a generic app should not have URL.
|
12
|
+
#
|
10
13
|
# BUGBUG: Kludgy. What do I really want to do here?
|
11
|
-
# Make it easy to define a
|
12
|
-
# also matchers for
|
14
|
+
# Make it easy to define a view's default url and
|
15
|
+
# also matchers for view urls for views with variable urls?
|
13
16
|
# rubocop:disable Style/TrivialAccessors
|
14
17
|
def self.url
|
15
18
|
@url
|
@@ -20,34 +23,40 @@ module RUTL
|
|
20
23
|
end
|
21
24
|
# rubocop:enable Style/TrivialAccessors
|
22
25
|
|
23
|
-
|
24
|
-
|
26
|
+
def go_to_here
|
27
|
+
# Ovveride this in base view to have something more
|
28
|
+
# complicated than this.
|
29
|
+
@interface.driver.navigate.to(url)
|
30
|
+
end
|
31
|
+
|
32
|
+
def loaded?
|
33
|
+
# Default to only checking url to see if view loaded.
|
34
|
+
url == @interface.driver.current_url
|
35
|
+
end
|
36
|
+
|
37
|
+
# Intentionally use a class variable to hald views. Once they're
|
38
|
+
# all loaded they are all loaded for everyone.
|
25
39
|
# rubocop:disable Style/ClassVars
|
26
|
-
@@
|
40
|
+
@@loaded_views = []
|
27
41
|
# rubocop:enable Style/ClassVars
|
28
42
|
|
29
43
|
def initialize(interface)
|
30
44
|
@interface = interface
|
31
|
-
# Dirty trick because we're loading all of
|
45
|
+
# Dirty trick because we're loading all of view classes from files and
|
32
46
|
# then initializing them, calling their layout methods to do magic.
|
33
|
-
# The
|
34
|
-
return if @@
|
47
|
+
# The view class knows what views are loaded.
|
48
|
+
return if @@loaded_views.include?(self.class)
|
35
49
|
layout
|
36
|
-
@@
|
50
|
+
@@loaded_views << self.class
|
37
51
|
end
|
38
52
|
|
39
|
-
|
40
|
-
# Ovveride this in base page to have something more
|
41
|
-
# complicated than this.
|
42
|
-
@interface.driver.navigate.to(url)
|
43
|
-
end
|
44
|
-
|
45
|
-
# Written by Browser and only used internally.
|
53
|
+
# Written by Application and only used internally.
|
46
54
|
attr_writer :interface
|
47
55
|
|
48
|
-
def loaded?
|
49
|
-
|
50
|
-
|
56
|
+
# def loaded?
|
57
|
+
# # In case I try calling this without defining it first.
|
58
|
+
# raise 'No #loaded? method defined.'
|
59
|
+
# end
|
51
60
|
|
52
61
|
# Dynamically add a method, :<name> (or :<name>= if setter)
|
53
62
|
# to the current class where that method creates an instance
|
@@ -57,7 +66,7 @@ module RUTL
|
|
57
66
|
# As it is, this seems silly to break into pieces for Rubocop.
|
58
67
|
# rubocop:disable Metrics/MethodLength
|
59
68
|
def add_method(context:, klass:, name:, setter: false)
|
60
|
-
name = "#{name}_#{klass.downcase}"
|
69
|
+
name = "#{name}_#{klass.downcase}" if RUTL::HUNGARIAN
|
61
70
|
constant = Module.const_get("RUTL::Element::#{klass.capitalize}")
|
62
71
|
self.class.class_exec do
|
63
72
|
if setter
|
@@ -77,7 +86,7 @@ module RUTL
|
|
77
86
|
# This creates a new element instance whenever it's called.
|
78
87
|
# Because of that we can't keep state in any element objects.
|
79
88
|
# That seems like a good thing, actually.
|
80
|
-
# Called by layout method on
|
89
|
+
# Called by layout method on views.
|
81
90
|
#
|
82
91
|
# Hard to make shorter.
|
83
92
|
# rubocop:disable Metrics/MethodLength
|
@@ -103,15 +112,15 @@ module RUTL
|
|
103
112
|
# Is this right at all???
|
104
113
|
case args[0].to_s
|
105
114
|
when /button/, /checkbox/, /element/, /link/, /text/,
|
106
|
-
'driver', '
|
115
|
+
'driver', 'children', 'loaded?'
|
107
116
|
true
|
108
117
|
when 'ok_link'
|
109
|
-
raise 'OK LINK WAY DOWN HERE IN BASE
|
118
|
+
raise 'OK LINK WAY DOWN HERE IN BASE VIEW!!!'
|
110
119
|
else
|
111
120
|
# I think it's good to raise but change the message.
|
112
121
|
raise 'TODO: BETTER ERROR MESSAGE, PLEASE. I AM SHOUTING!!!\n' \
|
113
|
-
'Drew, you hit this most often when checking current
|
114
|
-
"rather than current
|
122
|
+
'Drew, you hit this most often when checking current view ' \
|
123
|
+
"rather than current view class:\n\n #{args}"
|
115
124
|
# I think I want to raise instead of returningn false.
|
116
125
|
end
|
117
126
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#
|
2
|
+
# #view? is used in interface/base.rb and in rspec/rutl_matchers.rb
|
3
|
+
# so the method lives over in this lonely place.
|
4
|
+
#
|
5
|
+
module CheckView
|
6
|
+
def view?(checkme)
|
7
|
+
checkme.ancestors.include?(RUTL::View)
|
8
|
+
rescue NoMethodError
|
9
|
+
false
|
10
|
+
end
|
11
|
+
alias page? view?
|
12
|
+
end
|