spectre-reporter-html 1.0.0 → 1.1.0

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