rutl 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3a234a9eee8f10634928c60cfa5ff5e276249734ecbd3dbb595619c547abcdfe
4
+ data.tar.gz: 7656acfb1ec3376a1e0e6d06b2506b1ae4174036ae48131fa9360ef982e44431
5
+ SHA512:
6
+ metadata.gz: 7159d1e0680e8e34d22a8b1ca90997303ad03b8236b0764f5e521348db3e5f798109874a6e4b378239d9d9b54397213f88503e6a796608c00744d0dff1638ab2
7
+ data.tar.gz: af53558f19aea133bd042304c26dd89a9fd459843d4e789a7728ed34c7311198e13aacaeb533b96db7bfc1e1f423a4ddf740c75c9befb291b5a2af7855d9ef53
@@ -0,0 +1,57 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ # specify the version you desire here
10
+ - image: circleci/ruby:2.4.1-node-browsers
11
+
12
+ # Specify service dependencies here if necessary
13
+ # CircleCI maintains a library of pre-built images
14
+ # documented at https://circleci.com/docs/2.0/circleci-images/
15
+ # - image: circleci/postgres:9.4
16
+
17
+ working_directory: ~/repo
18
+
19
+ steps:
20
+ - checkout
21
+
22
+ # Download and cache dependencies
23
+ - restore_cache:
24
+ keys:
25
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}
26
+ # fallback to using the latest cache if no exact match is found
27
+ - v1-dependencies-
28
+
29
+ - run:
30
+ name: install dependencies
31
+ command: |
32
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
33
+
34
+ - save_cache:
35
+ paths:
36
+ - ./vendor/bundle
37
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}
38
+
39
+ # run tests!
40
+ - run:
41
+ name: run tests
42
+ command: |
43
+ mkdir /tmp/test-results
44
+ TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
45
+
46
+ bundle exec rspec --format progress \
47
+ --format RspecJunitFormatter \
48
+ --out /tmp/test-results/rspec.xml \
49
+ --format progress \
50
+ $TEST_FILES
51
+
52
+ # collect reports
53
+ - store_test_results:
54
+ path: /tmp/test-results
55
+ - store_artifacts:
56
+ path: /tmp/test-results
57
+ destination: test-results
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: 9ZcBAv3ZPtJvEMDw3ZP8SSKdCpIqldlBE
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ AllCops:
2
+ DefaultFormatter: progress
3
+ DisplayCopNames: true
4
+ DisplayStyleGuide: true
5
+ ExtraDetails: true
6
+
7
+ Metrics/BlockLength:
8
+ Enabled: false
9
+
10
+ Metrics/MethodLength:
11
+ Enabled: false
12
+
13
+ Style/BracesAroundHashParameters:
14
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ sudo: required
2
+ addons:
3
+ chrome: stable
4
+ language: ruby
5
+ rvm:
6
+ - 2.3.1
7
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rutl.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Drew Cooper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # RUTL
2
+
3
+ [![TravisCI](https://api.travis-ci.org/drewcoo/rutl.svg)](https://travis-ci.org/drewcoo/rutl)
4
+ [![CircleCI](https://circleci.com/gh/drewcoo/rutl.svg?style=shield)](https://circleci.com/gh/drewcoo/rutl)
5
+ [![Coverage Status](https://coveralls.io/repos/github/drewcoo/rutl/badge.svg?branch=master)](https://coveralls.io/github/drewcoo/rutl?branch=master)
6
+ [![Gem Version](https://badge.fury.io/rb/rutl.svg)](https://badge.fury.io/rb/rutl)
7
+
8
+ This is the Ruby Ui Test Library, or RUTL. Not to be confused with the Rutles.
9
+ https://www.rutles.org/
10
+
11
+ Framework goals:
12
+ * Define what's on a page in an easy, flexible way. Easy page objects!
13
+ * Abstract away things that make tests buggy and painful to maintain.
14
+ * Write test cases for native apps, the web, and desktop apps the same way.
15
+ * Make screenshotting and diffing screenshots sane and easy.
16
+ * TODO: I'm sure I'm missing some at the moment.
17
+ * Secondary-ish goal: Make fake browser to test the framework faster.
18
+ * Tertiary-ish: Stop calling browser "fake" because I'm sick of that word. Null!
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ ```ruby
25
+ gem 'rutl'
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ Or install it yourself as:
33
+
34
+ $ gem install rutl
35
+
36
+ ## Usage
37
+
38
+ TODO: Write usage instructions here
39
+
40
+ ## Roadmap
41
+ Coming up soon in almost no order:
42
+ * Make browser tests work on TravisCI.
43
+ * A test framework should have better tests. Goes with next bullet.
44
+ * Flesh out null interface/driver. Make them do what a real browser does.
45
+ * Make geckodriver work. Geckodriver-helper is failing me.
46
+ * Restructure tests to handle fake browsers and real browsers. Same structure?
47
+ * Make this work with pages in some other location so we can use it as a gem.
48
+ * Make work with TravisCI.
49
+ * Put more info in this readme.
50
+ * Take screenshots.
51
+ * Diff screenshots. Make this smart so we don't have to be experts.
52
+ * InternetExplorerDriver
53
+ * Other browser drivers? Look at https://github.com/fnando/browser
54
+ * Get this working with Appium:
55
+ * Make TK app to test on desktops and test it.
56
+ * Make Android example app and get this to work.
57
+ * Same with iPhone.
58
+ * Others?
59
+ * Spidering page object maker.
60
+ * Possibly pair the null browser with auto-generated pages for ______?
61
+ * Call rutl.rb properly.
62
+ * Optional install of test resources based on machine type.
63
+ * Instructions about machine installs to help people using gem.
64
+ * Pair with some kind of VM, Docker container, AMI, or something.
65
+
66
+ ## Development
67
+
68
+ Set everything up:
69
+ 1. Check out the repo.
70
+ 2. `cd` to the repo.
71
+ 3. `bundle install`
72
+ 4. `bundle exec rake`
73
+ Great! You've checked out the code, installed everything and run the tests.
74
+
75
+ Rubocop. I still have to tweak what I want it to complain about.
76
+ `bundle exec rubocop`
77
+
78
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
79
+
80
+ ## Contributing
81
+
82
+ Bug reports and pull requests are welcome on GitHub at https://github.com/drewcoo/rutl.
83
+
84
+
85
+ ## License
86
+
87
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ require 'rutl'
4
+
5
+ # You can add fixtures and/or initialization code here to make experimenting
6
+ # with your gem easier. You can also use a different console, if you like.
7
+
8
+ # (If you use this, don't forget to add pry to your Gemfile!)
9
+ # require "pry"
10
+ # Pry.start
11
+
12
+ require 'irb'
13
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,100 @@
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
+ def self.url; @url; end
13
+
14
+ def url; self.class.url; end
15
+
16
+ @@children = []
17
+ def self.children; @@children; end
18
+
19
+ attr_accessor :driver
20
+
21
+ # TODO: DO I REALLY WANT TO PASS IN DRIVER LIKE THAT?
22
+ def initialize
23
+ # Call a class's layout method the first time it's loaded
24
+ # and put the class name in a list of children, which is a
25
+ # list of all actual page objects in this case.
26
+ return if @@children.include?(self.class)
27
+ layout
28
+ @@children << self.class
29
+ end
30
+
31
+ def loaded?
32
+ @url = @driver.current_url
33
+ end
34
+
35
+ # BUGBUG: This creates a new element instance whenever it's called.
36
+ # Because of that we can't keep state in any element objects.
37
+ # Is that actually a good thing, maybe?
38
+
39
+ # Called by layout method on pages.
40
+ def method_missing(element, *args, &_block)
41
+ name, selector, rest = args
42
+ name = "#{name}_#{element.downcase}"
43
+
44
+ case element
45
+ when /button/, /checkbox/, /link/
46
+ # self.class.class_exec do
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
57
+ when /text/
58
+ self.class.class_exec do
59
+ # foo = Module.const_get(element.capitalize).new(selector, rest)
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
79
+ else
80
+ # TODO: replace with a super call. This is useful for debugging for now.
81
+ raise "#{element} NOT FOUND WITH ARGS #{args}!!!"
82
+ end
83
+ end
84
+
85
+ def respond_to_missing?(*args)
86
+ # Is this right at all???
87
+ case args[0].to_s
88
+ when /button/, /checkbox/, /link/, /text/,
89
+ 'driver', 'url', 'children', 'loaded?'
90
+ true
91
+ when 'ok_link'
92
+ raise 'OK LINK WAY DOWN HERE IN BASE PAGE!!!'
93
+ else
94
+ # I think it's good to raise but change the message.
95
+ raise 'Drew, you hit this most often when checking current page ' \
96
+ 'rather than current page class'
97
+ # I think I want to raise instead of returningn false.
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,65 @@
1
+ require 'rutl/utilities'
2
+ require 'rutl/base_page'
3
+
4
+ #
5
+ # Currently called Browser, this top-level class controls a browser and
6
+ # a fake browser. It will soon call into apps, at which point I need to
7
+ # rethink this naming convention.
8
+ #
9
+ class Browser
10
+ include Utilities
11
+
12
+ def initialize(interface_type: :none, page_object_dir: 'spec/pages')
13
+ @pages = load_pages(page_object_dir: page_object_dir)
14
+ load_interface(type: interface_type)
15
+ end
16
+
17
+ def load_interface(type: :none)
18
+ pages = @pages
19
+ require "rutl/interface/#{type}_interface"
20
+ klass = "#{type.to_s.capitalize}Interface"
21
+ @interface = Object.const_get(klass).new(pages: pages)
22
+ @interface.interface = @interface
23
+ end
24
+
25
+ # Ugly. Requires files for page objects. Returns array of class names to load.
26
+ def require_pages(page_object_dir: 'spec/pages')
27
+ names = []
28
+ Dir["#{page_object_dir}/*"].each do |file|
29
+ require "rutl/../../#{file}"
30
+ File.open(file).each do |line|
31
+ names << $1 if line =~ /class (.*) < BasePage/
32
+ end
33
+ end
34
+ names
35
+ end
36
+
37
+ def load_pages(*)
38
+ pages = []
39
+ names = require_pages
40
+ names.each do |klass|
41
+ # Don't have @interface set yet.
42
+ # That would have been the param to new, :interface.
43
+ pages << Object.const_get(klass).new
44
+ end
45
+ pages
46
+ end
47
+
48
+ def method_missing(method, *args, &block)
49
+ result = if args.empty?
50
+ @interface.send(method)
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)
57
+ else
58
+ result
59
+ end
60
+ end
61
+
62
+ def respond_to_missing?(*args)
63
+ @interface.respond_to?(*args)
64
+ end
65
+ end
@@ -0,0 +1,34 @@
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 :interface
8
+
9
+ def find_element(type, location)
10
+ # Return a new one of these so that it can be clicked ar written
11
+ # to or whatever.
12
+ element = NullDriverPageElement.new(type, location)
13
+ element.interface = @interface
14
+ element
15
+ end
16
+
17
+ # Cheap way to handle browser.navigate.to(url)
18
+ # TODO: Until I care about the url and then I should ????
19
+ def navigate
20
+ result = NullDriver.new
21
+ result.interface = @interface
22
+ result
23
+ end
24
+
25
+ def to(url)
26
+ result = @interface.find_page(url)
27
+ @interface.set_current_page result
28
+ result.url
29
+ end
30
+
31
+ def quit
32
+ 'quit'
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # This fakes all page elements when used with the null driver.
3
+ # It's a dirty way to avoid modeling all of what a driver talks to.
4
+ #
5
+ class NullDriverPageElement
6
+ attr_accessor :string
7
+ attr_reader :selector_type, :selector
8
+
9
+ attr_accessor :interface
10
+ attr_accessor :destinations
11
+
12
+ def initialize(selector_type, selector)
13
+ # :css, selector
14
+ @selector_type = selector_type
15
+ @selector = selector
16
+ end
17
+
18
+ def send_keys(string)
19
+ @string = string
20
+ end
21
+
22
+ def attribute(attr)
23
+ case attr.to_sym
24
+ when :value
25
+ @string
26
+ else
27
+ raise ArgumentError, "Attribute unknown: #{attr}"
28
+ end
29
+ end
30
+
31
+ # Return simple strings for checks against the NullDriver instead of
32
+ # having to use some heavyweight UI.
33
+ def clear
34
+ 'clear'
35
+ end
36
+
37
+ def click
38
+ 'click'
39
+ end
40
+ end
@@ -0,0 +1,95 @@
1
+ require 'rutl/utilities'
2
+
3
+ #
4
+ # I might need to consider renaming these.
5
+ # The *interface classes lie between Browser and the webdriver-level classes.
6
+ #
7
+ class BaseInterface
8
+ include Utilities
9
+
10
+ def current_url
11
+ current_page.url
12
+ end
13
+
14
+ attr_reader :pages
15
+
16
+ # Child classes must implement current_page
17
+
18
+ attr_accessor :driver
19
+
20
+ attr_accessor :interface
21
+
22
+ def initialize(pages:)
23
+ @pages = pages
24
+ raise 'Child classes must implement @driver.' unless defined? @driver
25
+ @pages.each { |p| p.driver = @driver }
26
+ end
27
+
28
+ def goto(input)
29
+ # TODO: KLUDGY. Fix. Modifier if usage bombs here. *shrug*
30
+ @driver.interface = @interface if 'NullInterface' == @interface.class.to_s
31
+ input = find_page(input) unless input.methods.include?(:url)
32
+
33
+ @driver.navigate.to input.url
34
+ end
35
+
36
+ def current_page
37
+ raise 'OVERRIDE IN CHILDREN'
38
+ end
39
+
40
+ def method_missing(method, *args, &block)
41
+ result = if args.empty?
42
+ current_page.send(method)
43
+ else
44
+ current_page.send(method, *args, &block)
45
+ end
46
+ raise interface.to_s if interface.nil?
47
+ raise result.to_s unless defined? result # result.interface
48
+ begin
49
+ result.interface = interface
50
+ rescue NoMethodError
51
+ raise NoMethodError, "METHOD NOT FOUND: #{method}"
52
+ end
53
+ result
54
+ end
55
+
56
+ # TODO: Is this needed? I not only find the page but also make sure the
57
+ # urls match. Even though that's what finding pages means?
58
+ def find_state(target_states)
59
+ target_states.each do |state|
60
+ next unless state.url == current_page.url
61
+ page = find_page(state, true)
62
+ return current_page = page if page.loaded?
63
+ end
64
+ raise current_page.to_s if current_page.class == InternetLoggedInPage
65
+ false
66
+ end
67
+
68
+ def find_page(page, raise_on_fail = false)
69
+ @pages.each do |p|
70
+ # page is a Page class
71
+ return p if page?(page) && p.class == page
72
+ # or a String, possibly URL
73
+ return p if String == page.class && page == p.url
74
+ end
75
+ raise "Page \"#{page}\" not found in pages #{@pages}" if raise_on_fail
76
+ end
77
+
78
+ def wait_for_transition(target_states)
79
+ #
80
+ # TODO: Should also see if there are other things to wait for.
81
+ # I don't think this is doing page load time.
82
+ #
83
+ await -> { find_state target_states }
84
+ end
85
+
86
+ def respond_to_missing?(*args)
87
+ # This can't be right. Figure it out later.
88
+ current_page.respond_to?(*args)
89
+ end
90
+
91
+ def quit
92
+ @driver.quit
93
+ @pages = []
94
+ end
95
+ end
@@ -0,0 +1,32 @@
1
+ require 'selenium-webdriver'
2
+ require 'rutl/interface/base_interface'
3
+
4
+ #
5
+ # Small interface for Chrome browser.
6
+ #
7
+ # TODO: Probably the current_page() implementation should move up to base.
8
+ #
9
+ class ChromeInterface < BaseInterface
10
+ def initialize(pages:)
11
+ @logged_in = true
12
+ options = Selenium::WebDriver::Chrome::Options.new
13
+ options.add_argument('--ignore-certificate-errors')
14
+ options.add_argument('--disable-popup-blocking')
15
+ options.add_argument('--disable-translate')
16
+ # Run headless on TravisCI
17
+ if 'true' == ENV['TRAVIS']
18
+ options.add_argument('--disable-gpu')
19
+ options.add_argument('--headless ')
20
+ options.add_argument('--no-sandbox')
21
+ end
22
+ @driver = Selenium::WebDriver.for :chrome, options: options
23
+ super
24
+ end
25
+
26
+ def current_page
27
+ url = @driver.current_url
28
+ page = find_page(url, true)
29
+ raise "PAGE NOT FOUND: #{url}, PAGES: #{@pages}" unless page
30
+ page
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # Page elements. Base class.
3
+ #
4
+ class BaseElement
5
+ attr_accessor :interface
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
16
+
17
+ def find_css(selectors)
18
+ result = driver.find_element(:css, selectors[:css])
19
+ return result unless NullDriver == result.class
20
+ result.destinations = @destinations
21
+ result.interface = @interface
22
+ result
23
+ end
24
+
25
+ def this_css
26
+ find_css(@selectors)
27
+ end
28
+ end
@@ -0,0 +1,10 @@
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
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,13 @@
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
+ this_css.click
9
+ interface.wait_for_transition(@destinations)
10
+ # TODO: Return what?
11
+ @destinations
12
+ end
13
+ end
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,30 @@
1
+ require 'rutl/interface/elements/base_element'
2
+
3
+ #
4
+ # I'm using the text element for all text-like things. Passowrds, too.
5
+ #
6
+ class Text < BaseElement
7
+ def initialize(selectors = {}, destinations = [])
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
30
+ end
@@ -0,0 +1,4 @@
1
+ require 'rutl/interface/elements/button'
2
+ require 'rutl/interface/elements/checkbox'
3
+ require 'rutl/interface/elements/link'
4
+ require 'rutl/interface/elements/text'
@@ -0,0 +1,26 @@
1
+ require 'selenium-webdriver'
2
+ require 'rutl/interface/base_interface'
3
+
4
+ #
5
+ # Small interface for Chrome browser.
6
+ #
7
+ # TODO: Probably the current_page() implementation should move up to base.
8
+ #
9
+ class FirefoxInterface < BaseInterface
10
+ def initialize(pages:)
11
+ @logged_in = true
12
+ options = Selenium::WebDriver::Firefox::Options.new
13
+ options.add_argument('--ignore-certificate-errors')
14
+ options.add_argument('--disable-popup-blocking')
15
+ options.add_argument('--disable-translate')
16
+ @driver = Selenium::WebDriver.for :firefox, options: options
17
+ super
18
+ end
19
+
20
+ def current_page
21
+ url = @driver.current_url
22
+ page = find_page(url, true)
23
+ raise "PAGE NOT FOUND: #{url}, PAGES: #{@pages}" unless page
24
+ page
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ require 'rutl/interface/base_interface'
2
+
3
+ #
4
+ # Interface-level code for fake browser.
5
+ #
6
+ class NullInterface < BaseInterface
7
+ # Only the null interface>>
8
+ attr_reader :current_page
9
+
10
+ @variables = []
11
+ attr_accessor :variables
12
+
13
+ def initialize(pages:)
14
+ @driver = NullDriver.new
15
+ @driver.interface = @interface
16
+ super
17
+ end
18
+
19
+ def set_current_page(page)
20
+ @current_page = page
21
+ end
22
+
23
+ def current_page
24
+ # Default to @pages.first if not set?
25
+ # A browser can alwasy check its current URL but the null driver can't.
26
+ @current_page || @pages.first
27
+ end
28
+
29
+ def wait_for_transition(destinations)
30
+ @current_page = find_page(destinations.first)
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # A catch-all bag for stuff I don't have elsewhere yet.
3
+ #
4
+ module Utilities
5
+ require 'timeout'
6
+
7
+ POLL_SLEEP_TIME = 0.1
8
+ DEFAULT_TIMEOUT = 5
9
+
10
+ # The lambda passed to await should return false if thing not found
11
+ # and something truthy if found
12
+ def await(lamb, timeout = DEFAULT_TIMEOUT, poll_sleep_time = POLL_SLEEP_TIME)
13
+ Timeout.timeout(timeout) do
14
+ loop do
15
+ result = lamb.call
16
+ return result if result
17
+ sleep poll_sleep_time
18
+ end
19
+ end
20
+ end
21
+
22
+ def class_info(object)
23
+ result = "CLASS: #{object.class}"
24
+ result += "\nmethods: #{(object.methods - Class.methods).sort}\n"
25
+ result
26
+ end
27
+
28
+ # Just call "caller" with no args for stack trace.
29
+ def location
30
+ caller(1..1).first
31
+ end
32
+
33
+ def page?(checkme)
34
+ checkme.ancestors.include?(BasePage)
35
+ rescue # BUGBUG: Didn't have time to find all the things to rescue yet.
36
+ false
37
+ end
38
+
39
+ def raise_if_not_page(page)
40
+ raise "NOT A PAGE: #{page}. Ancestors: #{page.ancestors}" unless page?(page)
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module RUTL
2
+ VERSION = '0.1.1'.freeze
3
+ end
data/lib/rutl.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rutl/browser'
2
+ require 'rutl/version'
3
+ #
4
+ # TODO: Rename to something better. RubyUI2API? RAPID for Ruby API DSL?
5
+ # The idea is that this framework should be usable for web, phone, and even
6
+ # desktop UI testing, turning the UI into an API via its DSL.
7
+ #
8
+ module RUTL
9
+ # Your code goes here...
10
+ end
data/rutl.gemspec ADDED
@@ -0,0 +1,44 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'rutl'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'rutl'
7
+ spec.version = RUTL::VERSION
8
+ spec.authors = ['Drew Cooper']
9
+ spec.email = ['drewcoo@gmail.com']
10
+
11
+ spec.summary = 'Ruby Ui Test Library'
12
+ spec.description = 'One gem to test them all and in the darkness bug them.'
13
+ spec.homepage = 'https://github.com/drewcoo/rutl'
14
+ spec.license = 'MIT'
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org/gems/rutl'
20
+ else
21
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
22
+ 'public gem pushes.'
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.bindir = 'exe'
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ['lib']
31
+
32
+ spec.add_development_dependency 'bundler', '~> 1.15'
33
+ if ENV['CIRCLECI'].nil?
34
+ spec.add_development_dependency 'chromedriver-helper', '~> 1.2'
35
+ end
36
+ spec.add_development_dependency 'coveralls' #@, require: false
37
+ #spec.add_development_dependency 'geckodriver-helper', '~> 0.20'
38
+ spec.add_development_dependency 'gem-release'
39
+ spec.add_development_dependency 'rake', '~> 12.3'
40
+ spec.add_development_dependency 'rspec', '~> 3.0'
41
+ spec.add_development_dependency 'rspec_junit_formatter', '~> 0.3'
42
+ spec.add_development_dependency 'rubocop', '~> 0.55'
43
+ spec.add_development_dependency 'selenium-webdriver', '~> 3.12'
44
+ end
metadata ADDED
@@ -0,0 +1,202 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rutl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Drew Cooper
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-05-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: chromedriver-helper
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: coveralls
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: gem-release
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '12.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '12.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec_junit_formatter
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.3'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.3'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.55'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.55'
125
+ - !ruby/object:Gem::Dependency
126
+ name: selenium-webdriver
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.12'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.12'
139
+ description: One gem to test them all and in the darkness bug them.
140
+ email:
141
+ - drewcoo@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".circleci/config.yml"
147
+ - ".coveralls.yml"
148
+ - ".gitignore"
149
+ - ".rspec"
150
+ - ".rubocop.yml"
151
+ - ".travis.yml"
152
+ - Gemfile
153
+ - LICENSE.txt
154
+ - README.md
155
+ - Rakefile
156
+ - bin/console
157
+ - bin/setup
158
+ - lib/rutl.rb
159
+ - lib/rutl/base_page.rb
160
+ - lib/rutl/browser.rb
161
+ - lib/rutl/driver/null_driver.rb
162
+ - lib/rutl/driver/null_driver_page_element.rb
163
+ - lib/rutl/interface/base_interface.rb
164
+ - lib/rutl/interface/chrome_interface.rb
165
+ - lib/rutl/interface/elements.rb
166
+ - lib/rutl/interface/elements/base_element.rb
167
+ - lib/rutl/interface/elements/button.rb
168
+ - lib/rutl/interface/elements/checkbox.rb
169
+ - lib/rutl/interface/elements/click_to_change_state_mixin.rb
170
+ - lib/rutl/interface/elements/link.rb
171
+ - lib/rutl/interface/elements/text.rb
172
+ - lib/rutl/interface/firefox_interface.rb
173
+ - lib/rutl/interface/null_interface.rb
174
+ - lib/rutl/utilities.rb
175
+ - lib/rutl/version.rb
176
+ - rutl.gemspec
177
+ homepage: https://github.com/drewcoo/rutl
178
+ licenses:
179
+ - MIT
180
+ metadata:
181
+ allowed_push_host: https://rubygems.org/gems/rutl
182
+ post_install_message:
183
+ rdoc_options: []
184
+ require_paths:
185
+ - lib
186
+ required_ruby_version: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ required_rubygems_version: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: '0'
196
+ requirements: []
197
+ rubyforge_project:
198
+ rubygems_version: 2.7.6
199
+ signing_key:
200
+ specification_version: 4
201
+ summary: Ruby Ui Test Library
202
+ test_files: []