spectre-reporter-html 1.1.2 → 2.0.1

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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/spectre/reporter/html.rb +1001 -1001
  3. metadata +7 -10
@@ -1,1180 +1,1180 @@
1
1
  require 'cgi'
2
2
  require 'base64'
3
- require 'spectre'
4
- require 'spectre/reporter'
5
-
6
- module Spectre::Reporter
7
- class HTML
8
- def initialize config
9
- @config = config
10
- @date_format = '%FT%T.%L'
11
- end
12
-
13
- def get_error_info error
14
- non_spectre_files = error.backtrace.select { |x| !x.include? 'lib/spectre' }
15
-
16
- if non_spectre_files.count > 0
17
- causing_file = non_spectre_files.first
18
- else
19
- causing_file = error.backtrace[0]
3
+ require 'socket'
4
+
5
+ module Spectre
6
+ module Reporter
7
+ class HTML
8
+ def initialize config
9
+ @config = config
10
+ @date_format = config['log_date_format']
20
11
  end
21
12
 
22
- matches = causing_file.match(/(.*\.rb):(\d+)/)
13
+ def get_error_info error
14
+ non_spectre_files = error.backtrace.reject { |x| x.include? 'lib/spectre' }
23
15
 
24
- return [nil, nil] unless matches
16
+ causing_file = if non_spectre_files.count.positive?
17
+ non_spectre_files.first
18
+ else
19
+ error.backtrace[0]
20
+ end
25
21
 
26
- file, line = matches.captures
27
- file.slice!(Dir.pwd + '/')
22
+ matches = causing_file.match(/(.*\.rb):(\d+)/)
28
23
 
29
- return [file, line]
30
- end
24
+ return [nil, nil] unless matches
31
25
 
32
- def read_resource filename
33
- file_path = File.join(__dir__, '../../../resources/', filename)
26
+ file, line = matches.captures
27
+ file.slice!(Dir.pwd + '/')
34
28
 
35
- File.open(file_path, 'rb') do |file|
36
- return file.read
29
+ [file, line]
37
30
  end
38
- end
39
31
 
40
- def report run_infos
41
- now = Time.now
42
-
43
- failures = run_infos.select { |x| x.failure != nil }
44
- errors = run_infos.select { |x| x.error != nil }
45
- skipped = run_infos.select { |x| x.skipped? }
46
- succeeded_count = run_infos.count - failures.count - errors.count - skipped.count
47
-
48
- if failures.count > 0
49
- overall_status = 'failed'
50
- elsif errors.count > 0
51
- overall_status = 'error'
52
- elsif skipped.count > 0
53
- overall_status = 'skipped'
54
- else
55
- overall_status = 'success'
32
+ def read_resource filename
33
+ file_path = File.join(__dir__, '../../../resources/', filename)
34
+
35
+ File.open(file_path, 'rb') do |file|
36
+ return file.read
37
+ end
56
38
  end
57
39
 
