chemlab 0.4.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +8 -0
  3. data/bin/chemlab +10 -0
  4. data/bin/chemlab-suite +1 -0
  5. data/bin/chemlab-test +1 -0
  6. data/lib/chemlab.rb +27 -17
  7. data/lib/chemlab/attributable.rb +16 -10
  8. data/lib/chemlab/cli/fixtures/new_library/.gitignore +63 -0
  9. data/lib/chemlab/cli/fixtures/new_library/Gemfile +5 -0
  10. data/lib/chemlab/cli/fixtures/new_library/README.md.erb +1 -0
  11. data/lib/chemlab/cli/fixtures/new_library/lib/new_library.rb.erb +7 -0
  12. data/lib/chemlab/cli/fixtures/new_library/lib/page/sample.rb.erb +9 -0
  13. data/lib/chemlab/cli/fixtures/new_library/new_library.gemspec.erb +23 -0
  14. data/lib/chemlab/cli/fixtures/new_library/spec/integration/page/sample_spec.rb.erb +17 -0
  15. data/lib/chemlab/cli/fixtures/new_library/spec/unit/page/sample_spec.rb.erb +19 -0
  16. data/lib/chemlab/cli/generator.rb +46 -0
  17. data/lib/chemlab/cli/generator/templates/page.erb +3 -0
  18. data/lib/chemlab/cli/new_library.rb +62 -0
  19. data/lib/chemlab/cli/stub.erb +66 -0
  20. data/lib/chemlab/cli/stubber.rb +74 -0
  21. data/lib/chemlab/component.rb +78 -8
  22. data/lib/chemlab/configuration.rb +60 -12
  23. data/lib/chemlab/element.rb +4 -0
  24. data/lib/chemlab/page.rb +19 -1
  25. data/lib/chemlab/runtime/browser.rb +13 -18
  26. data/lib/chemlab/runtime/env.rb +13 -9
  27. data/lib/chemlab/runtime/logger.rb +16 -13
  28. data/lib/chemlab/version.rb +1 -1
  29. data/lib/tasks/generate.rake +22 -0
  30. data/lib/tasks/generate_stubs.rake +20 -0
  31. data/lib/tasks/help.rake +24 -0
  32. data/lib/tasks/new.rake +19 -0
  33. data/lib/tasks/version.rake +8 -0
  34. metadata +113 -57
  35. data/lib/chemlab/api_fabricator.rb +0 -134
  36. data/lib/chemlab/resource.rb +0 -169
  37. data/lib/chemlab/runtime/api_client.rb +0 -18
  38. data/lib/chemlab/support/api.rb +0 -71
  39. data/lib/chemlab/support/logging.rb +0 -176
  40. data/lib/chemlab/support/repeater.rb +0 -65
  41. data/lib/chemlab/support/waiter.rb +0 -39
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../chemlab'
4
+ require 'erb'
5
+
6
+ module Chemlab
7
+ module CLI
8
+ # This class is dedicated to stubbing Page Libraries
9
+ class Stubber
10
+ class << self
11
+ def libraries
12
+ @libraries ||= []
13
+ end
14
+
15
+ # Generate all stubs in a particular path
16
+ def stub_all(path)
17
+ Array(path).each do |p|
18
+ p = File.expand_path(p)
19
+
20
+ PageLibrary.new(p).generate_stub if File.file?(p)
21
+ Dir["#{p}/**/*.rb"].each do |f|
22
+ next if File.basename(f).include?('.stub.')
23
+
24
+ PageLibrary.new(f).generate_stub
25
+ end
26
+ end
27
+ end
28
+
29
+ # For each page that extends Chemlab::Page, run the stubber
30
+ class Chemlab::Page
31
+ def self.inherited(subclass)
32
+ print subclass
33
+
34
+ Stubber.libraries << subclass
35
+ end
36
+ end
37
+ end
38
+
39
+ # A Class representation of a Page Library
40
+ class PageLibrary
41
+ attr_accessor :path
42
+
43
+ def initialize(path)
44
+ @path = File.absolute_path(path)
45
+ end
46
+
47
+ # Generate the stub for a given Page Library
48
+ def generate_stub
49
+ $stdout.print "- #{@path} :\t"
50
+
51
+ begin
52
+ load @path
53
+
54
+ library = Stubber.libraries.last # last appended library is this Page Library
55
+
56
+ require 'active_support/core_ext/string/inflections'
57
+
58
+ stub_path = @path.gsub(@path[@path.rindex('.')..], '.stub.rb')
59
+ File.open(stub_path, 'w') do |stub|
60
+ stub.write(ERB.new(File.read(File.expand_path("#{__dir__}/stub.erb")),
61
+ trim_mode: '%<>').result_with_hash({ library: library }))
62
+ Stubber.libraries.pop
63
+ end
64
+ rescue StandardError => e
65
+ # $stderr.print(e.message)
66
+ raise e
67
+ ensure
68
+ $stdout.print "\tDone.\n"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'forwardable'
4
+
3
5
  module Chemlab
