chemlab 0.5.0 → 0.6.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +6 -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 +11 -13
  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 +65 -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 -17
  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 +87 -49
  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,13 +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
9
+ extend Forwardable
8
10
  attr_accessor :session
9
11
  attr_reader :browser_options
10
12
 
13
+ def_delegators :session, :url, :refresh, :text, :quit
14
+
11
15
  def initialize(browser_options)
12
16
  @browser_options = browser_options
13
17
  @session = Session.new(browser_options)
@@ -22,32 +26,24 @@ module Chemlab
22
26
  @session.engine.goto(Chemlab.configuration.base_url + path)
23
27
  end
24
28
 
29
+ # The options used to create the browser session
30
+ def to_s
31
+ @browser_options.to_s
32
+ end
33
+
25
34
  # An individual session
26
35
  class Session
36
+ extend Forwardable
27
37
  attr_reader :engine
28
38
 
39
+ def_delegators :engine, :url, :refresh, :text, :quit
40
+
29
41
  def initialize(browser)
30
42
  @engine = Watir::Browser.new(*browser)
31
43
 
32
44
  # @engine.goto(Chemlab.configuration.base_url)
33
45
  end
34
46
 
35
- def current_url
36
- engine.url
37
- end
38
-
39
- def refresh
40
- engine.refresh
41
- end
42
-
43
- def text
44
- engine.text
45
- end
46
-
47
- def quit
48
- engine.close
49
- end
50
-
51
47
  def save_screenshot(file_name)
52
48
  engine.screenshot.save(file_name)
53
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