capydash 0.1.7 → 0.2.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/lib/capydash/rspec_integration.rb +285 -0
- data/lib/capydash/version.rb +1 -1
- data/lib/capydash.rb +6 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '08b9f8d42cd5b408688aaffd4e8000a2f2514e1b7cababeaef8e6b5f22e6255b'
|
|
4
|
+
data.tar.gz: fe000167bd555af57a386e7616a4a475b577881b8d8c7cd2cec996e81d5ce088
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25087505b55812541a3be9184467174c205c4c6b8cc75bd6c0c74c5c9a8995cbcd19c1f1b160c5ebda02e4cf14a6adb26de2639bc1097724931182e4ff9bbf2b
|
|
7
|
+
data.tar.gz: 16ebd46b3f89a641cd0260dbb2f08f7702b59ea89478568fbeb06e3f47e5e6a09dfdf501dd5cb528399d4dbed24417fd42c286dce391e625df9aff774168c4a0
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
require 'securerandom'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'erb'
|
|
5
|
+
|
|
6
|
+
module CapyDash
|
|
7
|
+
module RSpecIntegration
|
|
8
|
+
class << self
|
|
9
|
+
def setup!
|
|
10
|
+
return unless defined?(RSpec)
|
|
11
|
+
return if @configured
|
|
12
|
+
|
|
13
|
+
@results = []
|
|
14
|
+
@run_id = nil
|
|
15
|
+
@configured = true
|
|
16
|
+
|
|
17
|
+
RSpec.configure do |config|
|
|
18
|
+
config.before(:suite) do
|
|
19
|
+
CapyDash::RSpecIntegration.start_test_run
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
config.after(:each) do |example|
|
|
23
|
+
CapyDash::RSpecIntegration.record_example(example)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
config.after(:suite) do
|
|
27
|
+
CapyDash::RSpecIntegration.finish_test_run
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def start_test_run
|
|
33
|
+
@run_id = generate_run_id
|
|
34
|
+
@results = []
|
|
35
|
+
@started_at = Time.now
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def record_example(example)
|
|
39
|
+
return unless @run_id
|
|
40
|
+
|
|
41
|
+
execution_result = example.execution_result
|
|
42
|
+
|
|
43
|
+
# Map RSpec status to our status format
|
|
44
|
+
status = case execution_result.status.to_s
|
|
45
|
+
when 'passed'
|
|
46
|
+
'passed'
|
|
47
|
+
when 'failed'
|
|
48
|
+
'failed'
|
|
49
|
+
when 'pending'
|
|
50
|
+
'pending'
|
|
51
|
+
else
|
|
52
|
+
'unknown'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Extract error message if test failed
|
|
56
|
+
error_message = nil
|
|
57
|
+
if execution_result.status == :failed && execution_result.exception
|
|
58
|
+
error_message = format_exception(execution_result.exception)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Extract class name from example location
|
|
62
|
+
# RSpec examples are typically in files like spec/features/user_spec.rb
|
|
63
|
+
# We'll use the file path to determine the "class" name
|
|
64
|
+
file_path = example.metadata[:file_path] || ''
|
|
65
|
+
class_name = extract_class_name_from_path(file_path)
|
|
66
|
+
|
|
67
|
+
# Create test data structure matching Minitest format
|
|
68
|
+
test_data = {
|
|
69
|
+
test_name: "#{class_name}##{example.full_description}",
|
|
70
|
+
steps: [
|
|
71
|
+
{
|
|
72
|
+
step_name: 'test_execution',
|
|
73
|
+
detail: example.full_description,
|
|
74
|
+
status: status,
|
|
75
|
+
error: error_message
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Add location information
|
|
81
|
+
if example.metadata[:location]
|
|
82
|
+
test_data[:location] = example.metadata[:location]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
@results << test_data
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def finish_test_run
|
|
89
|
+
return unless @run_id
|
|
90
|
+
return if @results.empty?
|
|
91
|
+
|
|
92
|
+
# Calculate summary statistics
|
|
93
|
+
total_tests = @results.length
|
|
94
|
+
passed_tests = @results.count { |r| r[:steps].any? { |s| s[:status] == 'passed' } }
|
|
95
|
+
failed_tests = @results.count { |r| r[:steps].any? { |s| s[:status] == 'failed' } }
|
|
96
|
+
pending_tests = @results.count { |r| r[:steps].any? { |s| s[:status] == 'pending' } }
|
|
97
|
+
|
|
98
|
+
# Create run data structure matching Minitest format
|
|
99
|
+
run_data = {
|
|
100
|
+
id: @run_id,
|
|
101
|
+
created_at: @started_at.iso8601,
|
|
102
|
+
total_tests: total_tests,
|
|
103
|
+
passed_tests: passed_tests,
|
|
104
|
+
failed_tests: failed_tests,
|
|
105
|
+
tests: @results.map { |r| { test_name: r[:test_name], steps: r[:steps] } }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Save using the existing persistence layer
|
|
109
|
+
CapyDash.save_test_run(run_data)
|
|
110
|
+
|
|
111
|
+
# Generate report
|
|
112
|
+
generate_report(run_data)
|
|
113
|
+
|
|
114
|
+
# Clear state
|
|
115
|
+
@run_id = nil
|
|
116
|
+
@results = []
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def generate_run_id
|
|
122
|
+
"#{Time.now.to_i}_#{SecureRandom.hex(4)}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def extract_class_name_from_path(file_path)
|
|
126
|
+
return 'UnknownSpec' if file_path.nil? || file_path.empty?
|
|
127
|
+
|
|
128
|
+
# Extract filename without extension and path
|
|
129
|
+
filename = File.basename(file_path, '.rb')
|
|
130
|
+
|
|
131
|
+
# Convert snake_case to PascalCase
|
|
132
|
+
# e.g., "user_spec" -> "UserSpec", "features/user_flow_spec" -> "UserFlowSpec"
|
|
133
|
+
filename.split('_').map(&:capitalize).join('')
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def format_exception(exception)
|
|
137
|
+
return nil unless exception
|
|
138
|
+
|
|
139
|
+
message = exception.message || 'Unknown error'
|
|
140
|
+
backtrace = exception.backtrace || []
|
|
141
|
+
|
|
142
|
+
# Format similar to RSpec's output
|
|
143
|
+
formatted = "#{exception.class}: #{message}"
|
|
144
|
+
|
|
145
|
+
if backtrace.any?
|
|
146
|
+
# Include first few lines of backtrace
|
|
147
|
+
formatted += "\n" + backtrace.first(5).map { |line| " #{line}" }.join("\n")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
formatted
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def generate_report(run_data)
|
|
154
|
+
# Use the existing ReportGenerator but with our RSpec data
|
|
155
|
+
# We need to adapt it to work with our in-memory data structure
|
|
156
|
+
report_dir = File.join(Dir.pwd, "capydash_report")
|
|
157
|
+
FileUtils.mkdir_p(report_dir)
|
|
158
|
+
|
|
159
|
+
assets_dir = File.join(report_dir, "assets")
|
|
160
|
+
FileUtils.mkdir_p(assets_dir)
|
|
161
|
+
|
|
162
|
+
screenshots_dir = File.join(report_dir, "screenshots")
|
|
163
|
+
FileUtils.mkdir_p(screenshots_dir)
|
|
164
|
+
|
|
165
|
+
# Generate HTML report using the same template
|
|
166
|
+
html_content = generate_html(run_data, run_data[:created_at])
|
|
167
|
+
html_path = File.join(report_dir, "index.html")
|
|
168
|
+
File.write(html_path, html_content)
|
|
169
|
+
|
|
170
|
+
# Generate CSS and JS - use ReportGenerator's private methods via send
|
|
171
|
+
# These methods are private but we need them for RSpec reports
|
|
172
|
+
css_content = CapyDash::ReportGenerator.send(:generate_css)
|
|
173
|
+
css_path = File.join(assets_dir, "dashboard.css")
|
|
174
|
+
File.write(css_path, css_content)
|
|
175
|
+
|
|
176
|
+
js_content = CapyDash::ReportGenerator.send(:generate_javascript)
|
|
177
|
+
js_path = File.join(assets_dir, "dashboard.js")
|
|
178
|
+
File.write(js_path, js_content)
|
|
179
|
+
|
|
180
|
+
html_path
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def generate_html(test_data, created_at)
|
|
184
|
+
# Process test data into a structured format (same as ReportGenerator)
|
|
185
|
+
processed_tests = process_test_data(test_data)
|
|
186
|
+
|
|
187
|
+
# Calculate summary statistics
|
|
188
|
+
total_tests = processed_tests.sum { |test| test[:methods].length }
|
|
189
|
+
passed_tests = processed_tests.sum { |test| test[:methods].count { |method| method[:status] == 'passed' } }
|
|
190
|
+
failed_tests = total_tests - passed_tests
|
|
191
|
+
|
|
192
|
+
# Parse created_at if it's a string, otherwise use Time object
|
|
193
|
+
created_at_time = if created_at.is_a?(String)
|
|
194
|
+
Time.parse(created_at)
|
|
195
|
+
else
|
|
196
|
+
created_at
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Generate HTML using ERB template
|
|
200
|
+
template = File.read(File.join(__dir__, 'templates', 'report.html.erb'))
|
|
201
|
+
erb = ERB.new(template)
|
|
202
|
+
|
|
203
|
+
erb.result(binding)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def process_test_data(test_data)
|
|
207
|
+
return [] unless test_data[:tests]
|
|
208
|
+
|
|
209
|
+
# Group tests by class
|
|
210
|
+
tests_by_class = {}
|
|
211
|
+
|
|
212
|
+
test_data[:tests].each do |test|
|
|
213
|
+
test_name = test[:test_name] || 'UnknownTest'
|
|
214
|
+
|
|
215
|
+
# Extract class and method names from test name like "UserSpec#should visit the home page"
|
|
216
|
+
if test_name.include?('#')
|
|
217
|
+
class_name, method_name = test_name.split('#', 2)
|
|
218
|
+
else
|
|
219
|
+
class_name = extract_class_name(test_name)
|
|
220
|
+
method_name = extract_method_name(test_name)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
tests_by_class[class_name] ||= {
|
|
224
|
+
class_name: class_name,
|
|
225
|
+
methods: []
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Process steps
|
|
229
|
+
steps = test[:steps] || []
|
|
230
|
+
processed_steps = steps.map do |step|
|
|
231
|
+
{
|
|
232
|
+
name: step[:step_name] || step[:name] || 'unknown_step',
|
|
233
|
+
detail: step[:detail] || step[:description] || '',
|
|
234
|
+
status: step[:status] || 'unknown',
|
|
235
|
+
screenshot: step[:screenshot] ? File.basename(step[:screenshot]) : nil,
|
|
236
|
+
error: step[:error] || step[:message]
|
|
237
|
+
}
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Filter out "running" steps
|
|
241
|
+
processed_steps = processed_steps.reject { |step| step[:status] == 'running' }
|
|
242
|
+
|
|
243
|
+
# Determine method status
|
|
244
|
+
method_status = if processed_steps.any? { |s| s[:status] == 'failed' }
|
|
245
|
+
'failed'
|
|
246
|
+
elsif processed_steps.any? { |s| s[:status] == 'passed' }
|
|
247
|
+
'passed'
|
|
248
|
+
elsif processed_steps.any? { |s| s[:status] == 'pending' }
|
|
249
|
+
'pending'
|
|
250
|
+
else
|
|
251
|
+
'running'
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
tests_by_class[class_name][:methods] << {
|
|
255
|
+
name: method_name,
|
|
256
|
+
status: method_status,
|
|
257
|
+
steps: processed_steps
|
|
258
|
+
}
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
tests_by_class.values
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def extract_class_name(test_name)
|
|
265
|
+
return 'UnknownTest' if test_name.nil? || test_name.empty?
|
|
266
|
+
|
|
267
|
+
if test_name.include?('#')
|
|
268
|
+
test_name.split('#').first
|
|
269
|
+
else
|
|
270
|
+
test_name
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def extract_method_name(test_name)
|
|
275
|
+
return 'unknown_method' if test_name.nil? || test_name.empty?
|
|
276
|
+
|
|
277
|
+
if test_name.include?('#')
|
|
278
|
+
test_name.split('#').last
|
|
279
|
+
else
|
|
280
|
+
test_name
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
data/lib/capydash/version.rb
CHANGED
data/lib/capydash.rb
CHANGED
|
@@ -13,6 +13,12 @@ require "capydash/test_data_collector"
|
|
|
13
13
|
require "capydash/test_data_aggregator"
|
|
14
14
|
require "capydash/report_generator"
|
|
15
15
|
|
|
16
|
+
# Conditionally load RSpec integration if RSpec is present
|
|
17
|
+
if defined?(RSpec)
|
|
18
|
+
require "capydash/rspec_integration"
|
|
19
|
+
CapyDash::RSpecIntegration.setup!
|
|
20
|
+
end
|
|
21
|
+
|
|
16
22
|
module CapyDash
|
|
17
23
|
class << self
|
|
18
24
|
attr_accessor :configuration, :current_test, :config
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capydash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Damon Clark
|
|
@@ -129,6 +129,7 @@ files:
|
|
|
129
129
|
- lib/capydash/logger.rb
|
|
130
130
|
- lib/capydash/persistence.rb
|
|
131
131
|
- lib/capydash/report_generator.rb
|
|
132
|
+
- lib/capydash/rspec_integration.rb
|
|
132
133
|
- lib/capydash/templates/report.html.erb
|
|
133
134
|
- lib/capydash/test_data_aggregator.rb
|
|
134
135
|
- lib/capydash/test_data_collector.rb
|