4
6
  # The base representation of *any* UI component.
5
7
  class Component
@@ -8,25 +10,54 @@ module Chemlab
8
10
  def_delegator :browser, :browser
9
11
  def_delegators :evaluator, :attribute
10
12
 
13
+ # Perform actions against the given page
14
+ # @example
15
+ # Component.perform do |component|
16
+ # component.do_something
17
+ # end
18
+ # @example
19
+ # Component.perform(&:do_something)
20
+ # @return The instance of +Component+ used to perform the action
11
21
  def self.perform
12
22
  yield new if block_given?
13
23
  end
14
24
 
15
25
  class << self
26
+ # Elements defined on the page
27
+ # @example
28
+ # +text_field :username, id: 'username'+
29
+ # Will be in form:
30
+ # {
31
+ # type: :text_field,
32
+ # name: :username,
33
+ # args: [ { id: 'username' } ]
34
+ # }
35
+ # @return [Array]
36
+ def public_elements
37
+ @public_elements ||= []
38
+ end
39
+
40
+ # rubocop:disable Metrics/BlockLength
16
41
  Watir::Container.instance_methods(false).each do |watir_method|
42
+ # For each Watir element (h1, text_field, button, text_field, etc)
17
43
  define_method watir_method do |name, *args, &block|
18
44
  next if public_instance_methods.include? name
19
45
 
46
+ public_elements << { type: watir_method, name: name, args: args }
47
+
48
+ # @return [Watir::Element] the raw Watir element
20
49
  define_method("#{name}_element") do
21
50
  find_element(watir_method, name, args.first, &block)
22
51
  end
23
52
 
53
+ # @return [Boolean] true if the element is present
24
54
  define_method("#{name}?") do
25
- send("#{name}_element").present?
55
+ public_send("#{name}_element").present?
26
56
  end
27
57
 
58
+ # === GETTER / CLICKER === #
28
59
  define_method(name) do
29
- element = send("#{name}_element")
60
+ element = public_send("#{name}_element")
30
61
  if Element::CLICKABLES.include? watir_method
31
62
  element.wait_until(&:present?).click
32
63
  elsif Element::INPUTS.include? watir_method
@@ -36,22 +67,41 @@ module Chemlab
36
67
  end
37
68
  end
38
69
 
39
- define_method("#{name}=") do |val|
40
- if Element::SELECTABLES.include? watir_method
41
- send("#{name}_element").select val
42
- else
43
- send("#{name}_element").set val
70
+ if (Element::SELECTABLES + Element::INPUTS).include?(watir_method)
71
+ # === SETTER === #
72
+ define_method("#{name}=") do |val|
73
+ if Element::SELECTABLES.include? watir_method
74
+ public_send("#{name}_element").select val
75
+ else
76
+ public_send("#{name}_element").set val
77
+ end
44
78
  end
45
79
  end
46
80
  end
47
81
  end
82
+ # rubocop:enable Metrics/BlockLength
48
83
  end
49
84
 
85
+ # If this component is currently visible
86
+ # @return [Boolean] true if the defined elements in the library are present
87
+ # @note The page presence is determined by the elements defined on the page and their requirement
88
+ def visible?
89
+ missing_elements = [] + self.class.public_elements
90
+
91
+ self.class.public_elements.each do |element|
92
+ missing_elements.shift if has_element?(element[:type], element[:name], element[:args].first)
93
+ end
94
+
95
+ missing_elements.none?
96
+ end
97
+
98
+ # DSL for both components and resources.
50
99
  class DSL
51
100
  def initialize(base)
52
101
  @base = base
53
102
  end
54
103
 
104
+ # An attribute to define for a component or resource
55
105
  def attribute(name)
56
106
  @base.module_eval do
57
107
  attr_writer(name)
@@ -76,12 +126,32 @@ module Chemlab
76
126
 
77
127
  private
78
128
 
129
+ # Find an element on the page
130
+ # @note this method will wait for the element to appear
131
+ # @see [has_element]
132
+ # @api private
133
+ # @example
134
+ # #find_element(:text_field, :username) #=>
79
135
  def find_element(watir_method, name, locator = nil, &block)
80
136
  locator = { css: %([data-qa-selector="#{name}"]) } if locator.nil?
81
137
 
