rutl 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []