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.
- 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 +11 -13
- 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 +60 -12
- data/lib/chemlab/element.rb +4 -0
- data/lib/chemlab/page.rb +19 -1
- data/lib/chemlab/runtime/browser.rb +13 -17
- 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 +87 -49
- 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
data/lib/chemlab/resource.rb
DELETED
@@ -1,169 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'active_support/core_ext/array/extract_options'
|
4
|
-
|
5
|
-
module Chemlab
|
6
|
-
class Resource
|
7
|
-
extend SingleForwardable
|
8
|
-
include ApiFabricator
|
9
|
-
|
10
|
-
NoValueError = Class.new(RuntimeError)
|
11
|
-
|
12
|
-
def_delegators :evaluator, :attribute
|
13
|
-
|
14
|
-
def initialize(api_client)
|
15
|
-
@api_client = api_client
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.fabricate!(*args, &prepare_block)
|
19
|
-
fabricate_via_api!(*args, &prepare_block)
|
20
|
-
rescue NotImplementedError
|
21
|
-
fabricate_via_browser_ui!(*args, &prepare_block)
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.fabricate_via_browser_ui!(*args, &prepare_block)
|
25
|
-
options = args.extract_options!
|
26
|
-
resource = options.fetch(:resource) { new }
|
27
|
-
parents = options.fetch(:parents) { [] }
|
28
|
-
|
29
|
-
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
|
30
|
-
log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
|
31
|
-
|
32
|
-
current_url
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.fabricate_via_api!(*args, &prepare_block)
|
37
|
-
options = args.extract_options!
|
38
|
-
resource = options.fetch(:resource) { new }
|
39
|
-
parents = options.fetch(:parents) { [] }
|
40
|
-
|
41
|
-
raise NotImplementedError unless resource.api_support?
|
42
|
-
|
43
|
-
resource.eager_load_api_client!
|
44
|
-
|
45
|
-
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
|
46
|
-
log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.remove_via_api!(*args, &prepare_block)
|
51
|
-
options = args.extract_options!
|
52
|
-
resource = options.fetch(:resource) { new }
|
53
|
-
parents = options.fetch(:parents) { [] }
|
54
|
-
|
55
|
-
resource.eager_load_api_client!
|
56
|
-
|
57
|
-
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
|
58
|
-
log_fabrication(:api, resource, parents, args) { resource.remove_via_api! }
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def fabricate!(*_args)
|
63
|
-
raise NotImplementedError
|
64
|
-
end
|
65
|
-
|
66
|
-
def visit!
|
67
|
-
Runtime::Logger.debug(%Q[Visiting #{self.class.name} at "#{web_url}"])
|
68
|
-
|
69
|
-
# Just in case an async action is not yet complete
|
70
|
-
Support::WaitForRequests.wait_for_requests
|
71
|
-
|
72
|
-
Support::Retrier.retry_until do
|
73
|
-
visit(web_url)
|
74
|
-
wait_until { current_url.include?(URI.parse(web_url).path.split('/').last || web_url) }
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def populate(*attributes)
|
79
|
-
attributes.each(&method(:public_send))
|
80
|
-
end
|
81
|
-
|
82
|
-
def wait_until(max_duration: 60, sleep_interval: 0.1)
|
83
|
-
Support::Waiter.wait_until(max_duration: max_duration, sleep_interval: sleep_interval) do
|
84
|
-
yield
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def populate_attribute(name, block)
|
91
|
-
value = attribute_value(name, block)
|
92
|
-
|
93
|
-
raise NoValueError, "No value was computed for #{name} of #{self.class.name}." unless value
|
94
|
-
|
95
|
-
value
|
96
|
-
end
|
97
|
-
|
98
|
-
def attribute_value(name, block)
|
99
|
-
api_value = api_resource&.dig(name)
|
100
|
-
|
101
|
-
if api_value && block
|
102
|
-
log_having_both_api_result_and_block(name, api_value)
|
103
|
-
end
|
104
|
-
|
105
|
-
api_value || (block && instance_exec(&block))
|
106
|
-
end
|
107
|
-
|
108
|
-
def log_having_both_api_result_and_block(name, api_value)
|
109
|
-
QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored."
|
110
|
-
end
|
111
|
-
|
112
|
-
def self.do_fabricate!(resource:, prepare_block:, parents: [])
|
113
|
-
prepare_block.call(resource) if prepare_block
|
114
|
-
|
115
|
-
resource_web_url = yield
|
116
|
-
resource.web_url = resource_web_url
|
117
|
-
|
118
|
-
resource
|
119
|
-
end
|
120
|
-
|
121
|
-
private_class_method :do_fabricate!
|
122
|
-
|
123
|
-
def self.log_fabrication(method, resource, parents, args)
|
124
|
-
return yield unless Runtime::Env.debug?
|
125
|
-
|
126
|
-
start = Time.now
|
127
|
-
prefix = "==#{'=' * parents.size}>"
|
128
|
-
msg = [prefix]
|
129
|
-
msg << "Built a #{name}"
|
130
|
-
msg << "as a dependency of #{parents.last}" if parents.any?
|
131
|
-
msg << "via #{method}"
|
132
|
-
|
133
|
-
yield.tap do
|
134
|
-
msg << "in #{Time.now - start} seconds"
|
135
|
-
puts msg.join(' ')
|
136
|
-
puts if parents.empty?
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
private_class_method :log_fabrication
|
141
|
-
|
142
|
-
def self.evaluator
|
143
|
-
@evaluator ||= DSL.new(self)
|
144
|
-
end
|
145
|
-
|
146
|
-
private_class_method :evaluator
|
147
|
-
|
148
|
-
class DSL
|
149
|
-
def initialize(base)
|
150
|
-
@base = base
|
151
|
-
end
|
152
|
-
|
153
|
-
def attribute(name, &block)
|
154
|
-
@base.module_eval do
|
155
|
-
attr_writer(name)
|
156
|
-
|
157
|
-
define_method(name) do
|
158
|
-
instance_variable_get("@#{name}") ||
|
159
|
-
instance_variable_set(
|
160
|
-
"@#{name}",
|
161
|
-
populate_attribute(name, block))
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
attribute :web_url
|
168
|
-
end
|
169
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'airborne'
|
4
|
-
|
5
|
-
module Chemlab
|
6
|
-
module Runtime
|
7
|
-
module API
|
8
|
-
class Client
|
9
|
-
attr_reader :address, :user
|
10
|
-
|
11
|
-
def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true, user: nil, ip_limits: false)
|
12
|
-
@address = address
|
13
|
-
@user = user
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/lib/chemlab/support/api.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Chemlab
|
4
|
-
module Support
|
5
|
-
module API
|
6
|
-
HTTP_STATUS_OK = 200
|
7
|
-
HTTP_STATUS_CREATED = 201
|
8
|
-
HTTP_STATUS_NO_CONTENT = 204
|
9
|
-
HTTP_STATUS_ACCEPTED = 202
|
10
|
-
HTTP_STATUS_SERVER_ERROR = 500
|
11
|
-
|
12
|
-
def post(url, payload)
|
13
|
-
RestClient::Request.execute(
|
14
|
-
method: :post,
|
15
|
-
url: url,
|
16
|
-
payload: payload,
|
17
|
-
verify_ssl: false)
|
18
|
-
rescue RestClient::ExceptionWithResponse => e
|
19
|
-
return_response_or_raise(e)
|
20
|
-
end
|
21
|
-
|
22
|
-
def get(url, raw_response: false)
|
23
|
-
RestClient::Request.execute(
|
24
|
-
method: :get,
|
25
|
-
url: url,
|
26
|
-
verify_ssl: false,
|
27
|
-
raw_response: raw_response)
|
28
|
-
rescue RestClient::ExceptionWithResponse => e
|
29
|
-
return_response_or_raise(e)
|
30
|
-
end
|
31
|
-
|
32
|
-
def put(url, payload)
|
33
|
-
RestClient::Request.execute(
|
34
|
-
method: :put,
|
35
|
-
url: url,
|
36
|
-
payload: payload,
|
37
|
-
verify_ssl: false)
|
38
|
-
rescue RestClient::ExceptionWithResponse => e
|
39
|
-
return_response_or_raise(e)
|
40
|
-
end
|
41
|
-
|
42
|
-
def delete(url)
|
43
|
-
RestClient::Request.execute(
|
44
|
-
method: :delete,
|
45
|
-
url: url,
|
46
|
-
verify_ssl: false)
|
47
|
-
rescue RestClient::ExceptionWithResponse => e
|
48
|
-
return_response_or_raise(e)
|
49
|
-
end
|
50
|
-
|
51
|
-
def head(url)
|
52
|
-
RestClient::Request.execute(
|
53
|
-
method: :head,
|
54
|
-
url: url,
|
55
|
-
verify_ssl: false)
|
56
|
-
rescue RestClient::ExceptionWithResponse => e
|
57
|
-
return_response_or_raise(e)
|
58
|
-
end
|
59
|
-
|
60
|
-
def parse_body(response)
|
61
|
-
JSON.parse(response.body, symbolize_names: true)
|
62
|
-
end
|
63
|
-
|
64
|
-
def return_response_or_raise(error)
|
65
|
-
raise error unless error.respond_to?(:response) && error.response
|
66
|
-
|
67
|
-
error.response
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,176 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Chemlab
|
4
|
-
module Support
|
5
|
-
module Logging
|
6
|
-
def assert_no_element(name)
|
7
|
-
log("asserting no element :#{name}")
|
8
|
-
|
9
|
-
super
|
10
|
-
end
|
11
|
-
|
12
|
-
def refresh
|
13
|
-
log("refreshing #{current_url}")
|
14
|
-
|
15
|
-
super
|
16
|
-
end
|
17
|
-
|
18
|
-
def scroll_to(selector, text: nil)
|
19
|
-
msg = "scrolling to :#{selector}"
|
20
|
-
msg += " with text: #{text}" if text
|
21
|
-
log(msg)
|
22
|
-
|
23
|
-
super
|
24
|
-
end
|
25
|
-
|
26
|
-
def asset_exists?(url)
|
27
|
-
exists = super
|
28
|
-
|
29
|
-
log("asset_exists? #{url} returned #{exists}")
|
30
|
-
|
31
|
-
exists
|
32
|
-
end
|
33
|
-
|
34
|
-
def find_element(name, **kwargs)
|
35
|
-
log("finding :#{name} with args #{kwargs}")
|
36
|
-
|
37
|
-
element = super
|
38
|
-
|
39
|
-
log("found :#{name}") if element
|
40
|
-
|
41
|
-
element
|
42
|
-
end
|
43
|
-
|
44
|
-
def all_elements(name, **kwargs)
|
45
|
-
log("finding all :#{name} with args #{kwargs}")
|
46
|
-
|
47
|
-
elements = super
|
48
|
-
|
49
|
-
log("found #{elements.size} :#{name}") if elements
|
50
|
-
|
51
|
-
elements
|
52
|
-
end
|
53
|
-
|
54
|
-
def check_element(name)
|
55
|
-
log("checking :#{name}")
|
56
|
-
|
57
|
-
super
|
58
|
-
end
|
59
|
-
|
60
|
-
def uncheck_element(name)
|
61
|
-
log("unchecking :#{name}")
|
62
|
-
|
63
|
-
super
|
64
|
-
end
|
65
|
-
|
66
|
-
def click_element(name, page = nil, **kwargs)
|
67
|
-
msg = ["clicking :#{name}"]
|
68
|
-
msg << ", expecting to be at #{page.class}" if page
|
69
|
-
msg << "with args #{kwargs}"
|
70
|
-
|
71
|
-
log(msg.compact.join(' '))
|
72
|
-
|
73
|
-
super
|
74
|
-
end
|
75
|
-
|
76
|
-
def fill_element(name, content)
|
77
|
-
masked_content = name.to_s.include?('password') ? '*****' : content
|
78
|
-
|
79
|
-
log(%Q(filling :#{name} with "#{masked_content}"))
|
80
|
-
|
81
|
-
super
|
82
|
-
end
|
83
|
-
|
84
|
-
def select_element(name, value)
|
85
|
-
log(%Q(selecting "#{value}" in :#{name}))
|
86
|
-
|
87
|
-
super
|
88
|
-
end
|
89
|
-
|
90
|
-
def has_element?(name, **kwargs)
|
91
|
-
found = super
|
92
|
-
|
93
|
-
log_has_element_or_not('has_element?', name, found, **kwargs)
|
94
|
-
|
95
|
-
found
|
96
|
-
end
|
97
|
-
|
98
|
-
def has_no_element?(name, **kwargs)
|
99
|
-
found = super
|
100
|
-
|
101
|
-
log_has_element_or_not('has_no_element?', name, found, **kwargs)
|
102
|
-
|
103
|
-
found
|
104
|
-
end
|
105
|
-
|
106
|
-
def has_text?(text, **kwargs)
|
107
|
-
found = super
|
108
|
-
|
109
|
-
log(%Q{has_text?('#{text}', wait: #{kwargs[:wait] || Capybara.default_max_wait_time}) returned #{found}})
|
110
|
-
|
111
|
-
found
|
112
|
-
end
|
113
|
-
|
114
|
-
def has_no_text?(text, **kwargs)
|
115
|
-
found = super
|
116
|
-
|
117
|
-
log(%Q{has_no_text?('#{text}', wait: #{kwargs[:wait] || Capybara.default_max_wait_time}) returned #{found}})
|
118
|
-
|
119
|
-
found
|
120
|
-
end
|
121
|
-
|
122
|
-
def finished_loading?
|
123
|
-
log('waiting for loading to complete...')
|
124
|
-
now = Time.now
|
125
|
-
|
126
|
-
loaded = super
|
127
|
-
|
128
|
-
log("loading complete after #{Time.now - now} seconds")
|
129
|
-
|
130
|
-
loaded
|
131
|
-
end
|
132
|
-
|
133
|
-
def wait_for_animated_element(name)
|
134
|
-
log("waiting for animated element: #{name}")
|
135
|
-
|
136
|
-
super
|
137
|
-
end
|
138
|
-
|
139
|
-
def within_element(name, text: nil)
|
140
|
-
log("within element :#{name}")
|
141
|
-
|
142
|
-
element = super
|
143
|
-
|
144
|
-
log("end within element :#{name}")
|
145
|
-
|
146
|
-
element
|
147
|
-
end
|
148
|
-
|
149
|
-
def within_element_by_index(name, index)
|
150
|
-
log("within elements :#{name} at index #{index}")
|
151
|
-
|
152
|
-
element = super
|
153
|
-
|
154
|
-
log("end within elements :#{name} at index #{index}")
|
155
|
-
|
156
|
-
element
|
157
|
-
end
|
158
|
-
|
159
|
-
private
|
160
|
-
|
161
|
-
def log(msg)
|
162
|
-
Runtime::Logger.debug(msg)
|
163
|
-
end
|
164
|
-
|
165
|
-
def log_has_element_or_not(method, name, found, **kwargs)
|
166
|
-
msg = ["#{method} :#{name}"]
|
167
|
-
msg << %Q(with text "#{kwargs[:text]}") if kwargs[:text]
|
168
|
-
msg << "class: #{kwargs[:class]}" if kwargs[:class]
|
169
|
-
msg << "(wait: #{kwargs[:wait] || Capybara.default_max_wait_time})"
|
170
|
-
msg << "returned: #{found}"
|
171
|
-
|
172
|
-
log(msg.compact.join(' '))
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
@@ -1,65 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'active_support/inflector'
|
4
|
-
|
5
|
-
module Chemlab
|
6
|
-
module Support
|
7
|
-
module Repeater
|
8
|
-
DEFAULT_MAX_WAIT_TIME = 60
|
9
|
-
|
10
|
-
RetriesExceededError = Class.new(RuntimeError)
|
11
|
-
WaitExceededError = Class.new(RuntimeError)
|
12
|
-
|
13
|
-
def repeat_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false, log: true)
|
14
|
-
attempts = 0
|
15
|
-
start = Time.now
|
16
|
-
|
17
|
-
begin
|
18
|
-
while remaining_attempts?(attempts, max_attempts) && remaining_time?(start, max_duration)
|
19
|
-
QA::Runtime::Logger.debug("Attempt number #{attempts + 1}") if max_attempts && log
|
20
|
-
|
21
|
-
result = yield
|
22
|
-
return result if result
|
23
|
-
|
24
|
-
sleep_and_reload_if_needed(sleep_interval, reload_page)
|
25
|
-
attempts += 1
|
26
|
-
end
|
27
|
-
rescue StandardError, RSpec::Expectations::ExpectationNotMetError
|
28
|
-
raise unless retry_on_exception
|
29
|
-
|
30
|
-
attempts += 1
|
31
|
-
if remaining_attempts?(attempts, max_attempts) && remaining_time?(start, max_duration)
|
32
|
-
sleep_and_reload_if_needed(sleep_interval, reload_page)
|
33
|
-
|
34
|
-
retry
|
35
|
-
else
|
36
|
-
raise
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
if raise_on_failure
|
41
|
-
raise RetriesExceededError, "Retry condition not met after #{max_attempts} #{'attempt'.pluralize(max_attempts)}" unless remaining_attempts?(attempts, max_attempts)
|
42
|
-
|
43
|
-
raise WaitExceededError, "Wait condition not met after #{max_duration} #{'second'.pluralize(max_duration)}"
|
44
|
-
end
|
45
|
-
|
46
|
-
false
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def sleep_and_reload_if_needed(sleep_interval, reload_page)
|
52
|
-
sleep(sleep_interval)
|
53
|
-
reload_page.refresh if reload_page
|
54
|
-
end
|
55
|
-
|
56
|
-
def remaining_attempts?(attempts, max_attempts)
|
57
|
-
max_attempts ? attempts < max_attempts : true
|
58
|
-
end
|
59
|
-
|
60
|
-
def remaining_time?(start, max_duration)
|
61
|
-
max_duration ? Time.now - start < max_duration : true
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|