82
138
  return instance_exec(&block) if block_given?
83
139
 
84
- Chemlab.configuration.browser.session.engine.send(watir_method, locator).wait_until(&:exist?)
140
+ Chemlab.configuration.browser.session.engine.public_send(watir_method, locator).wait_until(&:exist?)
141
+ end
142
+
143
+ protected
144
+
145
+ # Check existence of an element on the page
146
+ # @note this method will _not_ wait for the element
147
+ # @see [find_element]
148
+ # @api private
149
+ # @example
150
+ # #has_element?(:text_field, :username) => returns false if the element is is not there
151
+ def has_element?(watir_method, name, locator = nil)
152
+ locator = { css: %([data-qa-selector="#{name}"]) } if locator.nil?
153
+
154
+ Chemlab.configuration.browser.session.engine.public_send(watir_method, locator).present?
85
155
  end
86
156
  end
87
157
  end
@@ -3,20 +3,77 @@
3
3
  module Chemlab
4
4
  # Chemlab Configuration
5
5
  class Configuration
6
+ include Runtime::Logger
7
+
8
+ # Chemlab Terminal Banner
9
+ BANNER = <<~'BANNER'
10
+
11
+ ## ##
12
+ #### ####
13
+ ###### ######
14
+ ####### #######
15
+ ######## #########
16
+ ########## #########
17
+ ########## ###########
18
+ ############ ############
19
+ ##########/------------------------------------------\#########
20
+ ##########/ / ___| |__ ___ _ __ ___ | | __ _| |__ \#########
21
+ #########| | | | '_ \ / _ \ '_ ` _ \| |/ _` | '_ \ |########
22
+ ##########| | |___| | | | __/ | | | | | | (_| | |_) | |#########
23
+ ###########\ \____|_| |_|\___|_| |_| |_|_|\__,_|_.__/ /###########
24
+ #############\------------------------------------------/#############
25
+ \*********************############################********************/
26
+ \\\********************##########################********************///
27
+ \\\\\\******************#########################*******************//////
28
+ \\\\\\\\******************########################******************////////
29
+ \\\\\\\\\\*****************######################*****************//////////
30
+ \\\\\\\\\\\\****************#####################****************/////////////
31
+ \\\\\\\\\\\\\\\***************####################***************///////////////
32
+ \\\\\\\\\\\\\\\\***************##################**************/////////////////
33
+ \\\\\\\\\\\\\\\\\*************#################**************/////////////////
34
+ \\\\\\\\\\\\\\\*************################************////////////////
35
+ \\\\\\\\\\\\\\************##############************//////////////
36
+ \\\\\\\\\\\\\\**********#############***********/////////////
37
+ \\\\\\\\\\\\**********############**********////////////
38
+ \\\\\\\\\\\********###########*********///////////
39
+ \\\\\\\\\********#########********//////////
40
+ \\\\\\\\\*******########*******////////
41
+ \\\\\\\*******######******////////
42
+ \\\\\\*****######*****//////
43
+ \\\\\****####****/////
44
+ \\\\***###***////
45
+ \\\**##**///
46
+ \****/
47
+ **
48
+ BANNER
49
+
50
+ def initialize
51
+ yield self if block_given?
52
+
53
+ log(BANNER, :begn) unless hide_banner
54
+ log(<<-CONF, :conf)
55
+
56
+ ==> Base URL: #{base_url}
57
+ ==> Browser: #{browser}
58
+ ==> Libraries: #{libraries}
59
+ CONF
60
+ end
61
+
6
62
  # Add a chemlab configuration
7
63
  def self.add_config(name)
8
64
  attr_accessor name
9
65
  end
10
66
 
11
67
  add_config :base_url
68
+ add_config :hide_banner
69
+
70
+ attr_reader :browser, :libraries
12
71
 
13
- attr_reader :browser
14
72
  # Set the browser and browser arguments Chemlab should use
15
73
  def browser=(browser)
16
74
  @browser = Runtime::Browser.new(browser)
17
75
  end
18
76
 
19
- attr_reader :libraries
20
77
  # Specify which libraries to load
21
78
  def libraries=(libraries = [])
22
79
  @libraries = Chemlab.const_set('Vendor', Module.new)
@@ -31,16 +88,7 @@ module Chemlab
31
88
  RSpec.configure do |rspec|
32
89
  yield rspec if block_given?
33
90
 
