Kea2-python 0.2.2__py3-none-any.whl → 0.2.4__py3-none-any.whl

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.

Potentially problematic release.


This version of Kea2-python might be problematic. Click here for more details.

@@ -24,6 +24,47 @@
24
24
  line-height: 1.6;
25
25
  }
26
26
 
27
+ /* Custom container width - wider than Bootstrap default */
28
+ .container {
29
+ max-width: 98% !important;
30
+ width: 98% !important;
31
+ }
32
+
33
+ @media (min-width: 1200px) {
34
+ .container {
35
+ max-width: 1800px !important;
36
+ width: 95% !important;
37
+ }
38
+ }
39
+
40
+ @media (min-width: 1400px) {
41
+ .container {
42
+ max-width: 2000px !important;
43
+ width: 92% !important;
44
+ }
45
+ }
46
+
47
+ @media (min-width: 1600px) {
48
+ .container {
49
+ max-width: 2200px !important;
50
+ width: 90% !important;
51
+ }
52
+ }
53
+
54
+ @media (min-width: 1800px) {
55
+ .container {
56
+ max-width: 2400px !important;
57
+ width: 88% !important;
58
+ }
59
+ }
60
+
61
+ @media (min-width: 2000px) {
62
+ .container {
63
+ max-width: 2600px !important;
64
+ width: 85% !important;
65
+ }
66
+ }
67
+
27
68
  .header {
28
69
  background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
29
70
  color: white;
@@ -81,9 +122,10 @@
81
122
 
82
123
  .screenshot-item {
83
124
  flex: 0 0 auto;
84
- width: 250px;
125
+ width: 300px;
85
126
  position: relative;
86
127
  transition: transform 0.2s;
128
+ margin-bottom: 10px;
87
129
  }
88
130
 
89
131
  .screenshot-item:hover {
@@ -91,45 +133,111 @@
91
133
  }
92
134
 
93
135
  .screenshot-img {
94
- width: 250px;
136
+ width: 300px;
95
137
  height: 400px;
96
138
  object-fit: contain;
97
139
  border-radius: 8px;
98
140
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
141
+ margin-bottom: 8px;
99
142
  }
100
143
 
101
144
  .screenshot-caption {
102
- font-size: 14px;
145
+ font-size: 13px;
103
146
  color: #555;
104
- padding: 8px 4px;
105
- text-overflow: ellipsis;
106
- overflow: hidden;
147
+ padding: 12px 8px;
107
148
  font-weight: 500;
108
149
  text-align: center;
109
150
  background-color: white;
110
- border-radius: 0 0 8px 8px;
151
+ border-radius: 8px;
111
152
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.05);
153
+ line-height: 1.5;
154
+ white-space: normal;
155
+ word-wrap: break-word;
156
+ min-height: 50px;
157
+ display: flex;
158
+ flex-direction: column;
159
+ justify-content: center;
160
+ align-items: center;
161
+ }
162
+
163
+ .screenshot-caption .step-number {
164
+ display: block;
165
+ font-weight: 600;
166
+ color: var(--primary-color);
167
+ font-size: 12px;
168
+ margin-bottom: 4px;
169
+ }
170
+
171
+ .screenshot-caption .step-action {
172
+ display: block;
173
+ font-size: 13px;
174
+ color: #666;
175
+ font-weight: 400;
176
+ line-height: 1.3;
112
177
  }
113
178
 
114
179
  .table-custom {
115
180
  border-radius: 10px;
116
181
  overflow: hidden;
117
182
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
183
+ width: 100%;
184
+ table-layout: auto;
118
185
  }
119
186
 
120
187
  .table-custom thead {
121
- background-color: var(--primary-color);
188
+ background-color: #495057;
122
189
  color: white;
123
190
  }
124
191
 
125
192
  .table-custom th {
126
193
  font-weight: 600;
127
- padding: 15px 20px;
194
+ padding: 15px 12px;
195
+ white-space: nowrap;
196
+ text-align: center;
128
197
  }
129
198
 
130
199
  .table-custom td {
131
- padding: 15px 20px;
200
+ padding: 15px 12px;
132
201
  vertical-align: middle;
202
+ text-align: center;
203
+ }
204
+
205
+ /* Specific column widths for property statistics table */
206
+ .table-custom th:nth-child(1), .table-custom td:nth-child(1) { /* Index */
207
+ width: 8%;
208
+ min-width: 60px;
209
+ }
210
+
211
+ .table-custom th:nth-child(2), .table-custom td:nth-child(2) { /* Property Name */
212
+ width: 25%;
213
+ min-width: 200px;
214
+ text-align: left;
215
+ }
216
+
217
+ .table-custom th:nth-child(3), .table-custom td:nth-child(3) { /* Precondition Satisfied */
218
+ width: 12%;
219
+ min-width: 100px;
220
+ }
221
+
222
+ .table-custom th:nth-child(4), .table-custom td:nth-child(4) { /* Executed */
223
+ width: 10%;
224
+ min-width: 80px;
225
+ }
226
+
227
+ .table-custom th:nth-child(5), .table-custom td:nth-child(5) { /* Fails */
228
+ width: 10%;
229
+ min-width: 80px;
230
+ }
231
+
232
+ .table-custom th:nth-child(6), .table-custom td:nth-child(6) { /* Errors */
233
+ width: 10%;
234
+ min-width: 80px;
235
+ }
236
+
237
+ .table-custom th:nth-child(7), .table-custom td:nth-child(7) { /* Error Details */
238
+ width: 25%;
239
+ min-width: 200px;
240
+ text-align: left;
133
241
  }
134
242
 
135
243
  .table-custom tbody tr:nth-of-type(odd) {
@@ -182,7 +290,6 @@
182
290
  border-radius: 3px;
183
291
  }
184
292
 
185
- /* 增加板块间距 */
186
293
  .section-block {
187
294
  margin-bottom: 70px;
188
295
  }
@@ -232,7 +339,7 @@
232
339
  }
233
340
 
234
341
  .activity-list {
235
- height: 300px; /* 固定高度 */
342
+ height: 550px;
236
343
  overflow-y: auto;
237
344
  border-radius: 8px;
238
345
  border: 1px solid rgba(0,0,0,0.1);
@@ -240,7 +347,7 @@
240
347
  background-color: rgba(255,255,255,0.5);
241
348
  scrollbar-width: thin;
242
349
  scrollbar-color: var(--primary-color) #eee;
243
- margin-bottom: 15px; /* 为翻页按钮留出空间 */
350
+ margin-bottom: 15px;
244
351
  }
245
352
 
