chemlab 0.2.1 → 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.
- checksums.yaml +4 -4
- data/Rakefile +6 -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 +65 -0
- data/lib/chemlab/cli/stubber.rb +74 -0
- data/lib/chemlab/component.rb +78 -8
- data/lib/chemlab/configuration.rb +61 -13
- data/lib/chemlab/element.rb +5 -0
- data/lib/chemlab/page.rb +20 -2
- data/lib/chemlab/runtime/browser.rb +19 -25
- 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 +68 -58
- 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,22 +3,79 @@
|
|
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
|
-
def libraries=(libraries=[])
|
78
|
+
def libraries=(libraries = [])
|
22
79
|
@libraries = Chemlab.const_set('Vendor', Module.new)
|
23
80
|
|
24
81
|
libraries.each do |library|
|
@@ -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,19 +1,24 @@
|
|
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
|
8
10
|
link
|
9
11
|
checkbox
|
10
12
|
image
|
13
|
+
radio
|
11
14
|
].freeze
|
12
15
|
|
16
|
+
# Elements that are +<select>+
|
13
17
|
SELECTABLES = %i[
|
14
18
|
select
|
15
19
|
].freeze
|
16
20
|
|
21
|
+
# Text input elements
|
17
22
|
INPUTS = %i[
|
18
23
|
text_field
|
19
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
|
+
attribute :path
|
6
7
|
|
7
|
-
|
8
|
-
|
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,53 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'forwardable'
|
4
4
|
|
5
5
|
module Chemlab
|
6
6
|
module Runtime
|
7
7
|
# The browser configurator
|
8
8
|
class Browser
|
9
|
-
extend
|
10
|
-
include ActiveSupport::Configurable
|
9
|
+
extend Forwardable
|
11
10
|
attr_accessor :session
|
12
11
|
attr_reader :browser_options
|
13
12
|
|
13
|
+
def_delegators :session, :url, :refresh, :text, :quit
|
14
|
+
|
14
15
|
def initialize(browser_options)
|
15
16
|
@browser_options = browser_options
|
17
|
+
@session = Session.new(browser_options)
|
16
18
|
end
|
17
19
|
|
18
20
|
def self.navigate_to(page_class)
|
19
|
-
Chemlab.configuration.browser.navigate_to(page_class.
|
21
|
+
Chemlab.configuration.browser.navigate_to(page_class.path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def navigate_to(path)
|
25
|
+
@session ||= Chemlab.configuration.browser.session
|
26
|
+
@session.engine.goto(Chemlab.configuration.base_url + path)
|
20
27
|
end
|
21
28
|
|
22
|
-
|
23
|
-
|
24
|
-
@
|
29
|
+
# The options used to create the browser session
|
30
|
+
def to_s
|
31
|
+
@browser_options.to_s
|
25
32
|
end
|
26
33
|
|
27
34
|
# An individual session
|
28
35
|
class Session
|
36
|
+
extend Forwardable
|
29
37
|
attr_reader :engine
|
30
38
|
|
31
|
-
|
32
|
-
@engine = Watir::Browser.new(*browser)
|
33
|
-
|
34
|
-
@engine.goto(Chemlab.configuration.base_url)
|
35
|
-
end
|
36
|
-
|
37
|
-
def current_url
|
38
|
-
engine.url
|
39
|
-
end
|
40
|
-
|
41
|
-
def refresh
|
42
|
-
engine.refresh
|
43
|
-
end
|
39
|
+
def_delegators :engine, :url, :refresh, :text, :quit
|
44
40
|
|
45
|
-
def
|
46
|
-
engine.
|
47
|
-
end
|
41
|
+
def initialize(browser)
|
42
|
+
@engine = Watir::Browser.new(*browser)
|
48
43
|
|
49
|
-
|
50
|
-
engine.close
|
44
|
+
# @engine.goto(Chemlab.configuration.base_url)
|
51
45
|
end
|
52
46
|
|
53
47
|
def save_screenshot(file_name)
|