chemlab 0.4.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +8 -0
- data/bin/chemlab +10 -0
- data/bin/chemlab-suite +1 -0
- data/bin/chemlab-test +1 -0
- data/lib/chemlab.rb +27 -17
- data/lib/chemlab/attributable.rb +16 -10
- data/lib/chemlab/cli/fixtures/new_library/.gitignore +63 -0
- data/lib/chemlab/cli/fixtures/new_library/Gemfile +5 -0
- data/lib/chemlab/cli/fixtures/new_library/README.md.erb +1 -0
- data/lib/chemlab/cli/fixtures/new_library/lib/new_library.rb.erb +7 -0
- data/lib/chemlab/cli/fixtures/new_library/lib/page/sample.rb.erb +9 -0
- data/lib/chemlab/cli/fixtures/new_library/new_library.gemspec.erb +23 -0
- data/lib/chemlab/cli/fixtures/new_library/spec/integration/page/sample_spec.rb.erb +17 -0
- data/lib/chemlab/cli/fixtures/new_library/spec/unit/page/sample_spec.rb.erb +19 -0
- data/lib/chemlab/cli/generator.rb +46 -0
- data/lib/chemlab/cli/generator/templates/page.erb +3 -0
- data/lib/chemlab/cli/new_library.rb +62 -0
- data/lib/chemlab/cli/stub.erb +66 -0
- data/lib/chemlab/cli/stubber.rb +74 -0
- data/lib/chemlab/component.rb +78 -8
- data/lib/chemlab/configuration.rb +60 -12
- data/lib/chemlab/element.rb +4 -0
- data/lib/chemlab/page.rb +19 -1
- data/lib/chemlab/runtime/browser.rb +13 -18
- data/lib/chemlab/runtime/env.rb +13 -9
- data/lib/chemlab/runtime/logger.rb +16 -13
- data/lib/chemlab/version.rb +1 -1
- data/lib/tasks/generate.rake +22 -0
- data/lib/tasks/generate_stubs.rake +20 -0
- data/lib/tasks/help.rake +24 -0
- data/lib/tasks/new.rake +19 -0
- data/lib/tasks/version.rake +8 -0
- metadata +113 -57
- data/lib/chemlab/api_fabricator.rb +0 -134
- data/lib/chemlab/resource.rb +0 -169
- data/lib/chemlab/runtime/api_client.rb +0 -18
- data/lib/chemlab/support/api.rb +0 -71
- data/lib/chemlab/support/logging.rb +0 -176
- data/lib/chemlab/support/repeater.rb +0 -65
- 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
|
data/lib/chemlab/component.rb
CHANGED
@@ -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
|
-
|
55
|
+
public_send("#{name}_element").present?
|
26
56
|
end
|
27
57
|
|
58
|
+
# === GETTER / CLICKER === #
|
28
59
|
define_method(name) do
|
29
|
-
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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.
|
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
|
-
|
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
|
data/lib/chemlab/element.rb
CHANGED
@@ -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
|
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
|
data/lib/chemlab/runtime/env.rb
CHANGED
@@ -4,18 +4,22 @@ module Chemlab
|
|
4
4
|
module Runtime
|
5
5
|
# Environment configuration file
|
6
6
|
module Env
|
7
|
-
|
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
|
-
|
10
|
-
enabled?(ENV['DEBUG'], default: false)
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
16
|
+
private
|
14
17
|
|
15
|
-
|
16
|
-
|
18
|
+
def enabled?(value, default: true)
|
19
|
+
return default if value.nil?
|
17
20
|
|
18
|
-
|
21
|
+
(value =~ /^(false|no|n|0)$/i) != 0
|
22
|
+
end
|
19
23
|
end
|
20
24
|
end
|
21
25
|
end
|