spectre-core 1.13.0 → 1.14.0

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