58
- json_report = {
59
- command: $COMMAND,
60
- project: @config['project'],
61
- date: now.strftime(@date_format),
62
- environment: @config['environment'],
63
- hostname: Socket.gethostname,
64
- duration: run_infos.sum { |x| x.duration },
65
- failures: failures.count,
66
- errors: errors.count,
67
- skipped: skipped.count,
68
- succeeded: succeeded_count,
69
- total: run_infos.count,
70
- overall_status: overall_status,
71
- tags: run_infos
72
- .map { |x| x.spec.tags }
73
- .flatten
74
- .uniq
75
- .sort,
76
- run_infos: run_infos.map do |run_info|
77
- failure = nil
78
- error = nil
79
-
80
- if run_info.failed? and not run_info.failure.cause
81
- failure_message = "Expected #{run_info.failure.expectation}"
82
- failure_message += " with #{run_info.data}" if run_info.data
83
- failure_message += " but it failed"
84
- failure_message += " with message: #{run_info.failure.message}" if run_info.failure.message
85
-
86
- failure = {
87
- message: failure_message,
88
- expected: run_info.failure.expected,
89
- actual: run_info.failure.actual,
40
+ def report run_infos
41
+ now = Time.now
42
+
43
+ failures = run_infos.select { |x| x.status == :failed }
44
+ errors = run_infos.select { |x| x.status == :error }
45
+ skipped = run_infos.select { |x| x.status == :skipped }
46
+ succeeded = run_infos.select { |x| x.status == :success }
47
+
48
+ overall_status = if failures.count.positive?
49
+ 'failed'
50
+ elsif errors.count.positive?
51
+ 'error'
52
+ elsif skipped.count.positive?
53
+ 'skipped'
54
+ else
55
+ 'success'
56
+ end
57
+
58
+ json_report = {
59
+ command: $COMMAND || 'spectre',
60
+ project: @config['project'] || '',
61
+ date: now.strftime(@date_format),
62
+ environment: @config['environment'] || Spectre::DEFAULT_ENV_NAME,
63
+ hostname: Socket.gethostname,
64
+ duration: run_infos.sum { |x| x.finished - x.started },
65
+ failures: failures.count,
66
+ errors: errors.count,
67
+ skipped: skipped.count,
68
+ succeeded: succeeded.count,
69
+ total: run_infos.count,
70
+ overall_status:,
71
+ tags: run_infos
72
+ .map { |x| x.respond_to?(:tags) ? x.parent.tags : [] }
73
+ .flatten
74
+ .uniq
75
+ .sort,
76
+ run_infos: run_infos.map do |run_info|
77
+ error = nil
78
+
79
+ if run_info.error
80
+ file, line = get_error_info(run_info.error)
81
+
82
+ error = {
83
+ type: run_info.error.class.name,
84
+ message: run_info.error.message,
85
+ file:,
86
+ line:,
87
+ stack_trace: run_info.error.backtrace,
88
+ }
89
+ end
90
+
91
+ {
92
+ status: run_info.status,
93
+ subject: run_info.parent.root.desc,
94
+ context: run_info.parent.desc,
95
+ tags: run_info.parent.respond_to?(:tags) ? run_info.parent.tags : nil,
96
+ name: run_info.parent.name,
97
+ desc: run_info.parent.desc,
98
+ file: run_info.parent.respond_to?(:file) ? run_info.parent.file : nil,
99
+ started: run_info.started.strftime(@date_format),
100
+ finished: run_info.finished.strftime(@date_format),
101
+ duration: run_info.finished - run_info.started,
102
+ properties: {},
103
+ data: run_info.parent.respond_to?(:data) ? run_info.parent.data : nil,
104
+ evaluations: run_info.evaluations
105
+ .select { |x| x.failures.any? }
106
+ .map do |evaluation|
107
+ {
108
+ desc: evaluation.desc,
109
+ failures: evaluation.failures.map do |failure|
110
+ {
111
+ message: failure.message,
112
+ }
113
+ end
114
+ }
115
+ end,
116
+ error:,
117
+ # the <script> element has to be escaped in any string, as it causes the inline JavaScript to break
118
+ log: run_info.logs.map { |x| [x[0], x[1].to_s.gsub(%r{<(/*script)}, '<`\1'), x[2], x[3]] },
90
119
  }
91
- end
92
-
93
- if run_info.error or (run_info.failed? and run_info.failure.cause)
94
- error = run_info.error || run_info.failure.cause
95
-
96
- file, line = get_error_info(error)
120
+ end,
121
+ config: @config.obfuscate!,
122
+ }
123
+
124
+ vuejs_content = read_resource('vue.global.prod.js')
125
+ open_sans_font = Base64.strict_encode64 read_resource('OpenSans-Regular.ttf')
126
+ fa_solid = Base64.strict_encode64 read_resource('fa-solid-900.ttf')
127
+ fa_regular = Base64.strict_encode64 read_resource('fa-regular-400.ttf')
128
+ icon = read_resource('spectre_icon.svg')
129
+
130
+ html_str = <<~HTML
131
+ <html>
132
+ <head>
133
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
134
+ <title>Spectre Report</title>
135
+
136
+ <!-- https://unpkg.com/vue@3.2.29/dist/vue.global.prod.js -->
137
+ <script>#{vuejs_content}</script>
138
+
139
+ <style>
140
+ @font-face{
141
+ font-family: 'Open Sans Regular';
142
+ src: url(data:font/ttf;base64,#{open_sans_font}) format('truetype');
143
+ }
97
144
 
98
- error = {
99
- type: error.class.name,
100
- message: error.message,
101
- file: file,
102
- line: line,
103
- stack_trace: error.backtrace,
104
- }
105
- end
106
-
107
- {
108
- status: run_info.status,
109
- subject: run_info.spec.subject.desc,
110
- context: run_info.spec.context.__desc,
111
- tags: run_info.spec.tags,
112
- name: run_info.spec.name,
113
- desc: run_info.spec.desc,
114
- file: run_info.spec.file,
115
- started: run_info.started.strftime(@date_format),
116
- finished: run_info.finished.strftime(@date_format),
117
- duration: run_info.duration,
118
- properties: run_info.properties,
119
- data: run_info.data,
120
- failure: failure,
121
- error: error,
122
- log: run_info.log.map do |x|
123
- log_text = x[1].to_s
124
- # the <script> element has to be escaped in any string, as it causes the inline JavaScript to break
125
- .gsub(/\<(\/*script)/, '<`\1')
126
- .force_encoding("ISO-8859-1")
127
- .encode("UTF-8")
128
-
129
- [x[0], log_text, x[2], x[3]]
130
- end,
131
- }
132
- end,
133
- config: @config.obfuscate!,
134
- }
135
-
136
- vuejs_content = read_resource('vue.global.prod.js')
137
- open_sans_font = Base64.strict_encode64 read_resource('OpenSans-Regular.ttf')
138
- fa_solid = Base64.strict_encode64 read_resource('fa-solid-900.ttf')
139
- fa_regular = Base64.strict_encode64 read_resource('fa-regular-400.ttf')
140
- icon = read_resource('spectre_icon.svg')
141
-
142
- html_str = <<~HTML
143
- <html>
144
- <head>
145
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
146
- <title>Spectre Report</title>
147
-
148
- <!-- https://unpkg.com/vue@3.2.29/dist/vue.global.prod.js -->
149
- <script>#{vuejs_content}</script>
150
-
151
- <style>
152
- @font-face{
153
- font-family: 'Open Sans Regular';
154
- src: url(data:font/ttf;base64,#{open_sans_font}) format('truetype');
155
- }
145
+ @font-face{
146
+ font-family: 'Font Awesome';
147
+ font-weight: 900;
148
+ src: url(data:font/ttf;base64,#{fa_solid}) format('truetype');
149
+ }
156
150
 
157
- @font-face{
158
- font-family: 'Font Awesome';
159
- font-weight: 900;
160
- src: url(data:font/ttf;base64,#{fa_solid}) format('truetype');
161
- }
151
+ @font-face{
152
+ font-family: 'Font Awesome';
153
+ font-weight: 400;
154
+ src: url(data:font/ttf;base64,#{fa_regular}) format('truetype');
155
+ }
162
156
 
163
- @font-face{
164
- font-family: 'Font Awesome';
165
- font-weight: 400;
166
- src: url(data:font/ttf;base64,#{fa_regular}) format('truetype');
167
- }
157
+ * {
158
+ box-sizing: border-box;
159
+ font-weight: inherit;
160
+ line-height: 1.5em;
161
+ font-size: inherit;
162
+ margin: 0rem;
163
+ padding: 0rem;
164
+ }
168
165
 
169
- * {
170
- box-sizing: border-box;
171
- font-weight: inherit;
172
- line-height: 1.5em;
173
- font-size: inherit;
174
- margin: 0rem;
175
- padding: 0rem;
176
- }
166
+ html, body {
167
+ padding: 0rem;
168
+ margin: 0rem;
169
+ }
177
170
 
178
- html, body {
179
- padding: 0rem;
180
- margin: 0rem;
181
- }
171
+ body {
172
+ font-family: 'Open Sans Regular', Arial, sans-serif;
173
+ color: #223857;
174
+ font-size: 16px;
175
+ background-color: #f6f7f8;
176
+ }
182
177
 
183
- body {
184
- font-family: 'Open Sans Regular', Arial, sans-serif;
185
- color: #223857;
186
- font-size: 16px;
187
- background-color: #f6f7f8;
188
- }
178
+ section {
179
+ margin-bottom: 2rem;
180
+ }
189
181
 
190
- section {
191
- margin-bottom: 2rem;
192
- }
182
+ #spectre-banner {
183
+ padding: 1rem;
184
+ font-weight: bold;
185
+ text-align: center;
186
+ text-transform: uppercase;
187
+ }
193
188
 
194
- #spectre-banner {
195
- padding: 1rem;
196
- font-weight: bold;
197
- text-align: center;
198
- text-transform: uppercase;
199
- }
189
+ #spectre-report {
190
+ padding: 2rem;
191
+ width: 90%;
192
+ margin: auto;
193
+ }
200
194
 
201
- #spectre-report {
202
- padding: 2rem;
203
- width: 90%;
204
- margin: auto;
205
- }
195
+ #spectre-tags {
196
+ text-align: center;
197
+ }
206
198
 
207
- #spectre-tags {
208
- text-align: center;
209
- }
199
+ #spectre-logo {
200
+ display: block;
201
+ margin: 1.5rem auto;
202
+ }
210
203
 
211
- #spectre-logo {
212
- display: block;
213
- margin: 1.5rem auto;
214
- }
204
+ fieldset {
205
+ border: 1px solid #dfe2e7;
206
+ border-radius: 3px;
207
+ margin-bottom: 0.3rem;
208
+ width: 100%;
209
+ padding: 0rem 1rem 1rem 1rem;
210
+ }
215
211
 
216
- fieldset {
217
- border: 1px solid #dfe2e7;
218
- border-radius: 3px;
219
- margin-bottom: 0.3rem;
220
- width: 100%;
221
- padding: 0rem 1rem 1rem 1rem;
222
- }
212
+ legend {
213
+ color: #bcc5d1;
214
+ font-size: 0.9rem;
215
+ padding: 0.3em;
216
+ }
223
217
 
224
- legend {
225
- color: #bcc5d1;
226
- font-size: 0.9rem;
227
- padding: 0.3em;
228
- }
218
+ th {
219
+ font-weight: bold;
220
+ text-align: right;
221
+ }
229
222
 
230
- th {
231
- font-weight: bold;
232
- text-align: right;
233
- }
223
+ td {
224
+ padding: 0.2em;
225
+ }
234
226
 
235
- td {
236
- padding: 0.2em;
237
- }
227
+ ul {
228
+ padding: 0rem 0rem 0rem 1rem;
229
+ }
238
230
 
239
- ul {
240
- padding: 0rem 0rem 0rem 1rem;
241
- }
231
+ /* spectre-logo */
242
232
 
243
- /* spectre-logo */
233
+ .spectre-status-success .spectre-logo-eye {
234
+ fill: #9ddf1c !important;
235
+ }
244
236
 
245
- .spectre-status-success .spectre-logo-eye {
246
- fill: #9ddf1c !important;
247
- }
237
+ .spectre-status-failed .spectre-logo-eye {
238
+ fill: #e61160 !important;
239
+ }
248
240
 
249
- .spectre-status-failed .spectre-logo-eye {
250
- fill: #e61160 !important;
251
- }
241
+ .spectre-status-error .spectre-logo-eye {
242
+ fill: #f5d915 !important;
243
+ }
252
244
 
253
- .spectre-status-error .spectre-logo-eye {
254
- fill: #f5d915 !important;
255
- }
245
+ .spectre-status-skipped .spectre-logo-eye {
246
+ fill: #c1d5d9 !important;
247
+ }
256
248
 
257
- .spectre-status-skipped .spectre-logo-eye {
258
- fill: #c1d5d9 !important;
259
- }
249
+ /* spectre-controls */
260
250
 
261
- /* spectre-controls */
251
+ ul.spectre-controls {
252
+ list-style: none;
253
+ margin: 0rem;
254
+ padding: 0rem;
255
+ text-align: center;
256
+ }
262
257
 
263
- ul.spectre-controls {
264
- list-style: none;
265
- margin: 0rem;
266
- padding: 0rem;
267
- text-align: center;
268
- }
258
+ ul.spectre-controls > li {
259
+ display: inline;
260
+ margin-right: 1rem;
261
+ }
269
262
 
270
- ul.spectre-controls > li {
271
- display: inline;
272
- margin-right: 1rem;
273
- }
263
+ .spectre-controls-clear {
264
+ position: relative;
265
+ }
274
266
 
275
- .spectre-controls-clear {
276
- position: relative;
277
- }
267
+ .spectre-controls-clear:before {
268
+ font-family: 'Font Awesome';
269
+ font-weight: 900;
270
+ content: '\\f0b0';
271
+ }
278
272
 
279
- .spectre-controls-clear:before {
280
- font-family: 'Font Awesome';
281
- font-weight: 900;
282
- content: '\\f0b0';
283
- }
273
+ .spectre-controls-clear.active:before {
274
+ font-family: 'Font Awesome';
275
+ font-weight: 900;
276
+ content: '\\e17b';
277
+ }
284
278
 
285
- .spectre-controls-clear.active:before {
286
- font-family: 'Font Awesome';
287
- font-weight: 900;
288
- content: '\\e17b';
289
- }
279
+ .spectre-filter-count {
280
+ font-size: 0.7em;
281
+ background-color: #223857;
282
+ color: #fff;
283
+ border-radius: 999px;
284
+ padding: 0em 0.5em;
285
+ position: absolute;
286
+ top: -1em;
287
+ }
290
288
 
291
- .spectre-filter-count {
292
- font-size: 0.7em;
293
- background-color: #223857;
294
- color: #fff;
295
- border-radius: 999px;
296
- padding: 0em 0.5em;
297
- position: absolute;
298
- top: -1em;
299
- }
289
+ .spectre-link {
290
+ display: inline;
291
+ border-bottom: 2px solid #3196d6;
292
+ text-decoration: none;
293
+ cursor: pointer;
294
+ }
300
295
 
301
- .spectre-link {
302
- display: inline;
303
- border-bottom: 2px solid #3196d6;
304
- text-decoration: none;
305
- cursor: pointer;
306
- }
296
+ .spectre-link:hover {
297
+ background-color: #dbedf8;
298
+ }
307
299
 
308
- .spectre-link:hover {
309
- background-color: #dbedf8;
310
- }
300
+ legend.spectre-toggle {
301
+ cursor: pointer;
302
+ user-select: none;
303
+ }
311
304
 
312
- legend.spectre-toggle {
313
- cursor: pointer;
314
- user-select: none;
315
- }
305
+ legend.spectre-toggle:before {
306
+ content: '+';
307
+ margin-right: 0.2em;
308
+ }
316
309
 
317
- legend.spectre-toggle:before {
318
- content: '+';
319
- margin-right: 0.2em;
320
- }
310
+ .spectre-expander:before {
311
+ font-family: 'Font Awesome';
312
+ font-weight: 400;
313
+ display: inline-block;
314
+ cursor: pointer;
315
+ content: '\\f0fe';
316
+ margin-right: 0.5rem;
317
+ }
321
318
 
322
- .spectre-expander:before {
323
- font-family: 'Font Awesome';
324
- font-weight: 400;
325
- display: inline-block;
326
- cursor: pointer;
327
- content: '\\f0fe';
328
- margin-right: 0.5rem;
329
- }
319
+ .active > .spectre-expander:before {
320
+ content: '\\f146';
321
+ }
330
322
 
331
- .active > .spectre-expander:before {
332
- content: '\\f146';
333
- }
323
+ #spectre-environment,
324
+ .spectre-command {
325
+ display: block;
326
+ font-family: monospace;
327
+ background-color: #223857;
328
+ color: #fff;
329
+ border-radius: 3px;
330
+ padding: 0.5rem;
331
+ }
334
332
 
335
- #spectre-environment,
336
- .spectre-command {
337
- display: block;
338
- font-family: monospace;
339
- background-color: #223857;
340
- color: #fff;
341
- border-radius: 3px;
342
- padding: 0.5rem;
343
- }
333
+ .spectre-command:before {
334
+ content: '$';
335
+ margin-right: 0.5rem;
336
+ }
344
337
 
345
- .spectre-command:before {
346
- content: '$';
347
- margin-right: 0.5rem;
348
- }
338
+ /* spectre-summary */
349
339
 
350
- /* spectre-summary */
340
+ .spectre-summary {
341
+ text-align: center;
342
+ }
351
343
 
352
- .spectre-summary {
353
- text-align: center;
354
- }
344
+ .spectre-summary span {
345
+ font-size: 1.5rem;
346
+ }
355
347
 
356
- .spectre-summary span {
357
- font-size: 1.5rem;
358
- }
348
+ .spectre-summary span:not(:last-child) {
349
+ margin-right: 2em;
350
+ }
359
351
 
360
- .spectre-summary span:not(:last-child) {
361
- margin-right: 2em;
362
- }
352
+ span.spectre-summary-project {
353
+ display: block;
354
+ font-size: 2.5em;
355
+ text-transform: uppercase;
356
+ margin: 1rem 0rem !important;
357
+ }
363
358
 
364
- span.spectre-summary-project {
365
- display: block;
366
- font-size: 2.5em;
367
- text-transform: uppercase;
368
- margin: 1rem 0rem !important;
369
- }
359
+ .spectre-summary-environment {
360
+ font-family: monospace;
361
+ }
370
362
 
371
- .spectre-summary-environment {
372
- font-family: monospace;
373
- }
363
+ .spectre-summary-environment:before,
364
+ .spectre-summary-date:before,
365
+ .spectre-summary-duration:before,
366
+ .spectre-summary-host:before {
367
+ font-family: 'Font Awesome';
368
+ margin-right: 0.5em;
369
+ }
374
370
 
375
- .spectre-summary-environment:before,
376
- .spectre-summary-date:before,
377
- .spectre-summary-duration:before,
378
- .spectre-summary-host:before {
379
- font-family: 'Font Awesome';
380
- margin-right: 0.5em;
381
- }
371
+ .spectre-summary-environment:before {
372
+ font-weight: 900;
373
+ content: '\\e587';
374
+ }
382
375
 
383
- .spectre-summary-environment:before {
384
- font-weight: 900;
385
- content: '\\e587';
386
- }
376
+ .spectre-summary-date:before {
377
+ content: '\\f133';
378
+ }
387
379
 
388
- .spectre-summary-date:before {
389
- content: '\\f133';
390
- }
380
+ .spectre-summary-duration:before {
381
+ font-weight: 900;
382
+ content: '\\f2f2';
383
+ }
391
384
 
392
- .spectre-summary-duration:before {
393
- font-weight: 900;
394
- content: '\\f2f2';
395
- }
385
+ .spectre-summary-host:before {
386
+ font-weight: 900;
387
+ content: '\\e4e5';
388
+ }
396
389
 
397
- .spectre-summary-host:before {
398
- font-weight: 900;
399
- content: '\\e4e5';
400
- }
390
+ ul.spectre-summary-result {
391
+ list-style: none;
392
+ padding: 0rem;
393
+ margin: 0rem;
394
+ text-align: center;
395
+ }
401
396
 
402
- ul.spectre-summary-result {
403
- list-style: none;
404
- padding: 0rem;
405
- margin: 0rem;
406
- text-align: center;
407
- }
397
+ ul.spectre-summary-result > li {
398
+ display: inline;
399
+ margin-right: 1rem;
400
+ }
408
401
 
409
- ul.spectre-summary-result > li {
410
- display: inline;
411
- margin-right: 1rem;
412
- }
402
+ ul.spectre-tags {
403
+ list-style: none;
404
+ padding: 0rem;
405
+ margin: 0rem;
406
+ user-select: none;
407
+ }
413
408
 
414
- ul.spectre-tags {
415
- list-style: none;
416
- padding: 0rem;
417
- margin: 0rem;
418
- user-select: none;
419
- }
409
+ ul.spectre-tags > li {
410
+ display: inline-block;
411
+ line-height: 1.5rem;
412
+ cursor: pointer;
413
+ }
420
414
 
421
- ul.spectre-tags > li {
422
- display: inline-block;
423
- line-height: 1.5rem;
424
- cursor: pointer;
425
- }
415
+ .spectre-tag {
416
+ color: #11c7e6;
417
+ padding: 0rem 0.5rem;
418
+ }
426
419
 
427
- .spectre-tag {
428
- color: #11c7e6;
429
- padding: 0rem 0.5rem;
430
- }
420
+ .spectre-tag:before {
421
+ content: '#';
422
+ }
431
423
 
432
- .spectre-tag:before {
433
- content: '#';
434
- }
424
+ .spectre-tag.active {
425
+ background-color: #11c7e6;
426
+ color: #223857;
427
+ border-radius: 999px;
428
+ }
435
429
 
436
- .spectre-tag.active {
437
- background-color: #11c7e6;
438
- color: #223857;
439
- border-radius: 999px;
440
- }
430
+ /* spectre-button */
431
+
432
+ .spectre-button {
433
+ background-color: #11c7e6;
434
+ border: 3px solid #11c7e6;
435
+ color: #0b2a63;
436
+ cursor: pointer;
437
+ border-radius: 999px;
438
+ padding: 0.5rem 1rem;
439
+ font-weight: bold;
440
+ user-select: none;
441
+ }
441
442
 
442
- /* spectre-button */
443
-
444
- .spectre-button {
445
- background-color: #11c7e6;
446
- border: 3px solid #11c7e6;
447
- color: #0b2a63;
448
- cursor: pointer;
449
- border-radius: 999px;
450
- padding: 0.5rem 1rem;
451
- font-weight: bold;
452
- user-select: none;
453
- }
443
+ .spectre-button:hover {
444
+ background-color: #7fe4f6;
445
+ border-color: #7fe4f6;
446
+ }
454
447
 
455
- .spectre-button:hover {
456
- background-color: #7fe4f6;
457
- border-color: #7fe4f6;
458
- }
448
+ .spectre-button:active {
449
+ background-color: #d2f6fc;
450
+ border-color: #d2f6fc;
451
+ }
459
452
 
460
- .spectre-button:active {
461
- background-color: #d2f6fc;
462
- border-color: #d2f6fc;
463
- }
453
+ .spectre-button.disabled {
454
+ background: none !important;
455
+ border-color: rgba(0, 0, 0, 0.1) !important;
456
+ color: rgba(0, 0, 0, 0.175) !important;
457
+ cursor: default !important;
458
+ }
464
459
 
465
- .spectre-button.disabled {
466
- background: none !important;
467
- border-color: rgba(0, 0, 0, 0.1) !important;
468
- color: rgba(0, 0, 0, 0.175) !important;
469
- cursor: default !important;
470
- }
460
+ .spectre-button.inactive {
461
+ background: none;
462
+ border-color: #0b2a63;
463
+ }
471
464
 
472
- .spectre-button.inactive {
473
- background: none;
474
- border-color: #0b2a63;
475
- }
465
+ .spectre-button.inactive:hover {
466
+ background-color: #0b2a63;
467
+ border-color: #0b2a63;
468
+ color: #fff;
469
+ }
476
470
 
477
- .spectre-button.inactive:hover {
478
- background-color: #0b2a63;
479
- border-color: #0b2a63;
480
- color: #fff;
481
- }
471
+ .spectre-button.inactive:active {
472
+ background-color: #0b3476;
473
+ border-color: #0b3476;
474
+ color: #fff;
475
+ }
482
476
 
483
- .spectre-button.inactive:active {
484
- background-color: #0b3476;
485
- border-color: #0b3476;
486
- color: #fff;
487
- }
477
+ /* spectre-badge */
488
478
 
489
- /* spectre-badge */
479
+ .spectre-badge {
480
+ font-size: 0.8em;
481
+ font-weight: bold;
482
+ background-color: #11c7e6;
483
+ color: #0b2a63;
484
+ border-radius: 999rem;
485
+ padding: 0.2rem 0.8rem;
486
+ }
490
487
 
491
- .spectre-badge {
492
- font-size: 0.8em;
493
- font-weight: bold;
494
- background-color: #11c7e6;
495
- color: #0b2a63;
496
- border-radius: 999rem;
497
- padding: 0.2rem 0.8rem;
498
- }
488
+ /* spectre-result */
499
489
 
500
- /* spectre-result */
490
+ .spectre-result-subjects {
491
+ list-style: none;
492
+ padding: 3rem;
493
+ margin: 0rem;
494
+ background-color: #fff;
495
+ border-radius: 3px;
496
+ box-shadow: 0 2px 5px 0 rgb(0 0 0 / 20%);
497
+ }
501
498
 
502
- .spectre-result-subjects {
503
- list-style: none;
504
- padding: 3rem;
505
- margin: 0rem;
506
- background-color: #fff;
507
- border-radius: 3px;
508
- box-shadow: 0 2px 5px 0 rgb(0 0 0 / 20%);
509
- }
499
+ .spectre-result-runinfos {
500
+ display: none;
501
+ }
510
502
 
511
- .spectre-result-runinfos {
512
- display: none;
513
- }
503
+ .active > .spectre-result-runinfos {
504
+ display: block;
505
+ }
514
506
 
515
- .active > .spectre-result-runinfos {
516
- display: block;
517
- }
507
+ ul.spectre-result-contexts, ul.spectre-result-runinfos {
508
+ list-style: none;
509
+ margin: 0rem;
510
+ }
518
511
 
519
- ul.spectre-result-contexts, ul.spectre-result-runinfos {
520
- list-style: none;
521
- margin: 0rem;
522
- }
512
+ .spectre-result-subjects > li {
513
+ margin-bottom: 1rem;
514
+ }
523
515
 
524
- .spectre-result-subjects > li {
525
- margin-bottom: 1rem;
526
- }
516
+ /* spectre-subject */
527
517
 
528
- /* spectre-subject */
518
+ .spectre-subject-desc {
519
+ font-weight: bold;
520
+ font-size: 1.3rem;
521
+ }
529
522
 
530
- .spectre-subject-desc {
531
- font-weight: bold;
532
- font-size: 1.3rem;
533
- }
523
+ /* spectre-context */
534
524
 
535
- /* spectre-context */
525
+ .spectre-context-desc {
526
+ font-style: italic;
527
+ color: #11c7e6;
528
+ }
536
529
 
537
- .spectre-context-desc {
538
- font-style: italic;
539
- color: #11c7e6;
540
- }
530
+ .spectre-context {
531
+ line-height: 1.5em;
532
+ }
541
533
 
542
- .spectre-context {
543
- line-height: 1.5em;
544
- }
534
+ /* spectre-runinfo */
545
535
 
546
- /* spectre-runinfo */
536
+ .spectre-runinfo {
537
+ padding: 0.8rem 1rem;
538
+ }
547
539
 
548
- .spectre-runinfo {
549
- padding: 0.8rem 1rem;
550
- }
540
+ .spectre-runinfo:not(:last-child) {
541
+ border-bottom: 1px solid rgb(0, 0, 0, 0.1);
542
+ }
551
543
 
552
- .spectre-runinfo:not(:last-child) {
553
- border-bottom: 1px solid rgb(0, 0, 0, 0.1);
554
- }
544
+ .spectre-runinfo-description > span {
545
+ margin: 0em 0.2em;
546
+ }
555
547
 
556
- .spectre-runinfo-description > span {
557
- margin: 0em 0.2em;
558
- }
548
+ .spectre-runinfo-details {
549
+ margin: 1rem 0rem;
550
+ padding-left: 1rem;
551
+ display: none;
552
+ }
559
553
 
560
- .spectre-runinfo-details {
561
- margin: 1rem 0rem;
562
- padding-left: 1rem;
563
- display: none;
564
- }
554
+ .spectre-runinfo.active .spectre-runinfo-details {
555
+ display: block;
556
+ }
565
557
 
566
- .spectre-runinfo.active .spectre-runinfo-details {
567
- display: block;
568
- }
558
+ .spectre-description-data {
559
+ color: #11c7e6;
560
+ }
569
561
 
570
- .spectre-description-data {
571
- color: #11c7e6;
572
- }
562
+ .spectre-description-name {
563
+ color: #9aa7b9;
564
+ }
573
565
 
574
- .spectre-description-name {
575
- color: #9aa7b9;
576
- }
566
+ .spectre-description-data:before,
567
+ .spectre-description-name:before {
568
+ content: '[';
569
+ }
577
570
 
578
- .spectre-description-data:before,
579
- .spectre-description-name:before {
580
- content: '[';
581
- }
571
+ .spectre-description-data:after,
572
+ .spectre-description-name:after {
573
+ content: ']';
574
+ }
582
575
 
583
- .spectre-description-data:after,
584
- .spectre-description-name:after {
585
- content: ']';
586
- }
576
+ .spectre-file {
577
+ font-family: monospace;
578
+ }
587
579
 
588
- .spectre-file {
589
- font-family: monospace;
590
- }
580
+ .spectre-code {
581
+ font-family: monospace;
582
+ }
591
583
 
592
- .spectre-code {
593
- font-family: monospace;
594
- }
584
+ .spectre-date {
585
+ font-style: italic;
586
+ }
595
587
 
596
- .spectre-date {
597
- font-style: italic;
598
- }
588
+ /* spectre-details */
599
589
 
600
- /* spectre-details */
590
+ .spectre-details-status {
591
+ text-transform: uppercase;
592
+ }
601
593
 
602
- .spectre-details-status {
603
- text-transform: uppercase;
604
- }
594
+ .spectre-evaluation-list {
595
+ list-style: none;
596
+ padding: 0;
597
+ margin: 0;
598
+ }
605
599
 
606
- /* spectre icons */
600
+ .spectre-failure-list {
601
+ list-style: none;
602
+ padding-left: 2em;
603
+ color: #e61160;
604
+ }
607
605
 
608
- .spectre-description-status:before {
609
- font-family: 'Font Awesome';
610
- font-weight: 900;
611
- margin: 0em 0.3em;
612
- }
606
+ /* spectre icons */
613
607
 
614
- .spectre-runinfo.spectre-status-success .spectre-description-status:before {
615
- content: '\\f00c';
616
- color: #9ddf1c;
617
- }
608
+ .spectre-description-status:before {
609
+ font-family: 'Font Awesome';
610
+ font-weight: 900;
611
+ margin: 0em 0.3em;
612
+ }
618
613
 
619
- .spectre-runinfo.spectre-status-failed .spectre-description-status:before {
620
- content: '\\f7a9';
621
- color: #e61160;
622
- }
614
+ .spectre-runinfo.spectre-status-success .spectre-description-status:before {
615
+ content: '\\f00c';
616
+ color: #9ddf1c;
617
+ }
623
618
 
624
- .spectre-runinfo.spectre-status-error .spectre-description-status:before {
625
- content: '\\f057';
626
- font-weight: 400;
627
- color: #f5d915;
628
- }
619
+ .spectre-runinfo.spectre-status-failed .spectre-description-status:before {
620
+ content: '\\f7a9';
621
+ color: #e61160;
622
+ }
629
623
 
630
- .spectre-runinfo.spectre-status-skipped .spectre-description-status:before {
631
- content: '\\f04e';
632
- color: #c1d5d9;
633
- }
624
+ .spectre-runinfo.spectre-status-error .spectre-description-status:before {
625
+ content: '\\f057';
626
+ font-weight: 400;
627
+ color: #f5d915;
628
+ }
634
629
 
635
- /* spectre-status colors */
630
+ .spectre-runinfo.spectre-status-skipped .spectre-description-status:before {
631
+ content: '\\f04e';
632
+ color: #c1d5d9;
633
+ }
636
634
 
637
- /* spectre-status colors SUCCESS */
635
+ /* spectre-status colors */
638
636
 
639
- .spectre-runinfo.spectre-status-success .spectre-details-status,
640
- .spectre-button.spectre-summary-succeeded,
641
- .spectre-status-success #spectre-banner {
642
- background-color: #9ddf1c;
643
- border-color: #9ddf1c;
644
- }
637
+ /* spectre-status colors SUCCESS */
645
638
 
646
- .spectre-button.spectre-summary-succeeded:hover {
647
- background-color: #c1f55b;
648
- border-color: #c1f55b;
649
- color: #0b2a63;
650
- }
639
+ .spectre-runinfo.spectre-status-success .spectre-details-status,
640
+ .spectre-button.spectre-summary-succeeded,
641
+ .spectre-status-success #spectre-banner {
642
+ background-color: #9ddf1c;
643
+ border-color: #9ddf1c;
644
+ }
651
645
 
652
- .spectre-button.spectre-summary-succeeded:active {
653
- background-color: #d3ff7c;
654
- border-color: #d3ff7c;
655
- color: #0b2a63;
656
- }
646
+ .spectre-button.spectre-summary-succeeded:hover {
647
+ background-color: #c1f55b;
648
+ border-color: #c1f55b;
649
+ color: #0b2a63;
650
+ }
657
651
 
658
- .spectre-button.inactive.spectre-summary-succeeded {
659
- background: none;
660
- border-color: #9ddf1c;
661
- color: #0b2a63;
662
- }
652
+ .spectre-button.spectre-summary-succeeded:active {
653
+ background-color: #d3ff7c;
654
+ border-color: #d3ff7c;
655
+ color: #0b2a63;
656
+ }
663
657
 
664
- .spectre-button.inactive.spectre-summary-succeeded:hover {
665
- background-color: #9ddf1c;
666
- border-color: #9ddf1c;
667
- }
658
+ .spectre-button.inactive.spectre-summary-succeeded {
659
+ background: none;
660
+ border-color: #9ddf1c;
661
+ color: #0b2a63;
662
+ }
668
663
 
669
- .spectre-button.inactive.spectre-summary-succeeded:active {
670
- background-color: #83bd11;
671
- border-color: #83bd11;
672
- }
664
+ .spectre-button.inactive.spectre-summary-succeeded:hover {
665
+ background-color: #9ddf1c;
666
+ border-color: #9ddf1c;
667
+ }
673
668
 
674
- .spectre-log-level-info {
675
- color: #9ddf1c;
676
- }
669
+ .spectre-button.inactive.spectre-summary-succeeded:active {
670
+ background-color: #83bd11;
671
+ border-color: #83bd11;
672
+ }
677
673
 
678
- /* spectre-status colors FAILED */
674
+ .spectre-log-level-info {
675
+ color: #9ddf1c;
676
+ }
679
677
 
680
- .spectre-runinfo.spectre-status-failed .spectre-details-status,
681
- .spectre-button.spectre-summary-failures,
682
- .spectre-status-failed #spectre-banner {
683
- background-color: #e61160;
684
- border-color: #e61160;
685
- }
678
+ /* spectre-status colors FAILED */
686
679
 
687
- .spectre-button.spectre-summary-failures:hover {
688
- background-color: #f56198;
689
- border-color: #f56198;
690
- color: #0b2a63;
691
- }
680
+ .spectre-runinfo.spectre-status-failed .spectre-details-status,
681
+ .spectre-button.spectre-summary-failures,
682
+ .spectre-status-failed #spectre-banner {
683
+ background-color: #e61160;
684
+ border-color: #e61160;
685
+ }
692
686
 
693
- .spectre-button.spectre-summary-failures:active {
694
- background-color: #ffadcb;
695
- border-color: #ffadcb;
696
- color: #0b2a63;
697
- }
687
+ .spectre-button.spectre-summary-failures:hover {
688
+ background-color: #f56198;
689
+ border-color: #f56198;
690
+ color: #0b2a63;
691
+ }
698
692
 
699
- .spectre-button.inactive.spectre-summary-failures {
700
- background: none;
701
- border-color: #e61160;
702
- color: #0b2a63;
703
- }
693
+ .spectre-button.spectre-summary-failures:active {
694
+ background-color: #ffadcb;
695
+ border-color: #ffadcb;
696
+ color: #0b2a63;
697
+ }
704
698
 
705
- .spectre-button.inactive.spectre-summary-failures:hover {
706
- background-color: #e61160;
707
- border-color: #e61160;
708
- }
699
+ .spectre-button.inactive.spectre-summary-failures {
700
+ background: none;
701
+ border-color: #e61160;
702
+ color: #0b2a63;
703
+ }
709
704
 
710
- .spectre-button.inactive.spectre-summary-failures:active {
711
- background-color: #bb084a;
712
- border-color: #bb084a;
713
- }
705
+ .spectre-button.inactive.spectre-summary-failures:hover {
706
+ background-color: #e61160;
707
+ border-color: #e61160;
708
+ }
714
709
 
715
- .spectre-log-level-error {
716
- color: #e61160;
717
- }
710
+ .spectre-button.inactive.spectre-summary-failures:active {
711
+ background-color: #bb084a;
712
+ border-color: #bb084a;
713
+ }
718
714
 
719
- /* spectre-status colors ERROR */
715
+ .spectre-log-level-error {
716
+ color: #e61160;
717
+ }
720
718
 
721
- .spectre-runinfo.spectre-status-error .spectre-details-status,
722
- .spectre-button.spectre-summary-errors,
723
- .spectre-status-error #spectre-banner {
724
- background-color: #f5d915;
725
- border-color: #f5d915;
726
- }
719
+ /* spectre-status colors ERROR */
727
720
 
728
- .spectre-button.spectre-summary-errors:hover {
729
- background-color: #fde95e;
730
- border-color: #fde95e;
731
- color: #0b2a63;
732
- }
721
+ .spectre-runinfo.spectre-status-error .spectre-details-status,
722
+ .spectre-button.spectre-summary-errors,
723
+ .spectre-status-error #spectre-banner {
724
+ background-color: #f5d915;
725
+ border-color: #f5d915;
726
+ }
733
727
 
734
- .spectre-button.spectre-summary-errors:active {
735
- background-color: #fff29b;
736
- border-color: #fff29b;
737
- color: #0b2a63;
738
- }
728
+ .spectre-button.spectre-summary-errors:hover {
729
+ background-color: #fde95e;
730
+ border-color: #fde95e;
731
+ color: #0b2a63;
732
+ }
739
733
 
740
- .spectre-button.inactive.spectre-summary-errors {
741
- background: none;
742
- border-color: #f5d915;
743
- color: #0b2a63;
744
- }
734
+ .spectre-button.spectre-summary-errors:active {
735
+ background-color: #fff29b;
736
+ border-color: #fff29b;
737
+ color: #0b2a63;
738
+ }
745
739
 
746
- .spectre-button.inactive.spectre-summary-errors:hover {
747
- background-color: #f5d915;
748
- border-color: #f5d915;
749
- }
740
+ .spectre-button.inactive.spectre-summary-errors {
741
+ background: none;
742
+ border-color: #f5d915;
743
+ color: #0b2a63;
744
+ }
750
745
 
751
- .spectre-button.inactive.spectre-summary-errors:active {
752
- background-color: #e7ca00;
753
- border-color: #e7ca00;
754
- }
746
+ .spectre-button.inactive.spectre-summary-errors:hover {
747
+ background-color: #f5d915;
748
+ border-color: #f5d915;
749
+ }
755
750
 
756
- .spectre-log-level-warn {
757
- color: #f5d915;
758
- }
751
+ .spectre-button.inactive.spectre-summary-errors:active {
752
+ background-color: #e7ca00;
753
+ border-color: #e7ca00;
754
+ }
759
755
 
760
- /* spectre-status colors SKIPPED */
756
+ .spectre-log-level-warn {
757
+ color: #f5d915;
758
+ }
761
759
 
762
- .spectre-runinfo.spectre-status-skipped .spectre-details-status,
763
- .spectre-button.spectre-summary-skipped,
764
- .spectre-status-skipped #spectre-banner {
765
- background-color: #c1d5d9;
766
- border-color: #c1d5d9;
767
- }
760
+ /* spectre-status colors SKIPPED */
768
761
 
769
- .spectre-log-level-debug {
770
- color: #c1d5d9;
771
- }
762
+ .spectre-runinfo.spectre-status-skipped .spectre-details-status,
763
+ .spectre-button.spectre-summary-skipped,
764
+ .spectre-status-skipped #spectre-banner {
765
+ background-color: #c1d5d9;
766
+ border-color: #c1d5d9;
767
+ }
772
768
 
773
- /* spectre-log */
769
+ .spectre-log-level-debug {
770
+ color: #c1d5d9;
771
+ }
774
772
 
775
- .spectre-log {
776
- font-family: monospace;
777
- font-size: 0.8rem;
778
- list-style: none;
779
- padding: 0rem;
780
- margin: 0rem;
781
- }
773
+ /* spectre-log */
782
774
 
783
- .spectre-log-entry {
784
- display: block;
785
- font-family: monospace;
786
- white-space: pre;
787
- }
775
+ .spectre-log {
776
+ font-family: monospace;
777
+ font-size: 0.8rem;
778
+ list-style: none;
779
+ padding: 0rem;
780
+ margin: 0rem;
781
+ }
788
782
 
789
- .spectre-log-timestamp {
790
- font-style: italic;
791
- color: rgba(0, 0, 0, 0.5);
792
- }
783
+ .spectre-log-entry {
784
+ display: block;
785
+ font-family: monospace;
786
+ white-space: pre;
787
+ }
793
788
 
794
- .spectre-log-timestamp:before {
795
- content: '[';
796
- color: #000;
797
- }
789
+ .spectre-log-timestamp {
790
+ font-style: italic;
791
+ color: rgba(0, 0, 0, 0.5);
792
+ }
798
793
 
799
- .spectre-log-timestamp:after {
800
- content: ']';
801
- color: #000;
802
- }
794
+ .spectre-log-timestamp:before {
795
+ content: '[';
796
+ color: #000;
797
+ }
803
798
 
804
- .spectre-log-level {
805
- text-transform: uppercase;
806
- }
807
- </style>
808
- </head>
809
- <body>
810
- <div id="app">
811
- <div :class="'spectre-status-' + spectreReport.overall_status">
812
- <div id="spectre-banner">{{ spectreReport.overall_status }}</div>
813
-
814
- <div class="spectre-summary">
815
- #{icon}
816
-
817
- <span class="spectre-summary-project">{{ spectreReport.project }}</span>
818
-
819
- <span class="spectre-summary-environment">{{ spectreReport.environment }}</span>
820
- <span class="spectre-summary-date">{{ new Date(spectreReport.date).toLocaleString() }}</span>
821
- <span class="spectre-summary-duration">{{ spectreReport.duration.toDurationString() }}</span>
822
- <span class="spectre-summary-host">{{ spectreReport.hostname }}</span>
823
- </div>
799
+ .spectre-log-timestamp:after {
800
+ content: ']';
801
+ color: #000;
802
+ }
824
803
 
825
- <div id="spectre-report">
826
- <section>
827
- <div class="spectre-command">{{ spectreCommand }}</div>
828
- </section>
829
-
830
- <section id="spectre-tags">
831
- <ul class="spectre-tags">
832
- <li class="spectre-tag" v-for="tag in spectreReport.tags" @click="toggleTagFilter(tag)" :class="{ active: tagFilter.includes(tag)}">{{ tag }}</li>
833
- </ul>
834
- </section>
835
-
836
- <section>
837
- <ul class="spectre-summary-result">
838
- <li
839
- class="spectre-button spectre-summary-succeeded"
840
- :class="{ disabled: spectreReport.succeeded == 0, inactive: statusFilter != null && statusFilter != 'success' }"
841
- @click="filter('success')">{{ spectreReport.succeeded }} succeeded</li>
842
-
843
- <li
844
- class="spectre-button spectre-summary-skipped"
845
- :class="{ disabled: spectreReport.skipped == 0, inactive: statusFilter != null && statusFilter != 'skipped' }"
846
- @click="filter('skipped')">{{ spectreReport.skipped }} skipped</li>
847
-
848
- <li
849
- class="spectre-button spectre-summary-failures"
850
- :class="{ disabled: spectreReport.failures == 0, inactive: statusFilter != null && statusFilter != 'failed' }"
851
- @click="filter('failed')">{{ spectreReport.failures }} failures</li>
852
-
853
- <li
854
- class="spectre-button spectre-summary-errors"
855
- :class="{ disabled: spectreReport.errors == 0, inactive: statusFilter != null && statusFilter != 'error' }"
856
- @click="filter('error')">{{ spectreReport.errors }} errors</li>
857
-
858
- <li
859
- class="spectre-button spectre-summary-total"
860
- :class="{ disabled: spectreReport.total == 0, inactive: statusFilter != null }"
861
- @click="showAll()">{{ spectreReport.total }} total</li>
862
- </ul>
863
- </section>
864
-
865
- <section>
866
- <ul class="spectre-section spectre-controls">
867
- <li class="spectre-link" @click="collapseAll()">collapse all</li>
868
- <li class="spectre-link" @click="expandAll()">expand all</li>
869
- <li class="spectre-link spectre-controls-clear" :class="{ active: tagFilter.length > 0 || statusFilter != null }" @click="clearFilter()">
870
- <span class="spectre-filter-count">{{ filteredResults.length }}/{{ spectreReport.run_infos.length }}<span>
871
- </li>
872
- </ul>
873
- </section>
874
-
875
- <section>
876
- <ul class="spectre-result-subjects">
877
- <li class="spectre-subject" v-for="(contexts, subject) in mappedResults">
878
- <span class="spectre-subject-desc">{{ subject }}</span>
879
-
880
- <ul class="spectre-result-contexts">
881
- <li class="spectre-context" v-for="(runInfos, context) in contexts" :class="{ active: expandedContexts.includes(subject + '_' + context) }">
882
- <span class="spectre-expander" @click="toggleContext(subject, context)"></span>
883
- <span class="spectre-context-desc">{{ context }}</span>
884
-
885
- <ul class="spectre-result-runinfos">
886
- <li class="spectre-runinfo" v-for="runInfo in runInfos" :class="['spectre-status-' + runInfo.status, { active: shownDetails.includes(runInfo) }]">
887
- <span class="spectre-expander" @click="showDetails(runInfo)"></span>
888
- <span class="spectre-runinfo-description">
889
- <span class="spectre-description-status"></span>
890
- <span class="spectre-description-name">{{ runInfo.name }}</span>
891
- <span class="spectre-description-data" v-if="runInfo.data">{{ runInfo.data }}</span>
892
- <span class="spectre-description-subject">{{ subject }}</span>
893
- <span class="spectre-description-spec">{{ runInfo.desc }}</span>
894
- </span>
895
-
896
- <div class="spectre-runinfo-details">
897
- <fieldset>
898
- <legend>Run Info</legend>
899
-
900
- <table>
901
- <tr><th>Status</th><td><span class="spectre-badge spectre-details-status">{{ runInfo.status }}</span></td></tr>
902
- <tr><th>Name</th><td>{{ runInfo.name }}</td></tr>
903
- <tr><th>Description</th><td>{{ runInfo.desc }}</td></tr>
904
- <tr><th>Tags</th><td>
905
- <ul class="spectre-tags">
906
- <li class="spectre-tag" v-for="tag in runInfo.tags" @click="toggleTagFilter(tag)" :class="{ active: tagFilter.includes(tag)}">{{ tag }}</li>
907
- </ul>
908
- </td></tr>
909
- <tr><th>File</th><td><span class="spectre-file">{{ runInfo.file }}<span></td></tr>
910
- <tr><th>Started</th><td><span class="spectre-date">{{ runInfo.started }}<span></td></tr>
911
- <tr><th>Finished</th><td><span class="spectre-date">{{ runInfo.finished }}<span></td></tr>
912
- <tr><th>Duration</th><td>{{ runInfo.duration.toDurationString() }}</td></tr>
913
- </table>
914
- </fieldset>
915
-
916
- <fieldset class="spectre-runinfo-data" v-if="runInfo.data">
917
- <legend>Data</legend>
918
- <pre>{{ runInfo.data }}</pre>
919
- </fieldset>
920
-
921
- <fieldset class="spectre-runinfo-properties" v-if="Object.keys(runInfo.properties).length > 0">
922
- <legend>Properties</legend>
923
-
924
- <table>
925
- <tr v-for="(item, key) in runInfo.properties" :key="key"><th>{{ key }}</th><td>{{ item }}</td></tr>
926
- </table>
927
- </fieldset>
928
-
929
- <fieldset class="spectre-runinfo-failure" v-if="runInfo.failure">
930
- <legend>Failure</legend>
931
-
932
- <table>
933
- <tr><th>Message</th><td>{{ runInfo.failure.message }}</td></tr>
934
- <tr><th>Expected</th><td>{{ runInfo.failure.expected }}</td></tr>
935
- <tr><th>Actual</th><td>{{ runInfo.failure.actual }}</td></tr>
936
- </table>
937
- </fieldset>
938
-
939
- <fieldset class="spectre-runinfo-error" v-if="runInfo.error">
940
- <legend>Error</legend>
941
-
942
- <table>
943
- <tr><th>File</th><td><span class="spectre-file">{{ runInfo.error.file }}</span></td></tr>
944
- <tr><th>Line</th><td>{{ runInfo.error.line }}</td></tr>
945
- <tr><th>Type</th><td><span class="spectre-code">{{ runInfo.error.type }}</span></td></tr>
946
- <tr><th>Message</th><td>{{ runInfo.error.message }}</td></tr>
947
- </table>
948
- </fieldset>
949
-
950
- <fieldset class="spectre-runinfo-stacktrace" v-if="runInfo.error && runInfo.error.stack_strace">
951
- <ul>
952
- <li v-for="stackTraceEntry in runInfo.error.stack_strace">{{ stackTraceEntry }}</li>
953
- </ul>
954
- </fieldset>
955
-
956
- <fieldset class="spectre-runinfo-log" v-if="runInfo.log && runInfo.log.length > 0">
957
- <legend class="spectre-toggle" @click="toggleLog(runInfo)">Log</legend>
958
-
959
- <ul class="spectre-log" v-if="shownLogs.includes(runInfo)">
960
- <li v-for="logEntry in runInfo.log" class="spectre-log-entry">
961
- <span class="spectre-log-timestamp">{{ logEntry[0] }}</span> <span class="spectre-log-level" :class="'spectre-log-level-' + logEntry[2]">{{ logEntry[2] }}</span> -- <span class="spectre-log-name">{{ logEntry[3] }}</span>: <span class="spectre-log-message">{{ logEntry[1] }}</span>
962
- </li>
963
- </ul>
964
- </fieldset>
965
- <div>
966
- </li>
967
- </ul>
968
- </li>
969
- </ul>
970
- </li>
971
- </ul>
972
- </section>
973
-
974
- <section id="spectre-environment">
975
- <pre>#{ CGI::escapeHTML(YAML.dump json_report[:config]) }</pre>
976
- </section>
804
+ .spectre-log-level {
805
+ text-transform: uppercase;
806
+ }
807
+ </style>
808
+ </head>
809
+ <body>
810
+ <div id="app">
811
+ <div :class="'spectre-status-' + spectreReport.overall_status">
812
+ <div id="spectre-banner">{{ spectreReport.overall_status }}</div>
813
+
814
+ <div class="spectre-summary">
815
+ #{icon}
816
+
817
+ <span class="spectre-summary-project">{{ spectreReport.project }}</span>
818
+
819
+ <span class="spectre-summary-environment">{{ spectreReport.environment }}</span>
820
+ <span class="spectre-summary-date">{{ new Date(spectreReport.date).toLocaleString() }}</span>
821
+ <span class="spectre-summary-duration">{{ spectreReport.duration.toDurationString() }}</span>
822
+ <span class="spectre-summary-host">{{ spectreReport.hostname }}</span>
823
+ </div>
824
+
825
+ <div id="spectre-report">
826
+ <section>
827
+ <div class="spectre-command">{{ spectreCommand }}</div>
828
+ </section>
829
+
830
+ <section id="spectre-tags">
831
+ <ul class="spectre-tags">
832
+ <li class="spectre-tag" v-for="tag in spectreReport.tags" @click="toggleTagFilter(tag)" :class="{ active: tagFilter.includes(tag)}">{{ tag }}</li>
833
+ </ul>
834
+ </section>
835
+
836
+ <section>
837
+ <ul class="spectre-summary-result">
838
+ <li
839
+ class="spectre-button spectre-summary-succeeded"
840
+ :class="{ disabled: spectreReport.succeeded == 0, inactive: statusFilter != null && statusFilter != 'success' }"
841
+ @click="filter('success')">{{ spectreReport.succeeded }} succeeded</li>
842
+
843
+ <li
844
+ class="spectre-button spectre-summary-skipped"
845
+ :class="{ disabled: spectreReport.skipped == 0, inactive: statusFilter != null && statusFilter != 'skipped' }"
846
+ @click="filter('skipped')">{{ spectreReport.skipped }} skipped</li>
847
+
848
+ <li
849
+ class="spectre-button spectre-summary-failures"
850
+ :class="{ disabled: spectreReport.failures == 0, inactive: statusFilter != null && statusFilter != 'failed' }"
851
+ @click="filter('failed')">{{ spectreReport.failures }} failures</li>
852
+
853
+ <li
854
+ class="spectre-button spectre-summary-errors"
855
+ :class="{ disabled: spectreReport.errors == 0, inactive: statusFilter != null && statusFilter != 'error' }"
856
+ @click="filter('error')">{{ spectreReport.errors }} errors</li>
857
+
858
+ <li
859
+ class="spectre-button spectre-summary-total"
860
+ :class="{ disabled: spectreReport.total == 0, inactive: statusFilter != null }"
861
+ @click="showAll()">{{ spectreReport.total }} total</li>
862
+ </ul>
863
+ </section>
864
+
865
+ <section>
866
+ <ul class="spectre-section spectre-controls">
867
+ <li class="spectre-link" @click="collapseAll()">collapse all</li>
868
+ <li class="spectre-link" @click="expandAll()">expand all</li>
869
+ <li class="spectre-link spectre-controls-clear" :class="{ active: tagFilter.length > 0 || statusFilter != null }" @click="clearFilter()">
870
+ <span class="spectre-filter-count">{{ filteredResults.length }}/{{ spectreReport.run_infos.length }}<span>
871
+ </li>
872
+ </ul>
873
+ </section>
874
+
875
+ <section>
876
+ <ul class="spectre-result-subjects">
877
+ <li class="spectre-subject" v-for="(contexts, subject) in mappedResults">
878
+ <span class="spectre-subject-desc">{{ subject }}</span>
879
+
880
+ <ul class="spectre-result-contexts">
881
+ <li class="spectre-context" v-for="(runInfos, context) in contexts" :class="{ active: expandedContexts.includes(subject + '_' + context) }">
882
+ <span class="spectre-expander" @click="toggleContext(subject, context)"></span>
883
+ <span class="spectre-context-desc">{{ context }}</span>
884
+
885
+ <ul class="spectre-result-runinfos">
886
+ <li class="spectre-runinfo" v-for="runInfo in runInfos" :class="['spectre-status-' + runInfo.status, { active: shownDetails.includes(runInfo) }]">
887
+ <span class="spectre-expander" @click="showDetails(runInfo)"></span>
888
+ <span class="spectre-runinfo-description">
889
+ <span class="spectre-description-status"></span>
890
+ <span class="spectre-description-name">{{ runInfo.name }}</span>
891
+ <span class="spectre-description-data" v-if="runInfo.data">{{ runInfo.data }}</span>
892
+ <span class="spectre-description-subject">{{ subject }}</span>
893
+ <span class="spectre-description-spec">{{ runInfo.desc }}</span>
894
+ </span>
895
+
896
+ <div class="spectre-runinfo-details">
897
+ <fieldset>
898
+ <legend>Run Info</legend>
899
+
900
+ <table>
901
+ <tr><th>Status</th><td><span class="spectre-badge spectre-details-status">{{ runInfo.status }}</span></td></tr>
902
+ <tr><th>Name</th><td>{{ runInfo.name }}</td></tr>
903
+ <tr><th>Description</th><td>{{ runInfo.desc }}</td></tr>
904
+ <tr><th>Tags</th><td>
905
+ <ul class="spectre-tags">
906
+ <li class="spectre-tag" v-for="tag in runInfo.tags" @click="toggleTagFilter(tag)" :class="{ active: tagFilter.includes(tag)}">{{ tag }}</li>
907
+ </ul>
908
+ </td></tr>
909
+ <tr><th>File</th><td><span class="spectre-file">{{ runInfo.file }}<span></td></tr>
910
+ <tr><th>Started</th><td><span class="spectre-date">{{ runInfo.started }}<span></td></tr>
911
+ <tr><th>Finished</th><td><span class="spectre-date">{{ runInfo.finished }}<span></td></tr>
912
+ <tr><th>Duration</th><td>{{ runInfo.duration.toDurationString() }}</td></tr>
913
+ </table>
914
+ </fieldset>
915
+
916
+ <fieldset class="spectre-runinfo-data" v-if="runInfo.data">
917
+ <legend>Data</legend>
918
+ <pre>{{ runInfo.data }}</pre>
919
+ </fieldset>
920
+
921
+ <fieldset class="spectre-runinfo-properties" v-if="Object.keys(runInfo.properties).length > 0">
922
+ <legend>Properties</legend>
923
+
924
+ <table>
925
+ <tr v-for="(item, key) in runInfo.properties" :key="key"><th>{{ key }}</th><td>{{ item }}</td></tr>
926
+ </table>
927
+ </fieldset>
928
+
929
+ <fieldset v-if="runInfo.evaluations.length > 0">
930
+ <legend>Failures</legend>
931
+
932
+ <ul class="spectre-evaluation-list">
933
+ <li class="spectre-evaluation"v-for="evaluation in runInfo.evaluations">
934
+ <span class="spectre-evaluation-desc">{{ evaluation.desc }}</span>
935
+ <ul class="spectre-failure-list" v-for="failure in evaluation.failures">
936
+ <li class="spectre-failure">{{ failure.message }}</li>
937
+ </ul>
938
+ </li>
939
+ </ul>
940
+ </fieldset>
941
+
942
+ <fieldset class="spectre-runinfo-error" v-if="runInfo.error">
943
+ <legend>Error</legend>
944
+
945
+ <table>
946
+ <tr><th>File</th><td><span class="spectre-file">{{ runInfo.error.file }}</span></td></tr>
947
+ <tr><th>Line</th><td>{{ runInfo.error.line }}</td></tr>
948
+ <tr><th>Type</th><td><span class="spectre-code">{{ runInfo.error.type }}</span></td></tr>
949
+ <tr><th>Message</th><td>{{ runInfo.error.message }}</td></tr>
950
+ </table>
951
+ </fieldset>
952
+
953
+ <fieldset class="spectre-runinfo-stacktrace" v-if="runInfo.error && runInfo.error.stack_strace">
954
+ <ul>
955
+ <li v-for="stackTraceEntry in runInfo.error.stack_strace">{{ stackTraceEntry }}</li>
956
+ </ul>
957
+ </fieldset>
958
+
959
+ <fieldset class="spectre-runinfo-log" v-if="runInfo.log && runInfo.log.length > 0">
960
+ <legend class="spectre-toggle" @click="toggleLog(runInfo)">Log</legend>
961
+
962
+ <ul class="spectre-log" v-if="shownLogs.includes(runInfo)">
963
+ <li v-for="logEntry in runInfo.log" class="spectre-log-entry">
964
+ <span class="spectre-log-timestamp">{{ logEntry[0] }}</span> <span class="spectre-log-level" :class="'spectre-log-level-' + logEntry[1]">{{ logEntry[1] }}</span> -- <span class="spectre-log-name">{{ logEntry[2] }}</span>: <span class="spectre-log-message">{{ logEntry[3] }}</span>
965
+ </li>
966
+ </ul>
967
+ </fieldset>
968
+ <div>
969
+ </li>
970
+ </ul>
971
+ </li>
972
+ </ul>
973
+ </li>
974
+ </ul>
975
+ </section>
976
+
977
+ <section id="spectre-environment">
978
+ <pre>#{CGI.escapeHTML(YAML.dump(json_report[:config]))}</pre>
979
+ </section>
980
+ </div>
977
981
  </div>
978
982
  </div>
979
- </div>
980
-
981
- <script>
982
- Array.prototype.groupBy = function(callbackFn) {
983
- const map = new Object();
984
- this.forEach((item) => {
985
- const key = callbackFn(item);
986
- const collection = map[key];
987
- if (!collection) {
988
- map[key] = [item];
989
- } else {
990
- collection.push(item);
991
- }
992
- });
993
- return map;
994
- }
995
-
996
- Object.prototype.map = function(callbackFn) {
997
- return Object
998
- .entries(this)
999
- .map(callbackFn);
1000
- };
1001
-
1002
- Array.prototype.toDict = function() {
1003
- return Object.assign({}, ...this.map((x) => ({[x[0]]: x[1]})));
1004
- };
1005
983
 
1006
- Array.prototype.distinct = function() {
1007
- return this.filter((value, index, self) => self.indexOf(value) === index);
1008
- };
984
+ <script>
985
+ Array.prototype.groupBy = function(callbackFn) {
986
+ const map = new Object();
987
+ this.forEach((item) => {
988
+ const key = callbackFn(item);
989
+ const collection = map[key];
990
+ if (!collection) {
991
+ map[key] = [item];
992
+ } else {
993
+ collection.push(item);
994
+ }
995
+ });
996
+ return map;
997
+ }
1009
998
 
1010
- Number.prototype.toDurationString = function() {
1011
- let date = new Date(this * 1000);
1012
- let hours = date.getUTCHours();
1013
- let minutes = date.getUTCMinutes();
1014
- let seconds = date.getUTCSeconds();
1015
- let milliseconds = date.getUTCMilliseconds();
999
+ Object.prototype.map = function(callbackFn) {
1000
+ return Object
1001
+ .entries(this)
1002
+ .map(callbackFn);
1003
+ };
1016
1004
 
1017
- let durationString = '';
1005
+ Array.prototype.toDict = function() {
1006
+ return Object.assign({}, ...this.map((x) => ({[x[0]]: x[1]})));
1007
+ };
1018
1008
 
1019
- if (hours > 0) {
1020
- durationString += `${hours}h`
1021
- }
1009
+ Array.prototype.distinct = function() {
1010
+ return this.filter((value, index, self) => self.indexOf(value) === index);
1011
+ };
1022
1012
 
1023
- if (minutes > 0 || hours > 0) {
1024
- if (durationString.length > 0) {
1025
- durationString += ' ';
1026
- }
1013
+ Number.prototype.toDurationString = function() {
1014
+ let date = new Date(this * 1000);
1015
+ let hours = date.getUTCHours();
1016
+ let minutes = date.getUTCMinutes();
1017
+ let seconds = date.getUTCSeconds();
1018
+ let milliseconds = date.getUTCMilliseconds();
1027
1019
 
1028
- durationString += `${minutes}m`
1029
- }
1020
+ let durationString = '';
1030
1021
 
1031
- if (seconds > 0 || minutes > 0) {
1032
- if (durationString.length > 0) {
1033
- durationString += ' ';
1022
+ if (hours > 0) {
1023
+ durationString += `${hours}h`
1034
1024
  }
1035
1025
 
1036
- durationString += `${seconds}s`
1037
- }
1026
+ if (minutes > 0 || hours > 0) {
1027
+ if (durationString.length > 0) {
1028
+ durationString += ' ';
1029
+ }
1038
1030
 
1039
- if (milliseconds > 0) {
1040
- if (durationString.length > 0) {
1041
- durationString += ' ';
1031
+ durationString += `${minutes}m`
1042
1032
  }
1043
1033
 
1044
- durationString += `${milliseconds}ms`
1045
- }
1046
-
1047
- if (durationString.length == 0) {
1048
- return `${this}s`
1049
- }
1050
-
1051
- return durationString;
1052
- }
1034
+ if (seconds > 0 || minutes > 0) {
1035
+ if (durationString.length > 0) {
1036
+ durationString += ' ';
1037
+ }
1053
1038
 
1054
- const { createApp } = Vue;
1055
-
1056
- window.App = createApp({
1057
- data() {
1058
- return {
1059
- spectreReport: #{json_report.to_json},
1060
- statusFilter: null,
1061
- tagFilter: [],
1062
- shownDetails: [],
1063
- shownLogs: [],
1064
- expandedContexts: [],
1039
+ durationString += `${seconds}s`
1065
1040
  }
1066
- },
1067
- mounted() {
1068
- this.expandAll();
1069
- },
1070
- computed: {
1071
- filteredResults() {
1072
- return this.spectreReport.run_infos
1073
- .filter(x => this.statusFilter == null || x.status == this.statusFilter)
1074
- .filter(x => this.tagFilter.length == 0 || x.tags.filter(x => this.tagFilter.includes(x)).length > 0);
1075
- },
1076
- mappedResults() {
1077
- return this.filteredResults
1078
- .groupBy(x => x.subject)
1079
- .map(([key, val]) => [key, val.groupBy(x => x.context || '[main]')])
1080
- .toDict();
1081
- },
1082
- spectreCommand() {
1083
- let cmd = this.spectreReport.command;
1084
- let filteredSpecs = this.filteredResults;
1085
-
1086
- if (this.statusFilter == null && this.tagFilter.length > 0) {
1087
- cmd += ` -t ${this.tagFilter.join(',')}`
1088
1041
 
1089
- } else if (this.statusFilter != null && filteredSpecs.length > 0) {
1090
- cmd += ` -s ${this.filteredResults.map(x => x.name).join(',')}`
1042
+ if (milliseconds > 0) {
1043
+ if (durationString.length > 0) {
1044
+ durationString += ' ';
1091
1045
  }
1092
1046
 
1093
- return cmd;
1094
- },
1095
- },
1096
- methods: {
1097
- filter(status) {
1098
- if (this.statusFilter == status) {
1099
- this.statusFilter = null;
1100
- return;
1101
- }
1047
+ durationString += `${milliseconds}ms`
1048
+ }
1102
1049
 
1103
- if (this.spectreReport.run_infos.filter(x => x.status == status).length == 0) {
1104
- return;
1105
- }
1050
+ if (durationString.length == 0) {
1051
+ return `${this}s`
1052
+ }
1106
1053
 
1107
- this.statusFilter = status;
1108
- },
1109
- showAll() {
1110
- this.statusFilter = null;
1111
- },
1112
- toggleTagFilter(tag) {
1113
- let index = this.tagFilter.indexOf(tag);
1054
+ return durationString;
1055
+ }
1114
1056
 
1115
- if (index > -1) {
1116
- this.tagFilter.splice(index, 1);
1117
- } else {
1118
- this.tagFilter.push(tag)
1057
+ const { createApp } = Vue;
1058
+
1059
+ window.App = createApp({
1060
+ data() {
1061
+ return {
1062
+ spectreReport: #{json_report.to_json},
1063
+ statusFilter: null,
1064
+ tagFilter: [],
1065
+ shownDetails: [],
1066
+ shownLogs: [],
1067
+ expandedContexts: [],
1119
1068
  }
1120
1069
  },
1121
- clearFilter() {
1122
- this.statusFilter = null;
1123
- this.tagFilter = [];
1070
+ mounted() {
1071
+ this.expandAll();
1124
1072
  },
1125
- showDetails(runInfo) {
1126
- let index = this.shownDetails.indexOf(runInfo);
1127
-
1128
- if (index > -1) {
1129
- this.shownDetails.splice(index, 1);
1130
- } else {
1131
- this.shownDetails.push(runInfo)
1132
- }
1073
+ computed: {
1074
+ filteredResults() {
1075
+ return this.spectreReport.run_infos
1076
+ .filter(x => this.statusFilter == null || x.status == this.statusFilter)
1077
+ .filter(x => this.tagFilter.length == 0 || x.tags.filter(x => this.tagFilter.includes(x)).length > 0);
1078
+ },
1079
+ mappedResults() {
1080
+ return this.filteredResults
1081
+ .groupBy(x => x.subject)
1082
+ .map(([key, val]) => [key, val.groupBy(x => x.context || '[main]')])
1083
+ .toDict();
1084
+ },
1085
+ spectreCommand() {
1086
+ let cmd = this.spectreReport.command;
1087
+ let filteredSpecs = this.filteredResults;
1088
+
1089
+ if (this.statusFilter == null && this.tagFilter.length > 0) {
1090
+ cmd += ` -t ${this.tagFilter.join(',')}`
1091
+
1092
+ } else if (this.statusFilter != null && filteredSpecs.length > 0) {
1093
+ cmd += ` -s ${this.filteredResults.map(x => x.name).join(',')}`
1094
+ }
1095
+
1096
+ return cmd;
1097
+ },
1133
1098
  },
1134
- toggleContext(subject, context) {
1135
- let key = subject + '_' + context;
1136
-
1137
- let index = this.expandedContexts.indexOf(key);
1138
-
1139
- if (index > -1) {
1140
- this.expandedContexts.splice(index, 1);
1141
- } else {
1142
- this.expandedContexts.push(key)
1143
- }
1144
- },
1145
- toggleLog(runInfo) {
1146
- let index = this.shownLogs.indexOf(runInfo);
1147
-
1148
- if (index > -1) {
1149
- this.shownLogs.splice(index, 1);
1150
- } else {
1151
- this.shownLogs.push(runInfo)
1099
+ methods: {
1100
+ filter(status) {
1101
+ if (this.statusFilter == status) {
1102
+ this.statusFilter = null;
1103
+ return;
1104
+ }
1105
+
1106
+ if (this.spectreReport.run_infos.filter(x => x.status == status).length == 0) {
1107
+ return;
1108
+ }
1109
+
1110
+ this.statusFilter = status;
1111
+ },
1112
+ showAll() {
1113
+ this.statusFilter = null;
1114
+ },
1115
+ toggleTagFilter(tag) {
1116
+ let index = this.tagFilter.indexOf(tag);
1117
+
1118
+ if (index > -1) {
1119
+ this.tagFilter.splice(index, 1);
1120
+ } else {
1121
+ this.tagFilter.push(tag)
1122
+ }
1123
+ },
1124
+ clearFilter() {
1125
+ this.statusFilter = null;
1126
+ this.tagFilter = [];
1127
+ },
1128
+ showDetails(runInfo) {
1129
+ let index = this.shownDetails.indexOf(runInfo);
1130
+
1131
+ if (index > -1) {
1132
+ this.shownDetails.splice(index, 1);
1133
+ } else {
1134
+ this.shownDetails.push(runInfo)
1135
+ }
1136
+ },
1137
+ toggleContext(subject, context) {
1138
+ let key = subject + '_' + context;
1139
+
1140
+ let index = this.expandedContexts.indexOf(key);
1141
+
1142
+ if (index > -1) {
1143
+ this.expandedContexts.splice(index, 1);
1144
+ } else {
1145
+ this.expandedContexts.push(key)
1146
+ }
1147
+ },
1148
+ toggleLog(runInfo) {
1149
+ let index = this.shownLogs.indexOf(runInfo);
1150
+
1151
+ if (index > -1) {
1152
+ this.shownLogs.splice(index, 1);
1153
+ } else {
1154
+ this.shownLogs.push(runInfo)
1155
+ }
1156
+ },
1157
+ collapseAll() {
1158
+ this.expandedContexts = [];
1159
+ },
1160
+ expandAll() {
1161
+ this.expandedContexts = this.spectreReport.run_infos
1162
+ .map(x => x.subject + '_' + (x.context || '[main]'))
1163
+ .distinct();
1152
1164
  }
1153
- },
1154
- collapseAll() {
1155
- this.expandedContexts = [];
1156
- },
1157
- expandAll() {
1158
- this.expandedContexts = this.spectreReport.run_infos
1159
- .map(x => x.subject + '_' + (x.context || '[main]'))
1160
- .distinct();
1161
1165
  }
1162
- }
1163
- }).mount('#app')
1164
- </script>
1165
- </body>
1166
- </html>
1167
- HTML
1168
-
1169
- Dir.mkdir @config['out_path'] unless Dir.exist? @config['out_path']
1166
+ }).mount('#app')
1167
+ </script>
1168
+ </body>
1169
+ </html>
1170
+ HTML
1170
1171
 
1171
- file_path = File.join(@config['out_path'], "spectre-html_#{now.strftime('%s')}.html")
1172
+ FileUtils.mkdir_p(@config['out_path'])
1172
1173
 
1173
- File.write(file_path, html_str)
1174
- end
1174
+ file_path = File.join(@config['out_path'], "spectre-html_#{now.strftime('%s')}.html")
1175
1175
 
1176
- Spectre.register do |config|
1177
- Spectre::Reporter.add HTML.new(config)
1176
+ File.write(file_path, html_str)
1177
+ end
1178
1178
  end
1179
1179
  end
1180
1180
  end