246
353
  .activity-list::-webkit-scrollbar {
@@ -260,7 +367,7 @@
260
367
  .activities-container {
261
368
  display: flex;
262
369
  flex-direction: column;
263
- height: 400px; /* 容器总高度 */
370
+ height: 650px;
264
371
  }
265
372
 
266
373
  .pagination-container {
@@ -280,6 +387,28 @@
280
387
  box-shadow: 0 1px 3px rgba(0,0,0,0.05);
281
388
  transition: all 0.2s;
282
389
  line-height: 1.5;
390
+ display: flex;
391
+ align-items: center;
392
+ justify-content: space-between;
393
+ }
394
+
395
+ .activity-item .activity-content {
396
+ display: flex;
397
+ align-items: center;
398
+ flex: 1;
399
+ min-width: 0;
400
+ }
401
+
402
+ .activity-item .activity-name {
403
+ flex: 1;
404
+ word-break: break-all;
405
+ margin-right: 10px;
406
+ }
407
+
408
+ .activity-item .traversal-badge {
409
+ flex-shrink: 0;
410
+ font-size: 0.85rem;
411
+ font-weight: 500;
283
412
  }
284
413
 
285
414
  .activity-item:hover {
@@ -287,19 +416,160 @@
287
416
  transform: translateX(3px);
288
417
  }
289
418
 
290
- /* Responsive adjustments */
419
+ .nav-tabs .nav-link {
420
+ color: #666;
421
+ border: 1px solid transparent;
422
+ border-radius: 6px 6px 0 0;
423
+ font-weight: 500;
424
+ transition: all 0.3s ease;
425
+ }
426
+
427
+ .nav-tabs .nav-link:hover {
428
+ color: var(--primary-color);
429
+ border-color: rgba(52, 152, 219, 0.2);
430
+ background-color: rgba(52, 152, 219, 0.05);
431
+ }
432
+
433
+ .nav-tabs .nav-link.active {
434
+ color: var(--primary-color);
435
+ background-color: white;
436
+ border-color: #dee2e6 #dee2e6 #fff;
437
+ font-weight: 600;
438
+ }
439
+
440
+ .tab-content {
441
+ border: 1px solid #dee2e6;
442
+ border-top: none;
443
+ border-radius: 0 0 8px 8px;
444
+ padding: 20px;
445
+ background-color: #fafafa;
446
+ }
447
+
448
+ .sorting-controls {
449
+ background-color: #f8f9fa;
450
+ border: 1px solid #e9ecef;
451
+ border-radius: 8px;
452
+ padding: 15px;
453
+ margin-bottom: 20px;
454
+ }
455
+
456
+ .sorting-controls .form-select {
457
+ min-width: 140px;
458
+ }
459
+
460
+ .sorting-controls .btn {
461
+ transition: all 0.3s ease;
462
+ }
463
+
464
+ .sorting-controls .btn:hover {
465
+ transform: translateY(-1px);
466
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
467
+ }
468
+
469
+ .sort-icon {
470
+ margin-left: 8px;
471
+ transition: all 0.3s ease;
472
+ font-size: 1.4rem;
473
+ color: #ffffff !important;
474
+ opacity: 0.6;
475
+ text-shadow: 0 0 3px rgba(0,0,0,0.3);
476
+ }
477
+
478
+ .sort-icon:hover {
479
+ opacity: 1;
480
+ transform: scale(1.2);
481
+ text-shadow: 0 0 5px rgba(0,0,0,0.5);
482
+ }
483
+
484
+ .sort-icon.active {
485
+ opacity: 1;
486
+ font-weight: bold;
487
+ text-shadow: 0 0 5px rgba(0,0,0,0.5);
488
+ }
489
+
490
+ .sort-icon.asc.active {
491
+ color: #40e0d0 !important;
492
+ }
493
+
494
+ .sort-icon.desc.active {
495
+ color: #ff6b6b !important;
496
+ }
497
+
498
+ .table-violations thead {
499
+ background-color: #dc3545 !important;
500
+ color: white;
501
+ }
502
+
503
+ .table-violations .sort-icon {
504
+ color: #ffffff !important;
505
+ text-shadow: 0 0 3px rgba(0,0,0,0.4);
506
+ }
507
+
508
+ .table-violations .sort-icon:hover {
509
+ opacity: 1;
510
+ transform: scale(1.2);
511
+ text-shadow: 0 0 5px rgba(0,0,0,0.6);
512
+ }
513
+
291
514
  @media (max-width: 768px) {
515
+ .container {
516
+ max-width: 98% !important;
517
+ width: 98% !important;
518
+ padding-left: 10px !important;
519
+ padding-right: 10px !important;
520
+ }
521
+
292
522
  .stat-value {
293
523
  font-size: 1.5rem;
294
524
  }
295
525
 
296
526
  .screenshot-item {
297
- width: 200px;
527
+ width: 280px;
298
528
  }
299
529
 
300
530
  .screenshot-img {
301
- width: 200px;
302
- height: 320px;
531
+ width: 280px;
532
+ height: 400px;
533
+ }
534
+
535
+ .table-custom {
536
+ font-size: 0.9rem;
537
+ }
538
+
539
+ .table-custom th, .table-custom td {
540
+ padding: 10px 6px;
541
+ }
542
+
543
+ .badge-custom {
544
+ font-size: 0.8rem;
545
+ padding: 4px 8px;
546
+ }
547
+ }
548
+
549
+ @media (max-width: 576px) {
550
+ .container {
551
+ max-width: 100% !important;
552
+ width: 100% !important;
553
+ padding-left: 5px !important;
554
+ padding-right: 5px !important;
555
+ }
556
+
557
+ .screenshot-item {
558
+ width: 260px;
559
+ }
560
+
561
+ .screenshot-img {
562
+ width: 260px;
563
+ height: 380px;
564
+ }
565
+
566
+ .table-custom {
567
+ font-size: 0.8rem;
568
+ }
569
+
570
+ .table-custom th, .table-custom td {
571
+ padding: 8px 4px;
572
+ white-space: normal;
303
573
  }
304
574
  }
305
575
  </style>
@@ -321,66 +591,46 @@
321
591
  <div class="summary-card">
322
592
  <h2 class="section-title">Test Summary</h2>
323
593
  <div class="row g-4">
324
- <div class="col-md-3 col-sm-6">
594
+ <div class="col-md-2 col-sm-6">
325
595
  <div class="text-center">
326
596
  <i class="bi bi-bug text-danger" style="font-size: 2rem;"></i>
327
597
  <span class="stat-value value-danger">{{ bugs_found }}</span>
328
598
  <span class="stat-label">Bugs Found</span>
329
599
  </div>
330
600
  </div>
331
- <div class="col-md-3 col-sm-6">
601
+ <div class="col-md-2 col-sm-6">
332
602
  <div class="text-center">
333
603
  <i class="bi bi-clock-history text-primary" style="font-size: 2rem;"></i>
334
- <span class="stat-value value-highlight">{{ (total_testing_time / 60)|int }} min: {{ (total_testing_time % 60)|int }} sec</span>
604
+ <span class="stat-value value-highlight">{{ total_testing_time }}</span>
335
605
  <span class="stat-label">Total Testing Time</span>
336
606
  </div>
337
607
  </div>
338
- <div class="col-md-3 col-sm-6">
608
+ <div class="col-md-2 col-sm-6">
339
609
  <div class="text-center">
340
610
  <i class="bi bi-activity text-success" style="font-size: 2rem;"></i>
341
611
  <span class="stat-value value-success">{{ executed_events }}</span>
342
612
  <span class="stat-label">Executed Events</span>
343
613
  </div>
344
614
  </div>
345
- <div class="col-md-3 col-sm-6">
615
+ <div class="col-md-2 col-sm-6">
346
616
  <div class="text-center">
347
617
  <i class="bi bi-pie-chart text-warning" style="font-size: 2rem;"></i>
348
618
  <span class="stat-value value-warning">{{ coverage_percent }}%</span>
349
619
  <span class="stat-label">Activity Coverage</span>
350
620
  </div>
351
621
  </div>
352
- </div>
353
- </div>
354
- </div>
355
- </div>
356
-
357
- <!-- Key Statistics -->
358
- <div class="row g-4 mb-4">
359
- <div class="col-12">
360
- <div class="stats-card">
361
- <div class="card-header bg-success text-white">
362
- <i class="bi bi-bar-chart"></i> Coverage Statistics
363
- </div>
364
- <div class="card-body">
365
- <div class="mb-3">
366
- <h5 class="d-flex justify-content-between">
367
- <span>Activity Coverage:</span>
368
- <span class="value-warning">{{ coverage_percent }}%</span>
369
- </h5>
370
- <div class="progress">
371
- <div class="progress-bar bg-warning" role="progressbar"
372
- style="width: {{ coverage_percent }}%;"
373
- aria-valuenow="{{ coverage_percent }}" aria-valuemin="0" aria-valuemax="100"></div>
622
+ <div class="col-md-2 col-sm-6">
623
+ <div class="text-center">
624
+ <i class="bi bi-list-check text-info" style="font-size: 2rem;"></i>
625
+ <span class="stat-value value-highlight">{{ all_properties_count }}</span>
626
+ <span class="stat-label">All Properties</span>
374
627
  </div>
375
628
  </div>
376
- <div class="row mt-4">
377
- <div class="col-6 text-center">
378
- <div class="stat-value value-primary">{{ total_activities_count }}</div>
379
- <div class="stat-label">Total Activities</div>
380
- </div>
381
- <div class="col-6 text-center">
382
- <div class="stat-value value-success">{{ tested_activities_count }}</div>
383
- <div class="stat-label">Tested Activities</div>
629
+ <div class="col-md-2 col-sm-6">
630
+ <div class="text-center">
631
+ <i class="bi bi-check-square text-secondary" style="font-size: 2rem;"></i>
632
+ <span class="stat-value value-success">{{ executed_properties_count }}</span>
633
+ <span class="stat-label">Executed Properties</span>
384
634
  </div>
385
635
  </div>
386
636
  </div>
@@ -388,29 +638,87 @@
388
638
  </div>
389
639
  </div>
390
640
 
641
+ <!-- Coverage Trend Chart -->
642
+ <div class="section-block">
643
+ <h2 class="section-title">Coverage Trend</h2>
644
+ <div class="chart-container">
645
+ <canvas id="coverageChart"></canvas>
646
+ </div>
647
+ </div>
648
+
649
+ <!-- Property Execution Trend Chart -->
650
+ <div class="section-block">
651
+ <h2 class="section-title">Property Execution Trend</h2>
652
+ <div class="chart-container">
653
+ <canvas id="propertyExecutionChart"></canvas>
654
+ </div>
655
+ </div>
656
+
391
657
  <!-- Tested Activities List -->
392
658
  <div class="section-block">
393
659
  <h2 class="section-title">Activities Coverage</h2>
394
660
 
395
- <div class="row g-4">
396
- <!-- Tested Activities Panel -->
397
- <div class="col-md-6">
398
- <div class="card">
399
- <div class="card-header bg-warning text-white">
400
- <div class="d-flex justify-content-between align-items-center">
401
- <span><i class="bi bi-check-circle"></i> Tested Activities ({{ tested_activities|length }})</span>
402
- <span class="badge bg-light text-dark">{{ tested_activities|length }} / {{ total_activities_count }}</span>
661
+ <div class="card">
662
+ <div class="card-header bg-primary text-white">
663
+ <div class="d-flex justify-content-between align-items-center">
664
+ <span><i class="bi bi-app"></i> Activities Coverage Overview</span>
665
+ <span class="badge bg-light text-dark" style="font-size: 1.1em; font-weight: 600;">Coverage: {{ coverage_percent }}%</span>
666
+ </div>
667
+ </div>
668
+ <div class="card-body">
669
+ <div class="alert alert-info mb-3" style="border-left: 4px solid #17a2b8; background-color: #f8f9fa;">
670
+ <small class="text-muted">
671
+ <i class="bi bi-info-circle me-1"></i>
672
+ <strong>Traversal Count Explanation:</strong>
673
+ The number after the <i class="bi bi-eye"></i> icon indicates how many times each Activity was visited during testing.
674
+ </small>
675
+ </div>
676
+
677
+ <!-- Navigation Tabs -->
678
+ <ul class="nav nav-tabs mb-3" id="activitiesTabs" role="tablist">
679
+ <li class="nav-item" role="presentation">
680
+ <button class="nav-link active" id="tested-tab" data-bs-toggle="tab"
681
+ data-bs-target="#tested-activities" type="button" role="tab"
682
+ aria-controls="tested-activities" aria-selected="true">
683
+ <i class="bi bi-check-circle"></i> Tested Activities ({{ tested_activities|length }})
684
+ </button>
685
+ </li>
686
+ <li class="nav-item" role="presentation">
687
+ <button class="nav-link" id="all-tab" data-bs-toggle="tab"
688
+ data-bs-target="#all-activities" type="button" role="tab"
689
+ aria-controls="all-activities" aria-selected="false">
690
+ <i class="bi bi-app"></i> All Activities ({{ total_activities|length }})
691
+ </button>
692
+ </li>
693
+ </ul>
694
+
695
+ <!-- Tab Content -->
696
+ <div class="tab-content" id="activitiesTabContent">
697
+ <!-- Tested Activities Tab -->
698
+ <div class="tab-pane fade show active" id="tested-activities" role="tabpanel"
699
+ aria-labelledby="tested-tab">
700
+ <div class="d-flex justify-content-between align-items-center mb-3">
701
+ <h5 class="mb-0 text-success">
702
+ <i class="bi bi-check-circle-fill"></i> Tested Activities
703
+ </h5>
704
+ <span class="badge bg-success">{{ tested_activities|length }} / {{ total_activities_count }}</span>
403
705
  </div>
404
- </div>
405
- <div class="card-body">
706
+
406
707
  <div class="activities-container">
407
708
  <div class="activity-list">
408
709
  {% if tested_activities|length > 0 %}
409
710
  <div id="tested-activities-container">
410
711
  {% for activity in tested_activities %}
411
712
  <div class="activity-item tested-activity" data-page="1">
412
- <i class="bi bi-check-circle-fill text-success me-2"></i>
413
- {{ activity }}
713
+ <div class="activity-content">
714
+ <i class="bi bi-check-circle-fill text-success me-2"></i>
715
+ <span class="activity-name">{{ activity }}</span>
716
+ </div>
717
+ {% if activity in activity_count_history %}
718
+ <span class="badge bg-info text-white traversal-badge">
719
+ <i class="bi bi-eye"></i> {{ activity_count_history[activity] }} times
720
+ </span>
721
+ {% endif %}
414
722
  </div>
415
723
  {% endfor %}
416
724
  </div>
@@ -421,40 +729,55 @@
421
729
  {% endif %}
422
730
  </div>
423
731
  <!-- Pagination for Tested Activities -->
424
- <div class="pagination-container d-flex justify-content-center">
732
+ <div class="pagination-container d-flex justify-content-between align-items-center">
733
+ <div class="d-flex align-items-center">
734
+ <label for="tested-page-size" class="form-label me-2 mb-0">Show:</label>
735
+ <select class="form-select form-select-sm" id="tested-page-size" style="width: auto;">
736
+ <option value="5">5</option>
737
+ <option value="10" selected>10</option>
738
+ <option value="20">20</option>
739
+ <option value="50">50</option>
740
+ <option value="100">100</option>
741
+ </select>
742
+ </div>
425
743
  <nav aria-label="Tested Activities Pagination">
426
- <ul class="pagination pagination-sm" id="tested-pagination">
744
+ <ul class="pagination pagination-sm mb-0" id="tested-pagination">
427
745
  <!-- Pagination will be generated by JavaScript -->
428
746
  </ul>
429
747
  </nav>
430
748
  </div>
431
749
  </div>
432
750
  </div>
433
- </div>
434
- </div>
435
-
436
- <!-- All Activities Panel -->
437
- <div class="col-md-6">
438
- <div class="card">
439
- <div class="card-header bg-primary text-white">
440
- <div class="d-flex justify-content-between align-items-center">
441
- <span><i class="bi bi-app"></i> All Activities ({{ total_activities|length }})</span>
442
- <span class="badge bg-light text-dark">Coverage: {{ coverage_percent }}%</span>
751
+
752
+ <!-- All Activities Tab -->
753
+ <div class="tab-pane fade" id="all-activities" role="tabpanel"
754
+ aria-labelledby="all-tab">
755
+ <div class="d-flex justify-content-between align-items-center mb-3">
756
+ <h5 class="mb-0 text-primary">
757
+ <i class="bi bi-app"></i> All Activities Overview
758
+ </h5>
759
+ <span class="badge bg-primary">Total: {{ total_activities|length }}</span>
443
760
  </div>
444
- </div>
445
- <div class="card-body">
761
+
446
762
  <div class="activities-container">
447
763
  <div class="activity-list">
448
764
  {% if total_activities|length > 0 %}
449
765
  <div id="all-activities-container">
450
766
  {% for activity in total_activities %}
451
767
  <div class="activity-item all-activity" data-page="1">
452
- {% if activity in tested_activities %}
453
- <i class="bi bi-check-circle-fill text-success me-2"></i>
454
- {% else %}
455
- <i class="bi bi-dash-circle text-secondary me-2"></i>
768
+ <div class="activity-content">
769
+ {% if activity in tested_activities %}
770
+ <i class="bi bi-check-circle-fill text-success me-2"></i>
771
+ {% else %}
772
+ <i class="bi bi-dash-circle text-secondary me-2"></i>
773
+ {% endif %}
774
+ <span class="activity-name">{{ activity }}</span>
775
+ </div>
776
+ {% if activity in activity_count_history %}
777
+ <span class="badge bg-info text-white traversal-badge">
778
+ <i class="bi bi-eye"></i> {{ activity_count_history[activity] }} times
779
+ </span>
456
780
  {% endif %}
457
- {{ activity }}
458
781
  </div>
459
782
  {% endfor %}
460
783
  </div>
@@ -465,9 +788,19 @@
465
788
  {% endif %}
466
789
  </div>
467
790
  <!-- Pagination for All Activities -->
468
- <div class="pagination-container d-flex justify-content-center">
791
+ <div class="pagination-container d-flex justify-content-between align-items-center">
792
+ <div class="d-flex align-items-center">
793
+ <label for="all-page-size" class="form-label me-2 mb-0">Show:</label>
794
+ <select class="form-select form-select-sm" id="all-page-size" style="width: auto;">
795
+ <option value="5">5</option>
796
+ <option value="10" selected>10</option>
797
+ <option value="20">20</option>
798
+ <option value="50">50</option>
799
+ <option value="100">100</option>
800
+ </select>
801
+ </div>
469
802
  <nav aria-label="All Activities Pagination">
470
- <ul class="pagination pagination-sm" id="all-pagination">
803
+ <ul class="pagination pagination-sm mb-0" id="all-pagination">
471
804
  <!-- Pagination will be generated by JavaScript -->
472
805
  </ul>
473
806
  </nav>
@@ -500,20 +833,12 @@
500
833
  </div>
501
834
  {% endif %}
502
835
 
503
- <!-- Coverage Trend Chart -->
504
- <div class="section-block">
505
- <h2 class="section-title">Coverage Trend</h2>
506
- <div class="chart-container">
507
- <canvas id="coverageChart"></canvas>
508
- </div>
509
- </div>
510
-
511
836
  <!-- Property Violations List -->
512
837
  {% if take_screenshots %}
513
838
  <div class="section-block">
514
839
  <h2 class="section-title">Property Violations</h2>
515
840
  <div class="table-responsive">
516
- <table class="table table-custom">
841
+ <table class="table table-custom table-violations">
517
842
  <thead>
518
843
  <tr>
519
844
  <th>Index</th>
@@ -537,9 +862,19 @@
537
862
  </table>
538
863
 
539
864
  <!-- Pagination for Property Violations -->
540
- <div class="d-flex justify-content-center mt-3">
865
+ <div class="d-flex justify-content-between align-items-center mt-3">
866
+ <div class="d-flex align-items-center">
867
+ <label for="violations-page-size" class="form-label me-2 mb-0">Show:</label>
868
+ <select class="form-select form-select-sm" id="violations-page-size" style="width: auto;">
869
+ <option value="5">5</option>
870
+ <option value="10" selected>10</option>
871
+ <option value="20">20</option>
872
+ <option value="50">50</option>
873
+ <option value="100">100</option>
874
+ </select>
875
+ </div>
541
876
  <nav aria-label="Property Violations Pagination">
542
- <ul class="pagination pagination-sm" id="violations-pagination">
877
+ <ul class="pagination pagination-sm mb-0" id="violations-pagination">
543
878
  <!-- Pagination will be generated by JavaScript -->
544
879
  </ul>
545
880
  </nav>
@@ -551,6 +886,7 @@
551
886
  <!-- Property Checking Statistics -->
552
887
  <div class="section-block">
553
888
  <h2 class="section-title">Property Checking Statistics</h2>
889
+
554
890
  <div class="table-responsive">
555
891
  <table class="table table-custom">
556
892
  <thead>
@@ -559,28 +895,171 @@
559
895
  <th>Property Name</th>
560
896
  <th>Precondition Satisfied</th>
561
897
  <th>Executed</th>
562
- <th>Fails</th>
563
- <th>Errors</th>
898
+ <th>Fails <i class="bi bi-arrow-down-up text-muted sort-icon" id="fails-sort" data-column="fails" data-order="none" style="cursor: pointer;"></i></th>
899
+ <th>Errors <i class="bi bi-arrow-down-up text-muted sort-icon" id="errors-sort" data-column="errors" data-order="none" style="cursor: pointer;"></i></th>
900
+ <th>Error Details</th>
564
901
  </tr>
565
902
  </thead>
566
903
  <tbody id="property-stats-container">
567
904
  {% for property_name, test_result in property_stats.items() %}
568
- <tr class="property-stat-row" data-page="1">
905
+ <tr class="property-stat-row" data-page="1"
906
+ data-index="{{ loop.index }}"
907
+ data-property-name="{{ property_name }}"
908
+ data-precond-satisfied="{{ test_result.precond_satisfied|default(0) }}"
909
+ data-executed="{{ test_result.executed|default(0) }}"
910
+ data-fails="{{ test_result.fail|default(0) }}"
911
+ data-errors="{{ test_result.error|default(0) }}">
569
912
  <td>{{ loop.index }}</td>
570
913
  <td><span class="badge bg-light text-dark badge-custom">{{ property_name }}</span></td>
571
914
  <td>{{ test_result.precond_satisfied|default(0) }}</td>
572
915
  <td>{{ test_result.executed|default(0) }}</td>
573
916
  <td><span class="badge bg-danger text-white">{{ test_result.fail|default(0) }}</span></td>
574
917
  <td><span class="badge bg-warning text-dark">{{ test_result.error|default(0) }}</span></td>
918
+ <td>
919
+ {% if (test_result.fail|default(0) > 0 or test_result.error|default(0) > 0) and property_name in property_error_details %}
920
+ {% set error_list = property_error_details[property_name] %}
921
+ {% set property_index = loop.index %}
922
+ {% if error_list|length == 1 %}
923
+ <!-- Single error - simple display -->
924
+ <button class="btn btn-sm btn-outline-danger" type="button" data-bs-toggle="collapse"
925
+ data-bs-target="#single-error-detail-{{ property_index }}" aria-expanded="false"
926
+ aria-controls="single-error-detail-{{ property_index }}">
927
+ <i class="bi bi-exclamation-triangle"></i> View Error
928
+ </button>
929
+ <div class="collapse mt-2" id="single-error-detail-{{ property_index }}">
930
+ <div class="card card-body bg-light">
931
+ <div class="mb-2">
932
+ <span class="badge bg-{{ 'danger' if error_list[0].state == 'fail' else 'warning' }}">
933
+ {{ error_list[0].state|upper }}
934
+ </span>
935
+ {% if error_list[0].occurrence_count > 1 %}
936
+ <span class="badge bg-info ms-2">
937
+ Occurred {{ error_list[0].occurrence_count }} times
938
+ </span>
939
+ {% endif %}
940
+ {% if error_list[0].startStepsCountList is defined and error_list[0].startStepsCountList|length > 0 %}
941
+ <span class="badge bg-secondary ms-2">
942
+ <i class="bi bi-step-forward"></i> Monkey Steps: {{ error_list[0].startStepsCountList|join(', ') }}
943
+ </span>
944
+ {% endif %}
945
+ </div>
946
+ {% if error_list[0].short_description %}
947
+ <div class="mb-2">
948
+ <strong>Error:</strong> <code>{{ error_list[0].short_description }}</code>
949
+ </div>
950
+ {% endif %}
951
+ <details>
952
+ <summary class="btn btn-sm btn-outline-secondary mb-2">Show Full Traceback</summary>
953
+ <pre class="text-danger mb-0" style="font-size: 0.85rem; white-space: pre-wrap;">{{ error_list[0].traceback }}</pre>
954
+ </details>
955
+ </div>
956
+ </div>
957
+ {% else %}
958
+ <!-- Multiple errors - tabbed display -->
959
+ <button class="btn btn-sm btn-outline-danger" type="button" data-bs-toggle="collapse"
960
+ data-bs-target="#multi-error-detail-{{ property_index }}" aria-expanded="false"
961
+ aria-controls="multi-error-detail-{{ property_index }}">
962
+ <i class="bi bi-exclamation-triangle"></i> View {{ error_list|length }} Errors
963
+ </button>
964
+ <div class="collapse mt-2" id="multi-error-detail-{{ property_index }}">
965
+ <div class="card card-body bg-light">
966
+ <!-- Error summary -->
967
+ <div class="mb-3">
968
+ <h6 class="text-danger">Multiple Errors Detected</h6>
969
+ <div class="d-flex flex-wrap gap-1">
970
+ {% for error in error_list %}
971
+ <span class="badge bg-{{ 'danger' if error.state == 'fail' else 'warning' }}">
972
+ {{ error.state|upper }} #{{ loop.index }}
973
+ {% if error.occurrence_count > 1 %} ({{ error.occurrence_count }}x){% endif %}
974
+ {% if error.startStepsCountList is defined and error.startStepsCountList|length > 0 %}
975
+ @{% if error.startStepsCountList|length == 1 %}{{ error.startStepsCountList[0] }}{% else %}{{ error.startStepsCountList[0] }}-{{ error.startStepsCountList[-1] }}{% endif %}
976
+ {% endif %}
977
+ </span>
978
+ {% endfor %}
979
+ </div>
980
+ </div>
981
+
982
+ <!-- Error tabs -->
983
+ <ul class="nav nav-pills nav-fill mb-3" id="multi-error-tabs-{{ property_index }}" role="tablist">
984
+ {% for error in error_list %}
985
+ <li class="nav-item" role="presentation">
986
+ <button class="nav-link {{ 'active' if loop.first else '' }} btn-sm"
987
+ id="multi-error-tab-{{ property_index }}-{{ loop.index }}"
988
+ data-bs-toggle="pill"
989
+ data-bs-target="#multi-error-content-{{ property_index }}-{{ loop.index }}"
990
+ type="button" role="tab"
991
+ aria-controls="multi-error-content-{{ property_index }}-{{ loop.index }}"
992
+ aria-selected="{{ 'true' if loop.first else 'false' }}">
993
+ <span class="badge bg-{{ 'danger' if error.state == 'fail' else 'warning' }} me-1">
994
+ {{ error.state|upper }}
995
+ </span>
996
+ #{{ loop.index }}
997
+ </button>
998
+ </li>
999
+ {% endfor %}
1000
+ </ul>
1001
+
1002
+ <!-- Error content -->
1003
+ <div class="tab-content" id="multi-error-content-{{ property_index }}">
1004
+ {% for error in error_list %}
1005
+ <div class="tab-pane fade {{ 'show active' if loop.first else '' }}"
1006
+ id="multi-error-content-{{ property_index }}-{{ loop.index }}"
1007
+ role="tabpanel"
1008
+ aria-labelledby="multi-error-tab-{{ property_index }}-{{ loop.index }}">
1009
+ <div class="mb-2">
1010
+ <span class="badge bg-{{ 'danger' if error.state == 'fail' else 'warning' }}">
1011
+ {{ error.state|upper }} #{{ loop.index }}
1012
+ </span>
1013
+ <small class="text-muted ms-2">Error {{ loop.index }} of {{ loop.length }}</small>
1014
+ {% if error.occurrence_count > 1 %}
1015
+ <span class="badge bg-info ms-2">
1016
+ {{ error.occurrence_count }} occurrences
1017
+ </span>
1018
+ {% endif %}
1019
+ {% if error.startStepsCountList is defined and error.startStepsCountList|length > 0 %}
1020
+ <span class="badge bg-secondary ms-2">
1021
+ <i class="bi bi-step-forward"></i> Monkey Steps: {{ error.startStepsCountList|join(', ') }}
1022
+ </span>
1023
+ {% endif %}
1024
+ </div>
1025
+ {% if error.short_description %}
1026
+ <div class="mb-2">
1027
+ <strong>Error:</strong> <code>{{ error.short_description }}</code>
1028
+ </div>
1029
+ {% endif %}
1030
+ <details>
1031
+ <summary class="btn btn-sm btn-outline-secondary mb-2">Show Full Traceback</summary>
1032
+ <pre class="text-danger mb-0" style="font-size: 0.85rem; white-space: pre-wrap;">{{ error.traceback }}</pre>
1033
+ </details>
1034
+ </div>
1035
+ {% endfor %}
1036
+ </div>
1037
+ </div>
1038
+ </div>
1039
+ {% endif %}
1040
+ {% else %}
1041
+ <span class="text-muted">-</span>
1042
+ {% endif %}
1043
+ </td>
575
1044
  </tr>
576
1045
  {% endfor %}
577
1046
  </tbody>
578
1047
  </table>
579
1048
 
580
1049
  <!-- Pagination for Property Checking Statistics -->
581
- <div class="d-flex justify-content-center mt-3">
1050
+ <div class="d-flex justify-content-between align-items-center mt-3">
1051
+ <div class="d-flex align-items-center">
1052
+ <label for="stats-page-size" class="form-label me-2 mb-0">Show:</label>
1053
+ <select class="form-select form-select-sm" id="stats-page-size" style="width: auto;">
1054
+ <option value="5">5</option>
1055
+ <option value="10" selected>10</option>
1056
+ <option value="20">20</option>
1057
+ <option value="50">50</option>
1058
+ <option value="100">100</option>
1059
+ </select>
1060
+ </div>
582
1061
  <nav aria-label="Property Stats Pagination">
583
- <ul class="pagination pagination-sm" id="stats-pagination">
1062
+ <ul class="pagination pagination-sm mb-0" id="stats-pagination">
584
1063
  <!-- Pagination will be generated by JavaScript -->
585
1064
  </ul>
586
1065
  </nav>
@@ -605,12 +1084,12 @@
605
1084
  console.log("Coverage data points:", coverageData.length);
606
1085
 
607
1086
  coverageData.sort(function(a, b) {
608
- return a.steps - b.steps;
1087
+ return a.stepsCount - b.stepsCount;
609
1088
  });
610
1089
 
611
- var steps = coverageData.map(function(item) { return item.steps; });
1090
+ var steps = coverageData.map(function(item) { return item.stepsCount; });
612
1091
  var coverages = coverageData.map(function(item) { return item.coverage; });
613
- var testedActivities = coverageData.map(function(item) { return item.tested_activities_count; });
1092
+ var testedActivities = coverageData.map(function(item) { return item.testedActivitiesCount; });
614
1093
 
615
1094
  if (steps.length > 0 && steps[0] > 0) {
616
1095
  steps.unshift(0);
@@ -711,7 +1190,7 @@
711
1190
  stepSize: steps.length > 0 ? Math.max(1, Math.ceil(Math.max(...steps) / 10)) : 1,
712
1191
  callback: function(value) {
713
1192
  if (Number.isInteger(value) && value <= (steps.length > 0 ? Math.max(...steps) : 10)) {
714
- return value;
1193
+ return value;
715
1194
  }
716
1195
  return null;
717
1196
  }
@@ -760,82 +1239,219 @@
760
1239
  }
761
1240
  });
