rutl 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '03966911ea3475762f121b4735aaee43a66af675c6b5baa255e329810c5fd890'
4
- data.tar.gz: 4fbc7ec877d27b7daddae7f02c5b35396d9b6a5f59ffee90a5e4a29896627e1c
3
+ metadata.gz: 67625dcf7a3007342c0ea59bedc973fb833ff74ffd41eec945b0c2dd6f1022ef
4
+ data.tar.gz: 22f30517ae2fc0f2c33263db4443285a04f12d212174a7519f0bc58b07006283
5
5
  SHA512:
6
- metadata.gz: 1cfe7eecf5fb7ab352d10408412835b827b7cb276959efeac9085a538d008bd8a829ad468b158135353b85c4a9277a6af9cab5eae200932667c10ce425c6e2e5
7
- data.tar.gz: ab287a5457fd4b3fe81ed869b3316f3165c1f97fbd92c3af04c42a935366488911f5e0b51d335c6c89621817ada69d766eec157a00f8db0317d1f7271475d095
6
+ metadata.gz: 55d5a134ee68df90af1621f591852cba3a4fa362c3bd37fee884b913826ea2847c9784a786d1dc5478f4b5390a82acd2138263cc304afd2a91158ccee32423ae
7
+ data.tar.gz: d0600c7d541a0a226e635d94421d899dff5998a386decd0b71efad990e9ef4a8f36f8f68708991af6cceb5926c85aaee47fd98fd9d8191d412833407d1b41dca
@@ -7,7 +7,8 @@ jobs:
7
7
  build:
8
8
  docker:
9
9
  # specify the version you desire here
10
- - image: circleci/ruby:2.4.1-node-browsers
10
+ - image: circleci/ruby:2.5.1-stretch-node-browsers
11
+ #- image: circleci/ruby:2.4.1-node-browsers
11
12
 
12
13
  # Specify service dependencies here if necessary
13
14
  # CircleCI maintains a library of pre-built images
@@ -71,4 +72,3 @@ jobs:
71
72
  - store_artifacts:
72
73
  path: /tmp/test-results
73
74
  destination: test-results
74
-
@@ -42,3 +42,10 @@ Style/ClassVars:
42
42
  Exclude:
43
43
  - 'lib/rutl/driver/null_driver_page_element.rb'
44
44
  - 'lib/rutl/base_page.rb'
45
+
46
+ # Rubocop flags lots of things as useless assignment when they're actually
47
+ # magic methods. Maybe this means I'm not handling respond_to_missing
48
+ # correctly. In fact, that seems likely.
49
+ Lint/UselessAssignment:
50
+ Exclude:
51
+ - 'spec/*_spec.rb'
@@ -1,23 +1,29 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2018-06-04 00:16:13 -0700 using RuboCop version 0.56.0.
3
+ # on 2018-06-07 12:55:41 -0700 using RuboCop version 0.56.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Offense count: 1
10
+ # Configuration parameters: CountComments.
11
+ Metrics/MethodLength:
12
+ Max: 11
13
+
9
14
  # Offense count: 4
10
15
  # Configuration parameters: AllowedVariables.
11
16
  Style/GlobalVars:
12
17
  Exclude:
13
18
  - 'lib/rutl/browser.rb'
14
- - 'lib/rutl/interface/elements/base_element.rb'
19
+ - 'lib/rutl/interface/elements/element.rb'
15
20
  - 'lib/rutl/interface/elements/element_context.rb'
16
21
  - 'lib/rutl/interface/null_interface.rb'
17
22
 
18
- # Offense count: 3
23
+ # Offense count: 4
19
24
  Style/MethodMissingSuper:
20
25
  Exclude:
21
- - 'lib/rutl/base_page.rb'
22
26
  - 'lib/rutl/browser.rb'
23
27
  - 'lib/rutl/interface/base_interface.rb'
28
+ - 'lib/rutl/interface/elements/element.rb'
29
+ - 'lib/rutl/page.rb'
@@ -5,3 +5,4 @@ language: ruby
5
5
  rvm:
6
6
  - 2.3.1
7
7
  before_install: gem install bundler -v 1.13.6
8
+ script: bundle exec rspec
data/README.md CHANGED
@@ -169,8 +169,7 @@ your tests screenshot anyway, just less magic.
169
169
 
170
170
  ## Roadmap
171
171
  Coming up soon in almost no order:
172
- * Handle error pages/partials.
173
- * Auto-screenshot on errors. Error destinations. Navigation errors. Unexpected exceptions?
172
+ * Handle other errors. Auto-screenshot on errors. Navigation errors. Unexpected exceptions?
174
173
  * A test framework should have better tests.
