rutl 0.6.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|