762
1241
 
1242
+ // Draw property execution trend chart
1243
+ var propertyExecutionData = {{ property_execution_data|safe }};
1244
+ console.log("Property execution data points:", propertyExecutionData.length);
1245
+
1246
+ // Ensure we have valid data
1247
+ if (propertyExecutionData.length === 0) {
1248
+ propertyExecutionData = [{"stepsCount": 0, "executedPropertiesCount": 0}];
1249
+ }
1250
+
1251
+ propertyExecutionData.sort(function(a, b) {
1252
+ return a.stepsCount - b.stepsCount;
1253
+ });
1254
+
1255
+ var propSteps = propertyExecutionData.map(function(item) { return item.stepsCount; });
1256
+ var executedProps = propertyExecutionData.map(function(item) { return item.executedPropertiesCount; });
1257
+
1258
+ // Add zero starting point if needed
1259
+ if (propSteps.length > 0 && propSteps[0] > 0) {
1260
+ propSteps.unshift(0);
1261
+ executedProps.unshift(0);
1262
+ }
1263
+
1264
+ console.log("Property execution steps:", propSteps);
1265
+ console.log("Executed properties values:", executedProps);
1266
+
1267
+ var propCtx = document.getElementById('propertyExecutionChart').getContext('2d');
1268
+ var propChart = new Chart(propCtx, {
1269
+ type: 'line',
1270
+ data: {
1271
+ labels: propSteps,
1272
+ datasets: [
1273
+ {
1274
+ label: 'Executed Properties',
1275
+ data: executedProps.map((value, index) => ({x: propSteps[index], y: value})),
1276
+ borderColor: '#e74c3c',
1277
+ backgroundColor: 'rgba(231, 76, 60, 0.1)',
1278
+ borderWidth: 3,
1279
+ fill: true,
1280
+ tension: 0.4,
1281
+ pointRadius: 4,
1282
+ pointHoverRadius: 6
1283
+ }
1284
+ ]
1285
+ },
1286
+ options: {
1287
+ responsive: true,
1288
+ maintainAspectRatio: false,
1289
+ aspectRatio: 2,
1290
+ plugins: {
1291
+ legend: {
1292
+ position: 'top',
1293
+ labels: {
1294
+ boxWidth: 15,
1295
+ usePointStyle: true,
1296
+ pointStyle: 'circle'
1297
+ }
1298
+ },
1299
+ tooltip: {
1300
+ mode: 'index',
1301
+ intersect: false,
1302
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
1303
+ titleColor: '#fff',
1304
+ bodyColor: '#fff',
1305
+ borderColor: 'rgba(255, 255, 255, 0.2)',
1306
+ borderWidth: 1,
1307
+ padding: 10,
1308
+ displayColors: true,
1309
+ callbacks: {
1310
+ label: function(context) {
1311
+ return 'Executed Properties: ' + context.parsed.y;
1312
+ }
1313
+ }
1314
+ }
1315
+ },
1316
+ scales: {
1317
+ x: {
1318
+ type: 'linear',
1319
+ beginAtZero: true,
1320
+ max: propSteps.length > 0 ? Math.max(...propSteps) : 10,
1321
+ grid: {
1322
+ display: false
1323
+ },
1324
+ title: {
1325
+ display: true,
1326
+ text: 'Steps Count',
1327
+ font: {
1328
+ size: 14
1329
+ }
1330
+ },
1331
+ ticks: {
1332
+ stepSize: propSteps.length > 0 ? Math.max(1, Math.ceil(Math.max(...propSteps) / 10)) : 1,
1333
+ callback: function(value) {
1334
+ if (Number.isInteger(value) && value <= (propSteps.length > 0 ? Math.max(...propSteps) : 10)) {
1335
+ return value;
1336
+ }
1337
+ return null;
1338
+ }
1339
+ }
1340
+ },
1341
+ y: {
1342
+ beginAtZero: true,
1343
+ title: {
1344
+ display: true,
1345
+ text: 'Executed Properties',
1346
+ font: {
1347
+ size: 14
1348
+ }
1349
+ },
1350
+ grid: {
1351
+ borderDash: [5, 5]
1352
+ },
1353
+ ticks: {
1354
+ stepSize: 1,
1355
+ callback: function(value) {
1356
+ if (Number.isInteger(value)) {
1357
+ return value;
1358
+ }
1359
+ return null;
1360
+ }
1361
+ }
1362
+ }
1363
+ },
1364
+ interaction: {
1365
+ mode: 'index',
1366
+ intersect: false
1367
+ },
1368
+ hover: {
1369
+ mode: 'index',
1370
+ intersect: false
1371
+ },
1372
+ animation: {
1373
+ duration: 1000,
1374
+ easing: 'easeOutQuart'
1375
+ }
1376
+ }
1377
+ });
1378
+
763
1379
  // Initialize pagination for Activities lists