34
- # TODO Change this. /spec/api /spec/ui is hardcoded
35
- rspec.define_derived_metadata(file_path: Regexp.new('/spec/api')) do |metadata|
36
- metadata[:type] = :api
37
- end
38
-
39
- rspec.define_derived_metadata(file_path: Regexp.new('/spec/ui')) do |metadata|
40
- metadata[:type] = :ui
41
- end
42
-
43
- rspec.after(:each, type: :ui) do
91
+ rspec.after(:each) do
44
92
  Chemlab.configuration.browser&.session&.engine&.quit
45
93
  end
46
94
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chemlab
4
+ # Element types
4
5
  module Element
6
+ # Elements that can be clicked
5
7
  CLICKABLES = %i[
6
8
  a
7
9
  button
@@ -11,10 +13,12 @@ module Chemlab
11
13
  radio
12
14
  ].freeze
13
15
 
16
+ # Elements that are +<select>+
14
17
  SELECTABLES = %i[
15
18
  select
16
19
  ].freeze
17
20
 
21
+ # Text input elements
18
22
  INPUTS = %i[
19
23
  text_field
20
24
  text_area
data/lib/chemlab/page.rb CHANGED
@@ -3,11 +3,29 @@
3
3
  module Chemlab
4
4
  # Representation of a Page on the UI
5
5
  class Page < Component
6
-
7
6
  attribute :path
8
7
 
8
+ # Visit the given page, specified by +path '/the_path'+
9
9
  def visit
10
10
  Runtime::Browser.navigate_to(self.class)
11
11
  end
12
+
13
+ # Check that the current Path segment of the URL matches the Path of the page
14
+ # @return [Boolean] true if the path segment matches
15
+ # @example
16
+ # class Page
17
+ # path '/test'
18
+ # end
19
+ #
20
+ # Page.perform do |page|
21
+ # page.visit
22
+ # expect(page).to be_on_page #=> passes if the path segment of the browser URL matches /test
23
+ # end
24
+ def on_page?
25
+ URI(Chemlab.configuration.browser.url).path&.match?(self.class.path)
26
+ rescue TypeError
27
+ # it's likely there is no path because of the page using the data protocol e.g.: "data:,"
28
+ false
29
+ end
12
30
  end
13
31
  end
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'forwardable'
4
+
3
5
  module Chemlab
4
6
  module Runtime
5
7
  # The browser configurator
6
8
  class Browser
7
- extend SingleForwardable
8
- include ActiveSupport::Configurable
9
+ extend Forwardable
9
10
  attr_accessor :session
10
11
  attr_reader :browser_options
11
12
 
13
+ def_delegators :session, :url, :refresh, :text, :quit
14
+
12
15
  def initialize(browser_options)
13
16
  @browser_options = browser_options
14
17
  @session = Session.new(browser_options)
@@ -23,32 +26,24 @@ module Chemlab
23
26
  @session.engine.goto(Chemlab.configuration.base_url + path)
24
27
  end
25
28
 
29
+ # The options used to create the browser session
30
+ def to_s
31
+ @browser_options.to_s
32
+ end
33
+
26
34
  # An individual session
27
35
  class Session
36
+ extend Forwardable
28
37
  attr_reader :engine
29
38
 
39
+ def_delegators :engine, :url, :refresh, :text, :quit
40
+
30
41
  def initialize(browser)
31
42
  @engine = Watir::Browser.new(*browser)
32
43
 
33
44
  # @engine.goto(Chemlab.configuration.base_url)
34
45
  end
35
46
 
36
- def current_url
37
- engine.url
38
- end
39
-
40
- def refresh
41
- engine.refresh
42
- end
43
-
44
- def text
45
- engine.text
46
- end
47
-
48
- def quit
49
- engine.close
50
- end
51
-
52
47
  def save_screenshot(file_name)
53
48
  engine.screenshot.save(file_name)
54
49
  end
@@ -4,18 +4,22 @@ module Chemlab
4
4
  module Runtime
5
5
  # Environment configuration file
6
6
  module Env
7
- module_function
7
+ class << self
8
+ # @return [Boolean] true if debug mode is enabled
9
+ # @example
10
+ # ENV['CHEMLAB_DEBUG'] = 'true'
11
+ # Runtime::Env.debug? #=> true
12
+ def debug?
13
+ enabled?(ENV['CHEMLAB_DEBUG'], default: false)
14
+ end
8
15
 
9
- def debug?
10
- enabled?(ENV['DEBUG'], default: false)
11
- end
12
-
13
- private
16
+ private
14
17
 
15
- def enabled?(value, default: true)
16
- return default if value.nil?
18
+ def enabled?(value, default: true)
19
+ return default if value.nil?
17
20
 
18
- (value =~ /^(false|no|0)$/i) != 0
21
+ (value =~ /^(false|no|n|0)$/i) != 0
22
+ end
19
23
  end
20
24
  end
21
25
  end