175
174
  * Diff screenshots. Make this smart so we don't have to be experts.
176
175
  * Put more info in this readme.
@@ -1,8 +1,11 @@
1
+ require 'utilities'
1
2
  #
2
3
  # Additional RSpec matchers specific to this framework go here.
3
4
  #
5
+
6
+ # Is it the expected page?
4
7
  RSpec::Matchers.define :be_page do |expected|
5
8
  match do |actual|
6
- actual.is_a?(expected) && expected.ancestors.include?(BasePage)
9
+ actual.is_a?(expected) && page?(expected)
7
10
  end
8
11
  end
@@ -9,13 +9,13 @@ module RUTL
9
9
  # Should define RUTL::PAGES directory for your code
10
10
  # or set ENV['RUTL_PAGES']
11
11
  # or Browser intialize will raise.
12
- PAGES = nil
12
+ # PAGES = nil
13
13
 
14
14
  # If this RUTL::SCREENSHOT_DIR or ENV['SCREENSHOT_DIR']
15
15
  # or Browser initialize is set, we take screenshots.
16
- SCREENSHOTS = nil
16
+ # SCREENSHOTS = nil
17
17
 
18
18
  # This one is for diffing against.
19
19
  # RUTL::KNOWN_GOOD_SCREENSHOTS
20
- REFERENCE_SCREENSHOTS = nil
20
+ # REFERENCE_SCREENSHOTS = nil
21
21
  end
@@ -1,65 +1,70 @@
1
- require 'rutl/utilities'
2
- require 'rutl/base_page'
1
+ require 'utilities'
2
+ require 'rutl/page'
3
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
4
+ module RUTL
5
+ #
6
+ # Currently called Browser, this top-level class controls a browser and
7
+ # a fake browser. It will soon call into apps, at which point I need to
8
+ # rethink this naming convention.
9
+ #
10
+ class Browser
11
+ include Utilities
11
12
 
12
- attr_reader :interface
13
+ attr_reader :interface
13
14
 
14
- def initialize(type:, rutl_pages: RUTL::PAGES || ENV['RUTL_PAGES'])
15
- if rutl_pages.nil? || rutl_pages.empty?
16
- raise "Set RUTL::PAGES or ENV['RUTL_PAGES'] or pass dir as rutl_pages:"
15
+ def initialize(type:, rutl_pages: RUTL::PAGES || ENV['RUTL_PAGES'])
16
+ if rutl_pages.nil? || rutl_pages.empty?
17
+ raise "Set RUTL::PAGES or ENV['RUTL_PAGES'] or pass dir as rutl_pages:"
18
+ end
19
+ # This is kind of evil. Figure out how to ditch the $ variable.
20
+ $browser = self
21
+ @interface = nil # TODO: Why this line? Do I need to do this?
22
+ @interface = load_interface(type)
23
+ @interface.pages = load_pages(dir: rutl_pages)
17
24
  end
18
- # This is kind of evil. Figure out how to ditch the $ variable.
19
- $browser = self
20
- @interface = nil
21
- @interface = load_interface(type)
22
- @interface.pages = load_pages(dir: rutl_pages)
23
- end
24
-
25
- def load_interface(type)
26
- require "rutl/interface/#{type}_interface"
27
- klass = "#{type.to_s.capitalize}Interface"
28
- Object.const_get(klass).new
29
- end
30
25
 
31
- # Ugly. Requires files for page objects. Returns array of class names to load.
32
- def require_pages(dir: 'spec/pages')
33
- names = []
34
- Dir["#{dir}/*"].each do |file|
35
- require "rutl/../../#{file}"
36
- File.open(file).each do |line|
37
- bingo = line.match(/class (.*) < BasePage/)
38
- names << bingo[1] if bingo && bingo[1]
26
+ def method_missing(method, *args, &block)
27
+ if args.empty?
28
+ @interface.send(method)
29
+ else
30
+ @interface.send(method, *args, &block)
39
31
  end
40
32
  end
41
- names
42
- end
43
33
 
44
- def load_pages(*)
45
- pages = []
46
- require_pages.each do |klass|
47
- # Don't have @interface set yet.
48
- # That would have been the param to new, :interface.
49
- pages << Object.const_get(klass).new(@interface)
34
+ def respond_to_missing?(*args)
35
+ @interface.respond_to?(*args)
50
36
  end
51
- pages
52
- end
53
37
 