764
- initPagination('tested-activities-container', 'tested-activity', 'tested-pagination', {{ items_per_page }});
765
- initPagination('all-activities-container', 'all-activity', 'all-pagination', {{ items_per_page }});
1380
+ initPagination('tested-activities-container', 'tested-activity', 'tested-pagination', 'tested-page-size');
1381
+ initPagination('all-activities-container', 'all-activity', 'all-pagination', 'all-page-size');
766
1382
 
767
1383
  // Initialize pagination for Property tables
768
- initPagination('property-violations-container', 'property-violation-row', 'violations-pagination', {{ items_per_page }});
769
- initPagination('property-stats-container', 'property-stat-row', 'stats-pagination', {{ items_per_page }});
1384
+ initPagination('property-violations-container', 'property-violation-row', 'violations-pagination', 'violations-page-size');
1385
+ initPagination('property-stats-container', 'property-stat-row', 'stats-pagination', 'stats-page-size');
770
1386
 
771
- // Pagination function
772
- function initPagination(containerId, itemClass, paginationId, itemsPerPage) {
773
- const container = document.getElementById(containerId);
774
- if (!container) return;
775
-
776
- const items = container.getElementsByClassName(itemClass);
777
- const totalItems = items.length;
778
- const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage));
779
-
780
- // Create pagination
781
- const paginationElement = document.getElementById(paginationId);
782
- if (!paginationElement) return;
783
-
784
- // Clear pagination
785
- paginationElement.innerHTML = '';
786
-
787
- // Previous button
788
- const prevLi = document.createElement('li');
789
- prevLi.className = 'page-item disabled';
790
- prevLi.innerHTML = '<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>';
791
- paginationElement.appendChild(prevLi);
792
-
793
- // Add page numbers
794
- for (let i = 1; i <= totalPages; i++) {
795
- const pageLi = document.createElement('li');
796
- pageLi.className = i === 1 ? 'page-item active' : 'page-item';
797
- pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
798
- pageLi.addEventListener('click', function(e) {
799
- e.preventDefault();
800
- goToPage(i, containerId, itemClass, itemsPerPage, paginationId);
801
- });
802
- paginationElement.appendChild(pageLi);
803
- }
804
-
805
- // Next button
806
- const nextLi = document.createElement('li');
807
- nextLi.className = totalPages <= 1 ? 'page-item disabled' : 'page-item';
808
- nextLi.innerHTML = '<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>';
809
- paginationElement.appendChild(nextLi);
810
-
811
- // Initialize page 1
812
- goToPage(1, containerId, itemClass, itemsPerPage, paginationId);
1387
+ // Initialize sorting for Property Checking Statistics
1388
+ initSorting();
1389
+
1390
+ // Simplified sorting function for Fails and Errors columns
1391
+ function initSorting() {
1392
+ const sortIcons = document.querySelectorAll('.sort-icon');
813
1393
 
814
- // Add event listeners to prev/next buttons
815
- if (paginationElement.firstChild) {
816
- paginationElement.firstChild.addEventListener('click', function(e) {
817
- e.preventDefault();
818
- const activePage = paginationElement.querySelector('.active');
819
- if (activePage && activePage.previousElementSibling && activePage.previousElementSibling.previousElementSibling) {
820
- const pageNum = parseInt(activePage.textContent) - 1;
821
- if (pageNum >= 1) {
822
- goToPage(pageNum, containerId, itemClass, itemsPerPage, paginationId);
1394
+ sortIcons.forEach(function(icon) {
1395
+ icon.addEventListener('click', function() {
1396
+ const column = this.dataset.column;
1397
+ const currentOrder = this.dataset.order;
1398
+
1399
+ // Reset all other sort icons
1400
+ sortIcons.forEach(function(otherIcon) {
1401
+ if (otherIcon !== icon) {
1402
+ otherIcon.dataset.order = 'none';
1403
+ otherIcon.className = 'bi bi-arrow-down-up sort-icon';
1404
+ otherIcon.style.color = '#6c757d';
823
1405
  }
1406
+ });
1407
+
1408
+ // Toggle current sort order
1409
+ let newOrder;
1410
+ if (currentOrder === 'none' || currentOrder === 'desc') {
1411
+ newOrder = 'asc';
1412
+ this.className = 'bi bi-arrow-up sort-icon active asc';
1413
+ this.style.color = '#28a745';
1414
+ } else {
1415
+ newOrder = 'desc';
1416
+ this.className = 'bi bi-arrow-down sort-icon active desc';
1417
+ this.style.color = '#dc3545';
824
1418
  }
1419
+
1420
+ this.dataset.order = newOrder;
1421
+ sortTable(column, newOrder);
825
1422
  });
826
- }
1423
+ });
827
1424
 
828
- if (paginationElement.lastChild) {
829
- paginationElement.lastChild.addEventListener('click', function(e) {
830
- e.preventDefault();
831
- const activePage = paginationElement.querySelector('.active');
832
- if (activePage && activePage.nextElementSibling && activePage.nextElementSibling.nextElementSibling) {
833
- const pageNum = parseInt(activePage.textContent) + 1;
834
- if (pageNum <= totalPages) {
835
- goToPage(pageNum, containerId, itemClass, itemsPerPage, paginationId);
836
- }
1425
+ function sortTable(column, order) {
1426
+ const container = document.getElementById('property-stats-container');
1427
+ const rows = Array.from(container.getElementsByClassName('property-stat-row'));
1428
+
1429
+ rows.sort(function(a, b) {
1430
+ let valueA, valueB;
1431
+
1432
+ if (column === 'fails') {
1433
+ valueA = parseInt(a.dataset.fails);
1434
+ valueB = parseInt(b.dataset.fails);
1435
+ } else if (column === 'errors') {
1436
+ valueA = parseInt(a.dataset.errors);
1437
+ valueB = parseInt(b.dataset.errors);
837
1438
  }
1439
+
1440
+ if (order === 'asc') {
1441
+ return valueA - valueB;
1442
+ } else {
1443
+ return valueB - valueA;
1444
+ }
1445
+ });
1446
+
1447
+ // Clear container and append sorted rows
1448
+ container.innerHTML = '';
1449
+ rows.forEach(function(row) {
1450
+ container.appendChild(row);
838
1451
  });
1452
+
1453
+ // Re-initialize pagination after sorting
1454
+ initPagination('property-stats-container', 'property-stat-row', 'stats-pagination', 'stats-page-size');
839
1455
  }
840
1456
  }
841
1457
 
@@ -884,6 +1500,94 @@
884
1500
  }
885
1501
  }
886
1502
 
1503
+ // Pagination function
1504
+ function initPagination(containerId, itemClass, paginationId, pageSizeSelectId) {
1505
+ const container = document.getElementById(containerId);
1506
+ const pageSizeSelect = document.getElementById(pageSizeSelectId);
1507
+ if (!container) return;
1508
+
1509
+ // Get initial page size
1510
+ let itemsPerPage = pageSizeSelect ? parseInt(pageSizeSelect.value) : 10;
1511
+
1512
+ // Add event listener for page size changes
1513
+ if (pageSizeSelect) {
1514
+ pageSizeSelect.addEventListener('change', function() {
1515
+ itemsPerPage = parseInt(this.value);
1516
+ renderPagination();
1517
+ goToPage(1, containerId, itemClass, itemsPerPage, paginationId);
1518
+ });
1519
+ }
1520
+
1521
+ function renderPagination() {
1522
+ const items = container.getElementsByClassName(itemClass);
1523
+ const totalItems = items.length;
1524
+ const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage));
1525
+
1526
+ // Create pagination
1527
+ const paginationElement = document.getElementById(paginationId);
1528
+ if (!paginationElement) return;
1529
+
1530
+ // Clear pagination
1531
+ paginationElement.innerHTML = '';
1532
+
1533
+ // Previous button
1534
+ const prevLi = document.createElement('li');
1535
+ prevLi.className = 'page-item disabled';
1536
+ prevLi.innerHTML = '<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>';
1537
+ paginationElement.appendChild(prevLi);
1538
+
1539
+ // Add page numbers
1540
+ for (let i = 1; i <= totalPages; i++) {
1541
+ const pageLi = document.createElement('li');
1542
+ pageLi.className = i === 1 ? 'page-item active' : 'page-item';
1543
+ pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
1544
+ pageLi.addEventListener('click', function(e) {
1545
+ e.preventDefault();
1546
+ goToPage(i, containerId, itemClass, itemsPerPage, paginationId);
1547
+ });
1548
+ paginationElement.appendChild(pageLi);
1549
+ }
1550
+
1551
+ // Next button
1552
+ const nextLi = document.createElement('li');
1553
+ nextLi.className = totalPages <= 1 ? 'page-item disabled' : 'page-item';
1554
+ nextLi.innerHTML = '<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>';
1555
+ paginationElement.appendChild(nextLi);
1556
+
1557
+ // Add event listeners to prev/next buttons
1558
+ if (paginationElement.firstChild) {
1559
+ paginationElement.firstChild.addEventListener('click', function(e) {
1560
+ e.preventDefault();
1561
+ const activePage = paginationElement.querySelector('.active');
1562
+ if (activePage && activePage.previousElementSibling && activePage.previousElementSibling.previousElementSibling) {
1563
+ const pageNum = parseInt(activePage.textContent) - 1;
1564
+ if (pageNum >= 1) {
1565
+ goToPage(pageNum, containerId, itemClass, itemsPerPage, paginationId);
1566
+ }
1567
+ }
1568
+ });
1569
+ }
1570
+
1571
+ if (paginationElement.lastChild) {
1572
+ paginationElement.lastChild.addEventListener('click', function(e) {
1573
+ e.preventDefault();
1574
+ const activePage = paginationElement.querySelector('.active');
1575
+ if (activePage && activePage.nextElementSibling && activePage.nextElementSibling.nextElementSibling) {
1576
+ const pageNum = parseInt(activePage.textContent) + 1;
1577
+ const totalPages = Math.ceil(container.getElementsByClassName(itemClass).length / itemsPerPage);
1578
+ if (pageNum <= totalPages) {
1579
+ goToPage(pageNum, containerId, itemClass, itemsPerPage, paginationId);
1580
+ }
1581
+ }
1582
+ });
1583
+ }
1584
+ }
1585
+
1586
+ // Initial render
1587
+ renderPagination();
1588
+ goToPage(1, containerId, itemClass, itemsPerPage, paginationId);
1589
+ }
1590
+
887
1591
  // Scroll functionality
888
1592
  const screenshots = document.getElementById('screenshots');
889
1593
  if (screenshots) {
@@ -894,6 +1598,56 @@
894
1598
  }
895
1599
  });
896
1600
  }
1601
+
1602
+ // Beautify screenshot captions
1603
+ function beautifyScreenshotCaptions() {
1604
+ const captions = document.querySelectorAll('.screenshot-caption');
1605
+ captions.forEach(function(caption) {
1606
+ const originalText = caption.textContent.trim();
1607
+
1608
+ // Handle Monkey Step format: "1. Monkey Step 6: SCROLL_TOP_DOWN"
1609
+ const monkeyStepMatch = originalText.match(/^(\d+)\.\s*Monkey Step (\d+):\s*(.+)$/);
1610
+ if (monkeyStepMatch) {
1611
+ const stepIndex = monkeyStepMatch[1];
1612
+ const monkeyStep = monkeyStepMatch[2];
1613
+ const action = monkeyStepMatch[3];
1614
+
1615
+ caption.innerHTML = `
1616
+ <div class="step-number">${stepIndex}. Monkey Step ${monkeyStep}</div>
1617
+ <div class="step-action">${action}</div>
1618
+ `;
1619
+ return;
1620
+ }
1621
+
1622
+ // Handle Script format: "1. click" or similar
1623
+ const scriptMatch = originalText.match(/^(\d+)\.\s*(.+)$/);
1624
+ if (scriptMatch) {
1625
+ const stepIndex = scriptMatch[1];
1626
+ const action = scriptMatch[2];
1627
+
1628
+ // Check if it's a property info
1629
+ if (action.includes(':')) {
1630
+ const parts = action.split(':');
1631
+ if (parts.length === 2) {
1632
+ caption.innerHTML = `
1633
+ <div class="step-number">${stepIndex}. ${parts[0].trim()}</div>
1634
+ <div class="step-action">${parts[1].trim()}</div>
1635
+ `;
1636
+ return;
1637
+ }
1638
+ }
1639
+
1640
+ // Simple action
1641
+ caption.innerHTML = `
1642
+ <div class="step-number">${stepIndex}. Script Step</div>
1643
+ <div class="step-action">${action}</div>
1644
+ `;
1645
+ }
1646
+ });
1647
+ }
1648
+
1649
+ // Call beautify function after DOM is ready
1650
+ beautifyScreenshotCaptions();
897
1651
  });
898
1652
  </script>
899
1653
  </body>