spectre-reporter-html 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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