54
- def method_missing(method, *args, &block)
55
- if args.empty?
56
- @interface.send(method)
57
- else
58
- @interface.send(method, *args, &block)
38
+ private
39
+
40
+ def load_interface(type)
41
+ require "rutl/interface/#{type}_interface"
42
+ klass = "RUTL::#{type.to_s.capitalize}Interface"
43
+ Object.const_get(klass).new
44
+ end
45
+
46
+ def load_pages(*)
47
+ pages = []
48
+ require_pages.each do |klass|
49
+ # Don't have @interface set yet.
50
+ # That would have been the param to new, :interface.
51
+ pages << Object.const_get(klass).new(@interface)
52
+ end
53
+ pages
59
54
  end
60
- end
61
55
 
62
- def respond_to_missing?(*args)
63
- @interface.respond_to?(*args)
56
+ # Ugly. Requires files for page objects.
57
+ # Returns array of class names to load.
58
+ def require_pages(dir: 'spec/pages')
59
+ names = []
60
+ Dir["#{dir}/*"].each do |file|
61
+ require "rutl/../../#{file}"
62
+ File.open(file).each do |line|
63
+ bingo = line.match(/class (.*) < RUTL::Page/)
64
+ names << bingo[1] if bingo && bingo[1]
65
+ end
66
+ end
67
+ names
68
+ end
64
69
  end
65
70
  end
