spectre-reporter-html 1.0.0 → 1.1.1

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