1
2
3
4
5
6
7
8
9
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
# frozen_string_literal: true
require 'forwardable'
require 'capybara/dsl'
require 'active_support/core_ext/array/extract_options'
module QA
module Resource
class Base
extend SingleForwardable
include ApiFabricator
extend Capybara::DSL
NoValueError = Class.new(RuntimeError)
def_delegators :evaluator, :attribute
def self.fabricate!(*args, &prepare_block)
fabricate_via_api!(*args, &prepare_block)
rescue NotImplementedError
fabricate_via_browser_ui!(*args, &prepare_block)
end
def self.fabricate_via_browser_ui!(*args, &prepare_block)
options = args.extract_options!
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
current_url
end
end
def self.fabricate_via_api!(*args, &prepare_block)
options = args.extract_options!
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
raise NotImplementedError unless resource.api_support?
resource.eager_load_api_client!
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
end
end
def self.remove_via_api!(*args, &prepare_block)
options = args.extract_options!
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
resource.eager_load_api_client!
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:api, resource, parents, args) { resource.remove_via_api! }
end
end
def fabricate!(*_args)
raise NotImplementedError
end
def visit!
Runtime::Logger.debug(%Q[Visiting #{self.class.name} at "#{web_url}"])
# Just in case an async action is not yet complete
Support::WaitForRequests.wait_for_requests
Support::Retrier.retry_until do
visit(web_url)
wait_until { current_url.include?(URI.parse(web_url).path.split('/').last || web_url) }
end
# Wait until the new page is ready for us to interact with it
Support::WaitForRequests.wait_for_requests
end
def populate(*attributes)
attributes.each(&method(:public_send))
end
def wait_until(max_duration: 60, sleep_interval: 0.1)
QA::Support::Waiter.wait_until(max_duration: max_duration, sleep_interval: sleep_interval) do
yield
end
end
private
def populate_attribute(name, block)
value = attribute_value(name, block)
raise NoValueError, "No value was computed for #{name} of #{self.class.name}." unless value
value
end
def attribute_value(name, block)
api_value = api_resource&.dig(name)
if api_value && block
log_having_both_api_result_and_block(name, api_value)
end
api_value || (block && instance_exec(&block))
end
def log_having_both_api_result_and_block(name, api_value)
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."
end
def self.do_fabricate!(resource:, prepare_block:, parents: [])
prepare_block.call(resource) if prepare_block
resource_web_url = yield
resource.web_url = resource_web_url
resource
end
private_class_method :do_fabricate!
def self.log_fabrication(method, resource, parents, args)
return yield unless Runtime::Env.debug?
start = Time.now
prefix = "==#{'=' * parents.size}>"
msg = [prefix]
msg << "Built a #{name}"
msg << "as a dependency of #{parents.last}" if parents.any?
msg << "via #{method}"
yield.tap do
msg << "in #{Time.now - start} seconds"
puts msg.join(' ')
puts if parents.empty?
end
end
private_class_method :log_fabrication
def self.evaluator
@evaluator ||= Base::DSL.new(self)
end
private_class_method :evaluator
class DSL
def initialize(base)
@base = base
end
def attribute(name, &block)
@base.module_eval do
attr_writer(name)
define_method(name) do
instance_variable_get("@#{name}") ||
instance_variable_set(
"@#{name}",
populate_attribute(name, block))
end
end
end
end
attribute :web_url
end
end
end
|