@@ -0,0 +1,74 @@
1
+ require 'fileutils'
2
+
3
+ module RUTL
4
+ #
5
+ # class to take photos of the screen (and diff them?)
6
+ #
7
+ class Camera
8
+ def guard
9
+ # When running headless, Selenium seems not to drop screenshots.
10
+ # So that makes this safe in places like Travis.
11
+ #
12
+ # We still need to guard against NullDriver or we'll to to screencap
13
+ # it when we're running head-fully.
14
+ #
15
+ # Will there be others?
16
+ @driver.is_a? RUTL::NullDriver
17
+ end
18
+
19
+ def initialize(driver, base_name: '')
20
+ @counter = 0
21
+ @driver = driver
22
+ return if guard
23
+ @base_name = base_name.sub('RUTL::', '')
24
+ @dir = File.join(RUTL::SCREENSHOTS, @base_name)
25
+ FileUtils.mkdir_p @dir
26
+ end
27
+
28
+ def shoot(path = nil)
29
+ return if guard
30
+ # Magic path is used for all auto-screenshots.
31
+ name = path || magic_path
32
+
33
+ FileUtils.mkdir_p @dir
34
+ file = File.join(@dir, pathify(name))
35
+ @driver.save_screenshot(file)
36
+ end
37
+ alias screenshot shoot
38
+
39
+ def clean_dir(dir)
40
+ FileUtils.rm_rf dir
41
+ FileUtils.mkdir_p dir
42
+ end
43
+
44
+ def counter
45
+ @counter += 1
46
+ # In the unlikely even that we have > 9 screenshots in a test case,
47
+ # format the counter to be two digits, zero padded.
48
+ format('%02d', @counter)
49
+ end
50
+
51
+ def magic_path
52
+ if defined? RSpec
53
+ RSpec.current_example.metadata[:full_description].to_s
54
+ else
55
+ # TODO: The behavior for non-RSpec users is ugly and broken.
56
+ # Each new test case will start taking numbered "auto-screenshot" pngs.
57
+ # And the next test case will overwrite them. Even if they didn't
58
+ # overwrite, I don't know how to correllate tests w/ scrrenshots. I'm
59
+ # leaving this broken for now.
60
+ # You can still tell it to take your own named screenshots whenever you
61
+ # like, of course.
62
+ 'auto-screenshot'
63
+ end
64
+ end
65
+
66
+ def pathify(path)
67
+ # Replace any of these with an underscore:
68
+ # space, octothorpe, slash, backslash, colon, period
69
+ name = path.gsub(%r{[ \#\/\\\:\.]}, '_')
70
+ # Also insert a counter and make sure we end with .png.
71
+ name.sub(/.png$/, '') + '_' + counter + '.png'
72
+ end
73
+ end
74
+ end
@@ -1,77 +1,90 @@
1
- require 'rutl/utilities'
2
- require 'rutl/screencam'
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
1
+ require 'utilities'
2
+ require 'rutl/camera'
9
3
 
10
- attr_accessor :driver
11
- attr_reader :camera
12
- attr_accessor :pages
4
+ module RUTL
5
+ #
6
+ # I might need to consider renaming these.
7
+ # The *interface classes lie between Browser and the webdriver-level classes.
8
+ #
9
+ class BaseInterface
10
+ include Utilities
13
11
 
14
- def initialize
15
- raise 'Child interface class must set @driver.' if @driver.nil?
16
- # base_name avoids collisions when unning the same tests with
17
- # different browsers.
18
- name = self.class.to_s .sub('Interface', '')
19
- @camera = ScreenCam.new(@driver, base_name: name)
20
- end
12
+ # RUTL::Driver
13
+ attr_accessor :driver
21
14
 
22
- def goto(page)
23
- raise 'expect Page class' unless page.ancestors.include?(BasePage)
24
- find_page(page).go_to_here
25
- @camera.screenshot
26
- end
15
+ # RUTL::Camera
16
+ attr_accessor :camera
27
17
 
28
- def current_page
29
- raise 'define in child classes'
30
- end
18
+ # Array of all RUTL::Page classes
19
+ attr_accessor :pages
31
20
 
32
- def method_missing(method, *args, &block)
33
- if args.empty?
34
- current_page.send(method)
35
- else
36
- current_page.send(method, *args, &block)
21
+ def initialize
22
+ raise 'Child interface class must set @driver.' if @driver.nil?
23
+ # base_name avoids collisions when unning the same tests with
24
+ # different browsers.
25
+ name = self.class.to_s.sub('RUTL::Interface', '')
26
+ @camera = Camera.new(@driver, base_name: name)
37
27
  end
38
- end
39
28
 
40
- # TODO: Is this needed? I not only find the page but also make sure the
41
- # urls match. Even though that's what finding pages means?
42
- def find_state(target_states)
43
- target_states.each do |state|
44
- next unless state.url == current_page.url
45
- page = find_page(state)
46
- return page if page.loaded?(@driver)
29
+ # Attempts to navigate to the page.
30
+ # Takes screenshot if successful.
31
+ def goto(page)
32
+ raise 'expect Page class' unless page?(page)
33
+ find_page(page).go_to_here
34
+ @camera.screenshot
47
35
  end
48
- false
49
- end
50
36
 
51
- def find_page(page)
52
- @pages.each do |p|
53
- return p if page?(page) && p.class == page
54
- return p if String == page.class && page == p.url
37
+ # Should define in children; raises here.
38
+ # Should return the current page class.
39
+ def current_page
40
+ raise 'define in child classes'
55
41
  end
56
- raise "Page \"#{page}\" not found in pages #{@pages}"
57
- end
58
42
 
59
- def wait_for_transition(target_states)
60
- #
61
- # TODO: Should also see if there are other things to wait for.
62
- # I don't think this is doing page load time.
63
- #
64
- await -> { find_state target_states }
65
- end
43
+ def method_missing(method, *args, &block)
44
+ if args.empty?
45
+ current_page.send(method)
46
+ else
47
+ current_page.send(method, *args, &block)
48
+ end
49
+ end
66
50
 
67
- def respond_to_missing?(*args)
68
- # This can't be right. Figure it out later.
69
- current_page.respond_to?(*args)
70
- end
51
+ # TODO: Is this needed? I not only find the page but also make sure the
52
+ # urls match. Even though that's what finding pages means?
53
+ def find_state(target_states)
54
+ target_states.each do |state|
55
+ next unless state.url == current_page.url
56
+ page = find_page(state)
57
+ return page if page.loaded?
58
+ end
59
+ false
60
+ end
61
+
62
+ # Attempts to find page by class or url.
63
+ def find_page(page)
64
+ @pages.each do |p|
65
+ return p if page?(page) && p.class == page
66
+ return p if String == page.class && page == p.url
67
+ end
68
+ raise "Page \"#{page}\" not found in pages #{@pages}"
69
+ end
70
+
71
+ # Calls the polling utility mathod await() with a lambda trying to
72
+ # find the next state, probably a Page class.
73
+ def wait_for_transition(target_states)
74
+ #
75
+ # TODO: Should also see if there are other things to wait for.
76
+ # I don't think this is doing page load time.
77
+ #
78
+ await -> { find_state target_states }
79
+ end
80
+
81
+ def respond_to_missing?(*args)
82
+ # This can't be right. Figure it out later.
83
+ current_page.respond_to?(*args)
84
+ end
71
85
 
72
- def quit
73
- @driver.quit
74
- # Maybe I'm reusing pages from @pages?
75
- # @pages = []
86
+ def quit
87
+ @driver.quit
88
+ end
76
89
  end
77
90
  end