-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathqa-test.html
More file actions
2100 lines (1909 loc) · 175 KB
/
Copy pathqa-test.html
File metadata and controls
2100 lines (1909 loc) · 175 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QA Team Lead — Тест валидации</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
:root {
--bg:#f4f6f9; --surface:#ffffff; --surface2:#f0f2f6; --border:#dde2ea;
--text:#111827; --text-muted:#6b7280; --text-light:#9ca3af;
--accent:#1d4ed8; --accent-soft:#dbeafe; --accent-hover:#1e40af;
--green:#059669; --green-soft:#d1fae5;
--red:#dc2626; --red-soft:#fee2e2;
--yellow:#d97706; --yellow-soft:#fef3c7;
--shadow-sm:0 1px 3px rgba(0,0,0,.08);
--shadow-md:0 4px 16px rgba(0,0,0,.10);
--radius:12px; --radius-sm:8px;
--font:'Sora',sans-serif; --mono:'IBM Plex Mono',monospace;
--transition:.18s ease;
}
[data-theme="dark"] {
--bg:#0f1117; --surface:#1a1d27; --surface2:#232636; --border:#2e3347;
--text:#f1f3f9; --text-muted:#8b92a5; --text-light:#5a6278;
--accent:#3b82f6; --accent-soft:#1e3a5f; --accent-hover:#60a5fa;
--green:#10b981; --green-soft:#064e3b;
--red:#ef4444; --red-soft:#450a0a;
--yellow:#f59e0b; --yellow-soft:#451a03;
--shadow-sm:0 1px 3px rgba(0,0,0,.3);
--shadow-md:0 4px 16px rgba(0,0,0,.4);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{font-family:var(--font);background:var(--bg);color:var(--text);min-height:100vh;transition:background var(--transition),color var(--transition);line-height:1.6}
button{font-family:var(--font);cursor:pointer;border:none;outline:none}
input{font-family:var(--font)}
/* LAYOUT */
.page{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 16px}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-md);width:100%;max-width:800px;padding:36px 42px;position:relative}
/* BRAND */
.brand{display:flex;align-items:center;gap:10px;margin-bottom:28px}
.brand-icon{width:34px;height:34px;background:var(--accent);border-radius:8px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.85rem;font-weight:700;flex-shrink:0}
.brand-label{font-size:.76rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--text-muted)}
/* THEME TOGGLE */
.theme-btn{position:fixed;top:18px;right:18px;width:40px;height:40px;background:var(--surface);border:1px solid var(--border);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.1rem;box-shadow:var(--shadow-sm);z-index:200;transition:background var(--transition)}
.theme-btn:hover{border-color:var(--accent)}
.lang-btn{position:fixed;top:18px;right:66px;width:40px;height:40px;background:var(--surface);border:1px solid var(--border);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.72rem;font-weight:700;font-family:var(--mono);color:var(--text-muted);box-shadow:var(--shadow-sm);z-index:200;cursor:pointer;transition:all var(--transition)}
.lang-btn:hover{border-color:var(--accent);color:var(--accent)}
/* TYPOGRAPHY */
.start-title{font-size:1.65rem;font-weight:700;color:var(--text);margin-bottom:4px;letter-spacing:-.02em}
.start-subtitle{font-size:.9rem;color:var(--text-muted);margin-bottom:24px}
/* RULES GRID */
.rules-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:22px}
.rule-chip{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px 14px;display:flex;align-items:flex-start;gap:9px}
.rule-chip .ri{font-size:1.1rem;flex-shrink:0;margin-top:1px}
.rule-chip p{font-size:.78rem;color:var(--text-muted);line-height:1.4}
.rule-chip strong{color:var(--text);display:block;font-size:.82rem;margin-bottom:1px}
/* SETTINGS BLOCK */
.settings-block{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:16px 18px;margin-bottom:20px}
.settings-title{font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);margin-bottom:14px}
.settings-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
.setting-item label{display:block;font-size:.76rem;font-weight:600;color:var(--text-muted);margin-bottom:5px}
.setting-item input[type=number]{width:100%;padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-family:var(--mono);font-size:.88rem;transition:border-color var(--transition)}
.setting-item input[type=number]:focus{outline:none;border-color:var(--accent)}
.setting-hint{font-size:.7rem;color:var(--text-light);margin-top:3px}
.auto-calc-note{font-size:.72rem;color:var(--accent);margin-top:3px}
/* NAME INPUT */
.name-section{margin-bottom:14px}
.name-section label{display:block;font-size:.8rem;font-weight:600;color:var(--text-muted);margin-bottom:5px}
.name-input{width:100%;padding:11px 14px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-size:.9rem;transition:border-color var(--transition)}
.name-input:focus{outline:none;border-color:var(--accent)}
.name-input.error-border{border-color:var(--red)}
.name-input::placeholder{color:var(--text-light)}
/* CANDIDATE STATUS MESSAGES */
.candidate-msg{display:none;padding:12px 14px;border-radius:var(--radius-sm);margin-bottom:14px;font-size:.83rem;line-height:1.5}
.candidate-msg.blocked{background:var(--red-soft);border:1px solid var(--red);color:var(--red)}
.candidate-msg.interrupted{background:var(--yellow-soft);border:1px solid var(--yellow);color:var(--yellow)}
.candidate-msg strong{display:block;margin-bottom:3px}
.candidate-msg.visible{display:block}
/* WARNING / INFO BOXES */
.warning-box{background:var(--yellow-soft);border:1px solid var(--yellow);border-radius:var(--radius-sm);padding:11px 14px;font-size:.8rem;color:var(--yellow);margin-bottom:20px;display:flex;gap:8px;align-items:flex-start}
.info-box{background:var(--accent-soft);border:1px solid var(--accent);border-radius:var(--radius-sm);padding:10px 14px;font-size:.78rem;color:var(--accent);margin-bottom:14px;display:flex;gap:8px}
/* BUTTONS */
.btn-primary{display:inline-flex;align-items:center;gap:7px;padding:12px 26px;background:var(--accent);color:#fff;border-radius:var(--radius-sm);font-size:.88rem;font-weight:600;transition:background var(--transition),transform .1s;letter-spacing:.01em}
.btn-primary:hover{background:var(--accent-hover)}
.btn-primary:active{transform:scale(.98)}
.btn-primary:disabled{opacity:.45;cursor:not-allowed;transform:none}
.btn-secondary{display:inline-flex;align-items:center;gap:6px;padding:10px 18px;background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:.82rem;font-weight:500;transition:all var(--transition)}
.btn-secondary:hover{border-color:var(--accent);color:var(--accent)}
.btn-danger{display:inline-flex;align-items:center;gap:6px;padding:10px 18px;background:var(--red-soft);color:var(--red);border:1px solid var(--red);border-radius:var(--radius-sm);font-size:.82rem;font-weight:500;transition:all var(--transition)}
.btn-danger:hover{background:var(--red);color:#fff}
/* QUIZ HEADER */
.quiz-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;gap:12px;flex-wrap:wrap}
.quiz-meta{font-size:.78rem;color:var(--text-muted);font-weight:500}
.quiz-meta span{font-family:var(--mono);font-size:.84rem;color:var(--accent);font-weight:500}
.cat-pill{display:inline-flex;align-items:center;padding:3px 9px;border-radius:999px;background:var(--accent-soft);color:var(--accent);font-size:.69rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;margin-top:5px}
/* TIMERS */
.timers-wrap{display:flex;align-items:center;gap:12px;flex-shrink:0}
.timer-ring{position:relative;width:44px;height:44px;flex-shrink:0}
.timer-ring svg{transform:rotate(-90deg)}
.timer-ring circle{fill:none;stroke-width:3.5;stroke-linecap:round}
.timer-ring .track{stroke:var(--border)}
.timer-ring .fill{stroke:var(--green);transition:stroke-dashoffset .9s linear,stroke .5s ease}
.timer-num{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-family:var(--mono);font-size:.76rem;font-weight:500;color:var(--text)}
.total-timer{display:flex;flex-direction:column;align-items:flex-end;gap:1px}
.total-timer-lbl{font-size:.68rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;font-weight:600}
.total-timer-val{font-family:var(--mono);font-size:.96rem;font-weight:600;color:var(--text);transition:color .3s}
.total-timer-val.warn{color:var(--yellow)}
.total-timer-val.danger{color:var(--red)}
/* PROGRESS */
.progress-track{height:4px;background:var(--surface2);border-radius:999px;margin-bottom:20px;overflow:hidden}
.progress-fill{height:100%;background:var(--accent);border-radius:999px;transition:width .4s ease}
/* QUESTION */
.question-text{font-size:1.05rem;font-weight:600;line-height:1.55;color:var(--text);margin-bottom:20px}
.options-grid{display:grid;gap:9px}
.opt-btn{width:100%;text-align:left;padding:13px 16px;border-radius:var(--radius-sm);border:1.5px solid var(--border);background:var(--surface);color:var(--text);font-size:.86rem;font-weight:400;line-height:1.5;transition:all var(--transition);display:flex;align-items:flex-start;gap:11px}
.opt-btn .opt-letter{display:flex;align-items:center;justify-content:center;width:23px;height:23px;border-radius:5px;background:var(--surface2);border:1px solid var(--border);font-size:.7rem;font-weight:700;font-family:var(--mono);color:var(--text-muted);flex-shrink:0;margin-top:1px;transition:all var(--transition)}
.opt-btn:hover:not(:disabled){border-color:var(--accent);background:var(--accent-soft)}
.opt-btn:hover:not(:disabled) .opt-letter{background:var(--accent);border-color:var(--accent);color:#fff}
.opt-btn.correct{border-color:var(--green)!important;background:var(--green-soft)!important;color:var(--green)!important}
.opt-btn.correct .opt-letter{background:var(--green)!important;border-color:var(--green)!important;color:#fff!important}
.opt-btn.wrong{border-color:var(--red)!important;background:var(--red-soft)!important;color:var(--red)!important}
.opt-btn.wrong .opt-letter{background:var(--red)!important;border-color:var(--red)!important;color:#fff!important}
.opt-btn:disabled{cursor:default}
/* EXPLANATION / TIMEOUT */
.explanation-box{display:none;margin-top:14px;padding:13px 15px;border-radius:var(--radius-sm);border-left:3px solid var(--accent);background:var(--surface2);font-size:.8rem;color:var(--text-muted);line-height:1.6}
.explanation-box.visible{display:block;animation:fadeIn .22s ease}
.explanation-box strong{color:var(--accent);display:block;margin-bottom:3px;font-size:.72rem;text-transform:uppercase;letter-spacing:.06em}
.timeout-box{display:none;margin-top:14px;padding:11px 15px;border-radius:var(--radius-sm);border:1px solid var(--red);background:var(--red-soft);color:var(--red);font-size:.82rem;font-weight:500}
.timeout-box.visible{display:block;animation:fadeIn .22s ease}
.total-timeout-box{display:none;margin-top:14px;padding:11px 15px;border-radius:var(--radius-sm);border:1px solid var(--yellow);background:var(--yellow-soft);color:var(--yellow);font-size:.85rem;font-weight:600}
@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
/* RESULT SCREEN */
.result-hero{text-align:center;margin-bottom:28px;padding-bottom:24px;border-bottom:1px solid var(--border)}
.result-score-big{font-size:3.8rem;font-weight:700;color:var(--accent);line-height:1;font-family:var(--mono);letter-spacing:-.04em}
.result-score-big sup{font-size:1.4rem;font-weight:400}
.result-level{display:inline-block;margin-top:9px;padding:5px 14px;border-radius:999px;font-size:.8rem;font-weight:600;letter-spacing:.04em}
.level-low{background:var(--red-soft);color:var(--red)}
.level-base{background:var(--yellow-soft);color:var(--yellow)}
.level-good{background:var(--accent-soft);color:var(--accent)}
.level-lead{background:var(--green-soft);color:var(--green)}
.result-name{font-size:.9rem;color:var(--text-muted);margin-top:7px}
.result-name strong{color:var(--text)}
.result-meta-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:20px}
.meta-box{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px}
.meta-box .mk{font-size:.7rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;font-weight:600;margin-bottom:2px}
.meta-box .mv{font-family:var(--mono);font-size:.9rem;font-weight:600;color:var(--text)}
.stats-row{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:24px}
.stat-box{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:13px 11px;text-align:center}
.stat-box .sv{font-size:1.4rem;font-weight:700;font-family:var(--mono);display:block}
.stat-box .sk{font-size:.69rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;font-weight:600;margin-top:2px}
.sv-correct{color:var(--green)}
.sv-wrong{color:var(--red)}
.sv-timeout{color:var(--yellow)}
.sv-time{color:var(--accent)}
.review-title{font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);margin-bottom:12px}
.review-list{display:grid;gap:9px;max-height:460px;overflow-y:auto;padding-right:3px}
.review-list::-webkit-scrollbar{width:4px}
.review-list::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
.review-item{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:13px 15px}
.review-item.ri-correct{border-left:3px solid var(--green)}
.review-item.ri-wrong{border-left:3px solid var(--red)}
.review-item.ri-timeout{border-left:3px solid var(--yellow)}
.ri-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:7px}
.ri-q{font-size:.83rem;font-weight:600;line-height:1.4;color:var(--text)}
.ri-status{flex-shrink:0;font-size:.67rem;font-weight:700;padding:2px 8px;border-radius:999px;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap}
.rs-correct{background:var(--green-soft);color:var(--green)}
.rs-wrong{background:var(--red-soft);color:var(--red)}
.rs-timeout{background:var(--yellow-soft);color:var(--yellow)}
.ri-answers{font-size:.76rem;color:var(--text-muted);display:grid;gap:3px;margin-bottom:6px}
.ri-answers span{display:flex;gap:5px;flex-wrap:wrap}
.ri-answers .lbl{font-weight:600;color:var(--text);white-space:nowrap}
.ri-expl{font-size:.74rem;color:var(--text-muted);border-top:1px solid var(--border);padding-top:6px;margin-top:3px;line-height:1.5}
.export-row{display:flex;gap:9px;flex-wrap:wrap;margin-top:20px;padding-top:18px;border-top:1px solid var(--border)}
.actions-row{display:flex;gap:9px;flex-wrap:wrap;margin-top:12px}
/* MODAL */
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:300;padding:20px}
.modal-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:28px 32px;width:100%;max-width:560px;box-shadow:0 8px 40px rgba(0,0,0,.2)}
.modal-title{font-size:1.05rem;font-weight:700;margin-bottom:16px;color:var(--text)}
.modal-close{position:absolute;top:14px;right:16px;background:none;border:none;font-size:1.3rem;cursor:pointer;color:var(--text-muted);line-height:1}
.modal-result{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:14px;margin-top:14px;font-size:.82rem;line-height:1.7}
.modal-result strong{color:var(--text)}
/* ADMIN PANEL */
.admin-wrap{position:fixed;bottom:0;left:0;right:0;background:var(--surface);border-top:1px solid var(--border);z-index:150}
.admin-toggle-btn{width:100%;padding:9px 20px;text-align:center;font-size:.75rem;color:var(--text-light);cursor:pointer;background:none;border:none;font-family:var(--font);letter-spacing:.05em;transition:color var(--transition)}
.admin-toggle-btn:hover{color:var(--text-muted)}
.admin-panel{padding:16px 20px;background:var(--surface2);border-top:1px solid var(--border)}
.admin-row{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap}
.admin-row input{flex:1;min-width:140px;padding:8px 11px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:.82rem}
.admin-row input:focus{outline:none;border-color:var(--accent)}
.admin-msg{margin-top:8px;font-size:.78rem;padding:7px 10px;border-radius:6px}
.admin-msg.ok{background:var(--green-soft);color:var(--green)}
.admin-msg.err{background:var(--red-soft);color:var(--red)}
/* MOBILE */
@media(max-width:640px){
.card{padding:22px 16px}
.rules-grid,.settings-grid{grid-template-columns:1fr}
.stats-row{grid-template-columns:1fr 1fr}
.result-meta-grid{grid-template-columns:1fr 1fr}
.start-title{font-size:1.35rem}
.question-text{font-size:.95rem}
.opt-btn{font-size:.82rem;padding:11px 12px}
.result-score-big{font-size:3rem}
.admin-row{flex-direction:column}
}
/* PRINT */
@media print{
.theme-btn,.admin-wrap,.export-row,.actions-row{display:none!important}
.card{box-shadow:none;border:none;max-width:100%;padding:0}
.review-list{max-height:none;overflow:visible}
}
.adm-tab{padding:6px 13px;border-radius:var(--radius-sm) var(--radius-sm) 0 0;font-size:.76rem;font-weight:600;background:transparent;border:1px solid transparent;border-bottom:none;color:var(--text-muted);cursor:pointer;font-family:var(--font);transition:color var(--transition)}
.adm-tab:hover{color:var(--text)}
.adm-tab-active{background:var(--surface);border-color:var(--border);color:var(--accent)}
</style>
</head>
<body>
<button class="theme-btn" id="themeBtn" title="Тема">🌙</button>
<button class="lang-btn" id="langBtn" onclick="toggleLang()">EN</button>
<!-- ═══════════ START SCREEN ═══════════ -->
<div class="page" id="screenStart">
<div class="card">
<div class="brand">
<div class="brand-icon">QA</div>
<span class="brand-label">Team Lead Validation Test</span>
</div>
<h1 class="start-title" id="startTitle">Тест QA Team Lead</h1>
<p class="start-subtitle" id="startSubtitle">60 вопросов по тест-дизайну, баг-менеджменту, автоматизации, метрикам и управлению командой.</p>
<div class="rules-grid">
<div class="rule-chip"><span class="ri">📋</span><div><strong>До 60 вопросов</strong><p>Практические ситуационные вопросы уровня Middle+/Senior/Lead</p></div></div>
<div class="rule-chip"><span class="ri">⏱️</span><div><strong>Два таймера</strong><p>На каждый вопрос и общий лимит времени теста</p></div></div>
<div class="rule-chip"><span class="ri">🔒</span><div><strong>Однократное прохождение</strong><p>Тест можно пройти один раз с одним именем на данном устройстве</p></div></div>
<div class="rule-chip"><span class="ri">📊</span><div><strong>Детальные результаты</strong><p>Разбор всех вопросов и экспорт в JSON / CSV / PDF</p></div></div>
</div>
<!-- Settings summary (read-only) -->
<div id="settingsSummary" style="background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 16px;margin-bottom:18px;font-size:.8rem;color:var(--text-muted);display:flex;gap:18px;flex-wrap:wrap">
<span>⚙ <strong id="summNumQ">30</strong> вопросов</span>
<span>⏱ <strong id="summTimeQ">30</strong> сек</span>
<span>🕐 <strong id="summTimeTotal">15</strong> мин</span>
</div>
<input type="hidden" id="settingNumQ" value="30">
<input type="hidden" id="settingTimeQ" value="30">
<input type="hidden" id="settingTimeTotal" value="15">
<!-- NAME --> <!-- NAME -->
<div class="name-section">
<label id="nameLbl">Имя кандидата <span style="color:var(--red)">*</span></label>
<input class="name-input" id="candidateName" type="text" placeholder="Введите полное имя кандидата" maxlength="100" required>
</div>
<!-- CANDIDATE STATUS MESSAGE -->
<div class="candidate-msg blocked" id="msgBlocked">
<strong>🚫 Повторное прохождение невозможно</strong>
<span id="msgBlockedText">Кандидат с таким именем уже проходил тест.</span>
</div>
<div class="candidate-msg interrupted" id="msgInterrupted">
<strong>⚠️ Тест был прерван</strong>
Тест был начат ранее и не завершён. Повторное прохождение невозможно без сброса администратором.
</div>
<div class="info-box">
<span>ℹ️</span>
<span>Тест можно пройти только один раз с указанным именем. Повторное прохождение с тем же именем будет заблокировано на этом устройстве.</span>
</div>
<div class="warning-box">
<span>⚠️</span>
<span>После нажатия «Начать тест» поставить на паузу нельзя. Перезагрузка страницы во время теста будет считаться прерванием и заблокирует повторный доступ для данного имени.</span>
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
<button class="btn-primary" id="btnStart">▶ Начать тест</button>
<button class="btn-secondary" id="btnCheckCandidate">🔍 Проверить кандидата</button>
<button class="btn-danger" id="btnClearViewed" style="display:none">🗑 Очистить просмотренный результат</button>
</div>
</div>
</div>
<!-- ═══════════ QUIZ SCREEN ═══════════ -->
<div class="page" id="screenQuiz" style="display:none">
<div class="card">
<div class="quiz-header">
<div>
<div class="quiz-meta">Вопрос <span id="qIndex">1</span> из <span id="qTotal">30</span></div>
<div class="cat-pill" id="qCategory">Категория</div>
</div>
<div class="timers-wrap">
<div class="total-timer">
<div class="total-timer-lbl">Общее</div>
<div class="total-timer-val" id="totalTimerVal">15:00</div>
</div>
<div class="timer-ring">
<svg width="44" height="44" viewBox="0 0 44 44">
<circle class="track" cx="22" cy="22" r="18"/>
<circle class="fill" id="timerCircle" cx="22" cy="22" r="18" stroke-dasharray="113.1" stroke-dashoffset="0"/>
</svg>
<div class="timer-num" id="timerNum">30</div>
</div>
</div>
</div>
<div class="progress-track">
<div class="progress-fill" id="progressFill" style="width:0%"></div>
</div>
<div class="question-text" id="questionText"></div>
<div class="options-grid" id="optionsGrid"></div>
<div class="explanation-box" id="explanationBox">
<strong>💡 Пояснение</strong>
<span id="explanationText"></span>
</div>
<div class="timeout-box" id="timeoutBox">⏰ Время на вопрос истекло. Правильный ответ выделен зелёным.</div>
<div class="total-timeout-box" id="totalTimeoutBox">🚨 Общее время теста истекло. Тест завершается автоматически.</div>
</div>
</div>
<!-- ═══════════ RESULT SCREEN ═══════════ -->
<div class="page" id="screenResult" style="display:none">
<div class="card" style="max-width:880px">
<div class="result-hero">
<div class="result-score-big" id="rScore">0<sup>%</sup></div>
<div class="result-level" id="rLevel">—</div>
<div class="result-name" id="rName"></div>
</div>
<div class="result-meta-grid">
<div class="meta-box"><div class="mk" id="rDateLbl">📅 Дата прохождения</div><div class="mv" id="rDate">—</div></div>
<div class="meta-box"><div class="mk" id="rNumQLbl">📋 Вопросов в тесте</div><div class="mv" id="rNumQ">—</div></div>
<div class="meta-box"><div class="mk" id="rTimeQLbl">⏱ Время на вопрос</div><div class="mv" id="rTimeQ">—</div></div>
<div class="meta-box"><div class="mk" id="rTotalLbl">🕐 Лимит теста</div><div class="mv" id="rTotalLimit">—</div></div>
<div class="meta-box"><div class="mk" id="rSpentLbl">⌛ Фактическое время</div><div class="mv" id="rTimeSpent">—</div></div>
<div class="meta-box"><div class="mk" id="rFinishLbl">🏁 Завершение</div><div class="mv" id="rFinishReason">Полное</div></div>
</div>
<div class="stats-row">
<div class="stat-box"><span class="sv sv-correct" id="rCorrect">0</span><span class="sk">✓ Верно</span></div>
<div class="stat-box"><span class="sv sv-wrong" id="rWrong">0</span><span class="sk">✗ Неверно</span></div>
<div class="stat-box"><span class="sv sv-timeout" id="rTimeout">0</span><span class="sk">⏰ Таймаут</span></div>
<div class="stat-box"><span class="sv sv-time" id="rPercent">0%</span><span class="sk">📊 Результат</span></div>
</div>
<div class="review-title">Детальный разбор</div>
<div class="review-list" id="reviewList"></div>
<div class="export-row">
<button class="btn-secondary" id="btnExportJSON">⬇ JSON</button>
<button class="btn-secondary" id="btnExportCSV">⬇ CSV</button>
<button class="btn-secondary" id="btnPrint">🖨 Печать / PDF</button>
</div>
<div class="actions-row">
<button class="btn-secondary" id="btnBackToStart">← На главную</button>
</div>
</div>
</div>
<!-- ═══════════ CHECK CANDIDATE MODAL ═══════════ -->
<div class="modal-overlay" id="modalCheck" style="display:none" onclick="closeCheckModal(event)">
<div class="modal-box" style="position:relative">
<button class="modal-close" onclick="closeModal()">✕</button>
<div class="modal-title">🔍 Проверить кандидата</div>
<div style="display:flex;gap:9px;margin-bottom:8px">
<input class="name-input" id="checkName" type="text" placeholder="Введите имя кандидата" style="flex:1">
<button class="btn-primary" onclick="doCheckCandidate()" style="padding:10px 18px;font-size:.84rem">Найти</button>
</div>
<div id="checkResult"></div>
<div style="margin-top:18px;border-top:1px solid var(--border);padding-top:14px">
<div style="font-size:.76rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);margin-bottom:10px">Все кандидаты</div>
<div id="allCandidatesList" style="font-size:.8rem;max-height:220px;overflow-y:auto"></div>
</div>
</div>
</div>
<!-- ═══════════ ADMIN PANEL ═══════════ -->
<div class="admin-wrap">
<button class="admin-toggle-btn" onclick="toggleAdmin()">🔐 Панель администратора</button>
<div class="admin-panel" id="adminPanel" style="display:none">
<div id="adminAuthGate">
<div style="font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);margin-bottom:10px">🔑 Вход</div>
<div style="display:flex;gap:9px;align-items:center;flex-wrap:wrap;max-width:480px">
<input type="password" id="adminAuthPwd" placeholder="Пароль" style="flex:1;min-width:160px;padding:8px 11px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:.82rem;font-family:var(--font)">
<button class="btn-primary" onclick="adminAuth()" style="padding:9px 18px;font-size:.82rem">Войти</button>
</div>
<div id="adminAuthErr" style="display:none;margin-top:6px;font-size:.76rem;color:var(--red)">❌ Неверный пароль</div>
</div>
<div id="adminContent" style="display:none">
<div style="display:flex;gap:3px;margin-bottom:14px;border-bottom:2px solid var(--border)">
<button class="adm-tab adm-tab-active" id="atab-settings" onclick="aTab('settings')">⚙ Настройки</button>
<button class="adm-tab" id="atab-reset" onclick="aTab('reset')">🔄 Сброс</button>
<button class="adm-tab" id="atab-history" onclick="aTab('history')">📊 История</button>
</div>
<!-- SETTINGS TAB -->
<div id="apane-settings">
<div style="font-size:.75rem;color:var(--text-muted);margin-bottom:10px">Значения применяются для всех следующих прохождений.</div>
<div class="settings-grid" style="max-width:500px;margin-bottom:12px">
<div class="setting-item"><label style="font-size:.76rem;font-weight:600;color:var(--text-muted);display:block;margin-bottom:4px">Кол-во вопросов</label><input type="number" id="aNumQ" min="5" max="60" value="30" style="width:100%;padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-family:var(--mono);font-size:.88rem"><div class="setting-hint">от 5 до 60</div></div>
<div class="setting-item"><label style="font-size:.76rem;font-weight:600;color:var(--text-muted);display:block;margin-bottom:4px">Сек на вопрос</label><input type="number" id="aTimeQ" min="10" max="180" value="30" style="width:100%;padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-family:var(--mono);font-size:.88rem"><div class="setting-hint">от 10 до 180</div></div>
<div class="setting-item"><label style="font-size:.76rem;font-weight:600;color:var(--text-muted);display:block;margin-bottom:4px">Лимит (мин)</label><input type="number" id="aTotalTime" min="1" max="120" value="15" style="width:100%;padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-family:var(--mono);font-size:.88rem"><div class="setting-hint">от 1 до 120</div></div>
</div>
<button class="btn-primary" onclick="saveAdminSettings()" style="padding:9px 20px;font-size:.82rem">💾 Сохранить</button>
<div id="adminSettingsMsg" class="admin-msg" style="display:none;margin-top:8px"></div>
</div>
<!-- RESET TAB -->
<div id="apane-reset" style="display:none">
<div style="font-size:.75rem;color:var(--text-muted);margin-bottom:10px">Сброс позволяет кандидату пройти тест повторно.</div>
<div class="admin-row">
<input type="text" id="adminCandidateName" placeholder="Имя кандидата">
<input type="password" id="adminPassword" placeholder="Пароль">
<button class="btn-primary" onclick="adminReset()" style="padding:9px 18px;font-size:.82rem">Сбросить</button>
</div>
<div id="adminMsg" style="display:none" class="admin-msg"></div>
</div>
<!-- HISTORY TAB -->
<div id="apane-history" style="display:none">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;flex-wrap:wrap;gap:8px">
<span id="historyCount" style="font-size:.76rem;color:var(--text-muted)"></span>
<div style="display:flex;gap:7px">
<button class="btn-secondary" onclick="exportHistoryCSV()" style="padding:6px 13px;font-size:.76rem">↓ CSV</button>
<button class="btn-primary" onclick="exportHistoryXLSX()" style="padding:6px 13px;font-size:.76rem">📊 Excel</button>
</div>
</div>
<div id="historyList" style="font-size:.8rem;max-height:280px;overflow-y:auto"></div>
</div>
</div>
</div>
</div>
<script>
// ═══════════════════════════════════════════════════════════════════════
// ADMIN PASSWORD
// NOTE: localStorage — не серверная защита. Кандидат может открыть DevTools
// и очистить данные. Это решение подходит для локального / HR-тестирования,
// где предполагается честное участие кандидата.
// ═══════════════════════════════════════════════════════════════════════
const ADMIN_PASSWORD = "qa-admin-2026";
// ── STORAGE KEYS ──
const SK_CANDIDATES = 'qa_test_completed_candidates'; // основное хранилище кандидатов
const SK_VIEWED = 'qa_lead_test_result'; // последний просмотренный результат (legacy)
const SK_THEME = 'qa_theme';
// ═══════════════════════════════════════════════════════════════════════
// QUESTIONS — 60 вопросов
// correctAnswer: индекс в массиве options (0=A, 1=B, 2=C, 3=D)
// ═══════════════════════════════════════════════════════════════════════
const QUESTIONS = [
// ── 1–30: Оригинальные вопросы ──
{
id:1, category:"Bug Management",
question:"Перед релизом найден дефект с Severity: Critical, Priority: Low. Как должен поступить QA Team Lead?",
options:["Автоматически заблокировать релиз, так как severity критическая","Игнорировать дефект — priority низкая, значит бизнес не спешит","Обсудить бизнес-риск с PO/PM и принять release decision на основе impact","Передать решение разработчику и не участвовать"],
correctAnswer:2,
explanation:"Severity — техническая серьёзность, Priority — бизнес-срочность. QA Lead обязан помочь оценить риск совместно с PO/PM, предложить mitigation plan и принять взвешенное решение о готовности к релизу."
},
{
id:2, category:"Test Design",
question:"Команда тестирует форму регистрации. Поле «возраст» принимает значения от 18 до 99. Какие граничные значения обязательны?",
options:["17, 18, 50, 99, 100","0, 50, 100","18, 99","18, 19, 98, 99"],
correctAnswer:0,
explanation:"Boundary Value Analysis требует проверки: минимум-1 (17), минимум (18), типичное (50), максимум (99), максимум+1 (100). Это покрывает граничные случаи и типичный диапазон."
},
{
id:3, category:"QA Metrics",
question:"Defect Leakage Rate в последнем спринте составил 35%. Что это означает и что делать QA Lead?",
options:["35% дефектов было найдено до деплоя — хороший показатель","35% дефектов попало в прод, минуя QA — нужно улучшить покрытие и gates","Метрика показывает процент флейки-тестов в CI","35% тест-кейсов упало на регрессии — нужно пересмотреть тесты"],
correctAnswer:1,
explanation:"Defect Leakage = (defects found in prod / total defects) × 100%. 35% означает серьёзную проблему: треть дефектов прошла через QA в продакшн. Нужен анализ root cause, усиление regression suite и definition of done."
},
{
id:4, category:"API Testing",
question:"При тестировании REST API POST /orders возвращает 201 Created с телом. Какой следующий шаг наиболее важен для QA?",
options:["Проверить только статус-код ответа","Проверить статус-код, схему тела ответа, заголовки и состояние данных в БД","Проверить время ответа","Сравнить ответ с документацией Swagger"],
correctAnswer:1,
explanation:"Полноценное API тестирование включает: status code, response body schema, заголовки (Content-Type, Location), и верификацию side effects — данные реально созданы в БД корректно."
},
{
id:5, category:"CI/CD & QA",
question:"QA Lead встраивает тесты в CI/CD pipeline. Что следует запускать при каждом pull request?",
options:["Только unit-тесты — они быстрые","Полный регрессионный suite — чтобы ничего не пропустить","Unit + интеграционные тесты и critical path smoke-тесты — баланс скорости и покрытия","Только E2E тесты, так как они ближе к реальному пользователю"],
correctAnswer:2,
explanation:"При каждом PR нужен быстрый feedback: unit + integration + smoke critical path (Test Pyramid). Полная регрессия запускается перед релизом или ночью. Цель PR gate — fail fast без тормозов пайплайна."
},
{
id:6, category:"Team Management",
question:"Junior QA постоянно пишет неинформативные баг-репорты без шагов воспроизведения. Как QA Lead должен исправить ситуацию?",
options:["Отклонять все его репорты без объяснений","Провести парное занятие, показать шаблон good bug report, установить чеклист review для багов","Передать его задачи более опытному QA","Написать memo с правилами и отправить по почте"],
correctAnswer:1,
explanation:"Менторинг — ключевая компетенция QA Lead. Правильный подход: показать example, дать template, ввести review процесс. Это растит команду, а не создаёт зависимость или демотивацию."
},
{
id:7, category:"Risk-Based Testing",
question:"У команды 3 дня на регрессионное тестирование перед крупным релизом. Какой подход правильный?",
options:["Тестировать всё подряд по алфавиту","Провести risk assessment: prioritize high-risk areas, critical paths и регрессию изменённого кода","Запустить автотесты и считать работу выполненной","Тестировать только то, что изменилось в этом релизе"],
correctAnswer:1,
explanation:"Risk-based testing — основа эффективного QA при ограниченном времени. Нужно оценить: вероятность поломки × бизнес-impact. Приоритизировать critical paths, интеграционные точки и области, изменённые в данном релизе."
},
{
id:8, category:"Agile / Scrum",
question:"На планировании спринта разработчики оценили задачи, а QA не успел оценить объём тестирования. Что должен сделать QA Lead?",
options:["Принять эстимацию разработчиков молча","Добавить фиксированный буфер 20% на тестирование","Настоять на включении QA-эстимации в story points и уточнить DoD со сценариями тестирования","Тестировать то, что успеет, остальное — следующий спринт"],
correctAnswer:2,
explanation:"QA должен участвовать в estimation и refinement. Story point должен включать время на тест-дизайн, выполнение тестов и автоматизацию. Definition of Done обязан содержать QA acceptance criteria."
},
{
id:9, category:"SQL & Data Validation",
question:"После миграции данных нужно проверить корректность. Какой SQL-запрос поможет найти дублирующиеся email в таблице users?",
options:["SELECT email FROM users WHERE email IS NOT NULL","SELECT email, COUNT(*) as cnt FROM users GROUP BY email HAVING COUNT(*) > 1","SELECT DISTINCT email FROM users","SELECT email FROM users ORDER BY email"],
correctAnswer:1,
explanation:"GROUP BY + HAVING COUNT(*) > 1 — стандартный паттерн поиска дублей. QA должен уметь писать такие запросы для data validation после миграций, ETL-процессов и интеграций."
},
{
id:10, category:"Automation",
question:"Какой тип тестов наименее целесообразно автоматизировать в первую очередь?",
options:["Регрессионные тесты, которые запускаются при каждом деплое","Smoke-тесты на critical path","Тесты на интуитивность UX и удобство пользователя","API-тесты на основные CRUD операции"],
correctAnswer:2,
explanation:"UX usability тестирование требует человеческого суждения и субъективной оценки. Автоматизировать его бессмысленно — нужен реальный пользователь или UX-исследователь. Всё остальное — хорошие кандидаты для автоматизации."
},
{
id:11, category:"Bug Lifecycle",
question:"Разработчик закрыл баг со статусом «Cannot reproduce». Как должен реагировать QA?",
options:["Принять закрытие — разработчик всегда прав","Переоткрыть баг с полными шагами воспроизведения, логами, скриншотами и environment info","Немедленно эскалировать PM","Удалить баг из трекера и создать новый"],
correctAnswer:1,
explanation:"Cannot Reproduce не значит «не существует». QA должен обогатить репорт: точные шаги, тестовые данные, версию ПО, логи, env. Если воспроизводится стабильно у QA — это основание для реопена с доказательной базой."
},
{
id:12, category:"QA Process",
question:"Вас попросили построить QA-процесс в команде, где его нет совсем. С чего начать?",
options:["Сразу писать автотесты","Провести аудит: понять product, текущий SDLC, болевые точки; затем внедрять процессы инкрементально","Нанять больше QA и сразу покрыть всё тестами","Внедрить всю документацию Test Plan, Test Strategy за первую неделю"],
correctAnswer:1,
explanation:"QA с нуля начинается с discovery: понять продукт, команду, риски. Затем quick wins (bug tracking, test cases для critical paths), потом CI integration, автоматизация. Нельзя строить rocket ship без понимания куда летит компания."
},
{
id:13, category:"Release Readiness",
question:"Какой из критериев НЕ является типичным release readiness criteria?",
options:["Нет открытых Critical/Blocker дефектов","Test pass rate ≥ 95% на regression suite","Все задачи в спринте закрыты разработчиками","Performance baseline соответствует SLA"],
correctAnswer:2,
explanation:"Закрытые задачи разработки — это development done, не release readiness. Release gate включает: дефекты (no critical open), test coverage, performance, security, smoke-тесты в staging. Статус задачи в Jira — не QA критерий."
},
{
id:14, category:"Exploratory Testing",
question:"Команда провела exploratory testing сессию. Как QA Lead должен оформить результаты?",
options:["Exploratory testing не требует документации — это свободное исследование","Создать test charter, session notes, найденные дефекты и coverage areas — session-based format","Написать полноценные тест-кейсы по всему, что проверялось","Передать устный отчёт PM"],
correctAnswer:1,
explanation:"Exploratory testing документируется через Test Charter (цель сессии), Session Notes (что сделано), Coverage Map (что покрыто), список найденных дефектов. Session-Based Test Management (SBTM) — стандартный подход."
},
{
id:15, category:"Communication",
question:"PO требует выпустить релиз завтра, но у QA есть незакрытый Major-дефект. Что должен сделать QA Lead?",
options:["Молча выпустить релиз — решение за PO","Заблокировать релиз без обсуждения","Чётко донести риск, предложить mitigation (hotfix plan, rollback strategy), и зафиксировать решение письменно","Уйти на больничный, чтобы не нести ответственность"],
correctAnswer:2,
explanation:"QA Lead — это gate keeper рисков, не блокер. Нужно ясно коммуницировать риск (что может сломаться, для кого, какой impact), предложить план митигации, и если PO принимает риск осознанно — зафиксировать это письменно."
},
{
id:16, category:"Flaky Tests",
question:"В CI 15% автотестов нестабильны (иногда падают без изменений кода). Как QA Lead должен решить эту проблему?",
options:["Удалить нестабильные тесты — они создают шум","Игнорировать — нестабильность в тестах это норма","Провести анализ причин (race conditions, env зависимости, таймауты), починить или изолировать в отдельный suite с мониторингом","Запускать тесты 3 раза и считать успешным если хотя бы один прошёл"],
correctAnswer:2,
explanation:"Flaky tests разрушают доверие к CI. Подходы: root cause analysis, fix timeouts/waits, mock external dependencies, использовать retry с отчётностью, quarantine flaky suite. Автоматический retry без fix — временная мера, не решение."
},
{
id:17, category:"Test Design",
question:"Приложение имеет 5 фильтров, каждый из 3 значений (243 комбинации). Какой подход позволит сократить до ~25 тестов с разумным покрытием?",
options:["Equivalence Partitioning","All-Pairs (Pairwise) Testing","Decision Table Testing","State Transition Testing"],
correctAnswer:1,
explanation:"Pairwise (All-Pairs) Testing — математически оптимизированный метод, покрывающий все пары значений (не все комбинации). Большинство багов порождается взаимодействием двух факторов, что позволяет сократить 243 теста до ~25–30."
},
{
id:18, category:"Team Management",
question:"Два QA в команде конфликтуют из-за подхода к приоритизации дефектов. Как реагирует QA Lead?",
options:["Выбрать сторону более опытного QA","Игнорировать конфликт — команда сама разберётся","Провести встречу, выслушать обе стороны, зафиксировать общее соглашение по severity/priority matrix и ввести его как командный стандарт","Передать конфликт HR"],
correctAnswer:2,
explanation:"QA Lead создаёт ясность через стандарты. Конфликт по классификации дефектов — сигнал отсутствия общего definition. Нужен severity/priority матрикс как командное соглашение, согласованное со стейкхолдерами."
},
{
id:19, category:"Smoke & Sanity",
question:"В чём ключевое отличие Smoke Testing от Sanity Testing?",
options:["Smoke — ручное, Sanity — автоматическое","Smoke проверяет, стабильна ли сборка для дальнейшего тестирования; Sanity — проверяет конкретную исправленную функцию или дефект","Smoke более детальный, Sanity — быстрый","Это синонимы с разными названиями"],
correctAnswer:1,
explanation:"Smoke (Build Verification Test) — широкая, поверхностная проверка «пригоден ли билд к тестированию». Sanity — узкая, глубокая проверка конкретного fix или фичи, обычно без регрессии всего приложения."
},
{
id:20, category:"Production Incidents",
question:"В продакшне произошёл инцидент — критический баг был пропущен QA. Что должен сделать QA Lead в первую очередь?",
options:["Найти виновного QA и наказать","Провести blameless postmortem: выяснить root cause, почему тест не поймал баг, какие процессы нужно улучшить","Написать больше тест-кейсов на этот конкретный сценарий","Увеличить команду QA"],
correctAnswer:1,
explanation:"Blameless postmortem — стандарт SRE/DevOps культуры. Цель: не найти виновного, а устранить системную причину. Что пошло не так в процессе? Почему тест не существовал или не сработал? Как предотвратить в будущем?"
},
{
id:21, category:"API Testing",
question:"Какой HTTP статус-код должен вернуть REST API при попытке создать ресурс с невалидными данными?",
options:["200 OK","404 Not Found","422 Unprocessable Entity или 400 Bad Request","500 Internal Server Error"],
correctAnswer:2,
explanation:"400 Bad Request — общая ошибка клиента, 422 Unprocessable Entity — семантически некорректные данные. 500 означает ошибку сервера, что неверно при проблеме клиентских данных."
},
{
id:22, category:"Test Coverage",
question:"PM говорит: «У нас 80% test coverage — значит мы хорошо протестированы». QA Lead должен:",
options:["Согласиться — 80% это хорошо по индустриальным стандартам","Объяснить, что code coverage ≠ functional coverage, и важно покрытие user scenarios, edge cases и рисков, а не строк кода","Увеличить coverage до 100% любой ценой","Ничего не делать — решение за PM"],
correctAnswer:1,
explanation:"Code coverage — метрика качества тестов, не гарантия отсутствия багов. 80% строк покрыто ≠ критические сценарии протестированы. QA Lead должен говорить о functional coverage, risk coverage и business scenario coverage."
},
{
id:23, category:"UI Testing",
question:"Автоматизированный UI тест падает после каждого редизайна. Как правильно решить проблему хрупкости?",
options:["Использовать XPath с абсолютными путями для надёжности","Ввести Page Object Model, использовать data-testid атрибуты, изолировать локаторы от бизнес-логики","Переписывать тесты вручную после каждого редизайна","Отказаться от UI автоматизации"],
correctAnswer:1,
explanation:"Page Object Model инкапсулирует локаторы в одном месте. data-testid (или aria-label) — стабильные атрибуты специально для тестирования. Абсолютные XPath — самый хрупкий вариант."
},
{
id:24, category:"QA Process",
question:"QA-команда тратит 60% времени на ручную регрессию перед каждым релизом. Как QA Lead должен изменить ситуацию?",
options:["Нанять больше QA для ручного тестирования","Полностью отказаться от регрессии","Провести ROI-анализ автоматизации, определить приоритетные кандидаты, внедрить automated regression suite с CI интеграцией","Сократить частоту релизов"],
correctAnswer:2,
explanation:"60% на ручную регрессию — сигнал к автоматизации. ROI calculation: (время на ручную регрессию × частота × стоимость) vs (время на создание автотестов + maintenance). ROI обычно положительный уже через 3–5 итераций."
},
{
id:25, category:"Agile / Scrum",
question:"На retrospective выявили, что баги находятся слишком поздно — в конце спринта. Какое системное решение правильное?",
options:["Добавить QA больше времени в конце спринта","Внедрить Shift-Left подход: вовлечь QA с refinement, писать acceptance criteria до разработки, использовать TDD/BDD","Тестировать только в выделенных QA-спринтах","Увеличить длину спринта до 4 недель"],
correctAnswer:1,
explanation:"Shift-Left Testing — перенос QA активностей влево в SDLC. QA участвует в refinement, пишет acceptance criteria (Gherkin/BDD), разработчики имеют ясные критерии приёмки. Баги находятся до кода, а не после."
},
{
id:26, category:"Knowledge Sharing",
question:"QA Lead уходит в отпуск на 3 недели. Что должно быть подготовлено?",
options:["Контактный телефон на случай срочных вопросов","Актуальная документация процессов, назначенный acting lead, runbook по release process и escalation matrix","Поставить задачи в Jira и надеяться, что команда справится","Перенести все релизы на период после отпуска"],
correctAnswer:1,
explanation:"Knowledge sharing — обязанность QA Lead. Bus factor = 1 это риск. Нужны: документированные процессы, trained backup lead, release checklist, escalation paths. Это также признак зрелости команды и процесса."
},
{
id:27, category:"SQL & Data Validation",
question:"QA нужно проверить, что у всех заказов есть привязанный пользователь (нет orphan records). Какой SQL запрос подходит?",
options:["SELECT * FROM orders WHERE user_id IS NULL","SELECT o.* FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE u.id IS NULL","SELECT * FROM orders INNER JOIN users ON orders.user_id = users.id","SELECT COUNT(*) FROM orders"],
correctAnswer:1,
explanation:"LEFT JOIN + WHERE u.id IS NULL — классический паттерн поиска orphan records. Вариант А найдёт только orders с NULL user_id, но пропустит несуществующие ID."
},
{
id:28, category:"QA Metrics",
question:"Какая метрика лучше всего показывает качество тест-дизайна команды, а не качество продукта?",
options:["Defect Density (число дефектов на 1000 LOC)","Test Case Effectiveness (% дефектов, найденных тест-кейсами vs общее число дефектов)","MTTR (Mean Time To Restore)","Code Coverage %"],
correctAnswer:1,
explanation:"Test Case Effectiveness = найденные тест-кейсами дефекты / всего дефектов. Высокий % означает, что QA тест-дизайн реально ловит баги. Низкий — тест-кейсы не покрывают реальные сценарии использования."
},
{
id:29, category:"Automation",
question:"Разработчик предлагает покрыть весь функционал E2E автотестами. QA Lead должен:",
options:["Согласиться — E2E тесты наиболее близки к пользователю","Объяснить Test Pyramid: большинство покрытия должно быть unit+integration, E2E — только для critical paths, так как они дорогие и хрупкие","Отказаться от E2E полностью","Делегировать решение команде разработки"],
correctAnswer:1,
explanation:"Test Pyramid (Mike Cohn): много unit, меньше integration, минимум E2E. E2E тесты: медленные, хрупкие, дорогие в поддержке. Инвертированная пирамида (Ice Cream Cone) — анти-паттерн."
},
{
id:30, category:"Team Management",
question:"Senior QA в команде демотивирован рутинными задачами и думает об уходе. Как QA Lead должен удержать его?",
options:["Поднять зарплату — единственный рабочий инструмент","Игнорировать — если уйдёт, найдём замену","Обсудить карьерные цели, делегировать ownership над автоматизацией/архитектурой, вовлечь в менторинг junior'ов и технические решения","Перевести его на другой проект внутри компании"],
correctAnswer:2,
explanation:"Retention через growth: Senior QA нужны ownership, impact, и рост. Делегирование ownership над automation strategy, code review QA тестов, менторинг, участие в architectural decisions — это то, что удерживает senior специалистов."
},
// ── 31–60: Новые вопросы ──
{
id:31, category:"Test Strategy",
question:"Команде нужно написать Test Strategy для нового продукта. Что является ключевым отличием Test Strategy от Test Plan?",
options:["Test Strategy описывает конкретные тест-кейсы, Test Plan — общий подход","Test Strategy определяет методы, подходы и принципы тестирования (как тестировать); Test Plan описывает конкретные активности, сроки, ресурсы (что, когда, кто)","Test Strategy создаётся QA, Test Plan — менеджментом","Это синонимы с разными названиями"],
correctAnswer:1,
explanation:"Test Strategy — стратегический документ: подходы, методы, типы тестирования, инструменты, критерии входа/выхода. Test Plan — тактический: конкретные задачи, сроки, ответственные, ресурсы. Strategy может быть общей для всей компании, Plan — для конкретного проекта/релиза."
},
{
id:32, category:"QA Process Maturity",
question:"Согласно TMMi, на каком уровне зрелости находится команда, если QA-процессы определены, задокументированы и повторяемы, но метрики системно не собираются?",
options:["Уровень 1 — Initial (хаотичный)","Уровень 2 — Managed (управляемый)","Уровень 3 — Defined (определённый)","Уровень 4 — Measured (измеряемый)"],
correctAnswer:2,
explanation:"TMMi Level 3 (Defined): процессы стандартизированы, задокументированы и применяются во всей организации, но систематическое измерение и управление через метрики — это уже Level 4. Level 2 — процессы управляемы, но не стандартизированы."
},
{
id:33, category:"Test Planning",
question:"QA Lead готовит Test Plan для релиза новой платёжной фичи. Какой раздел наиболее критичен для этого типа функционала?",
options:["Риски и митигация: определение что может пойти не так, план на случай сбоев","Список тест-кейсов","Расписание встреч команды","История предыдущих тест-планов"],
correctAnswer:0,
explanation:"Для платёжного функционала риски критически важны: потеря денег, двойные списания, отказы транзакций. Test Plan должен содержать risk register с mitigation strategies, fallback plans и чёткими stop criteria. Это не менее важно, чем список тест-кейсов."
},
{
id:34, category:"Release Management",
question:"Что такое Feature Flag (Feature Toggle) и как он помогает QA при релизах?",
options:["Переключатель для включения debug-режима","Механизм включения/отключения функционала без деплоя, позволяющий тестировать фичу в проде без её активации для всех пользователей","Флаг в баг-трекере для маркировки критических дефектов","Настройка CI/CD для ускорения сборки"],
correctAnswer:1,
explanation:"Feature Flags позволяют деплоить код без включения функционала. QA может тестировать фичу в продакшн-среде на ограниченной аудитории (canary), откатить без деплоя при проблемах, и разделить deployment от release."
},
{
id:35, category:"Risk-Based Testing",
question:"Как правильно приоритизировать тест-кейсы при risk-based подходе?",
options:["По сложности написания — начинать с простых тест-кейсов","По алфавиту категорий","По вероятности отказа × бизнес-impact: высокий risk = высший приоритет выполнения","Сначала тестировать новый функционал, потом старый"],
correctAnswer:2,
explanation:"Risk-based prioritization = Likelihood of failure × Business impact. Тесты с высоким risk-score выполняются первыми. Это гарантирует, что при нехватке времени критические сценарии уже покрыты, а не только «удобные» для написания."
},
{
id:36, category:"Regression Optimization",
question:"Команда хочет сократить время регрессионного тестирования с 8 часов до 2 часов. Какой подход наиболее эффективен?",
options:["Удалить половину тест-кейсов случайно","Внедрить test impact analysis: запускать только тесты, связанные с изменёнными компонентами кода","Запускать тесты параллельно без анализа покрытия","Проводить регрессию только раз в месяц"],
correctAnswer:1,
explanation:"Test Impact Analysis (TIA) анализирует, какие тесты покрывают изменённый код, и запускает только их. Инструменты: Pytest-cov, Jest --changedSince, Gradle Test Retry. Сокращение 80% суит при 95% релевантности — реальный результат."
},
{
id:37, category:"Automation Strategy",
question:"Команда хочет внедрить автоматизацию. Какой первый шаг правильный согласно automation strategy?",
options:["Определить ROI: какие тесты повторяются чаще всего, какова стоимость ручного выполнения vs создания и поддержки автотестов","Купить дорогой инструмент автоматизации","Сразу покрыть всё автотестами","Нанять automation engineer и дать ему полную свободу"],
correctAnswer:0,
explanation:"Automation strategy начинается с ROI analysis. Цель: определить highest-value candidates — тесты, которые часто запускаются, стабильны в поведении, имеют предсказуемый expected result и дорого обходятся при ручном выполнении."
},
{
id:38, category:"Flaky Tests",
question:"Как отличить flaky test от реального бага в коде приложения?",
options:["Запустить тест несколько раз: flaky тест показывает непостоянные результаты (pass/fail) при одинаковых условиях; реальный баг воспроизводится стабильно","Flaky тест всегда падает — реальный баг только иногда","Flaky тест всегда связан с UI, реальный баг — с backend","Flaky тест виден только в CI, реальный баг — везде"],
correctAnswer:0,
explanation:"Признак flaky test — non-determinism: тест иногда проходит, иногда падает при одинаковых условиях. Причины: race conditions, timing issues, внешние зависимости (БД, сеть), shared state. Реальный баг воспроизводится стабильно при тех же условиях."
},
{
id:39, category:"Test Data Management",
question:"QA-команда работает с prod-like данными для тестирования. Какой главный риск и как его митигировать?",
options:["Данные слишком большие и тесты медленно работают — использовать минимальный датасет","Prod данные всегда устаревшие — обновлять их ежедневно","Разработчики не должны видеть prod данные — ограничить доступ только QA","Реальные персональные данные (PII) нарушают GDPR/PDPA — применять data masking/anonymization перед использованием в тест-среде"],
correctAnswer:3,
explanation:"Использование реальных PII в тест-среде — compliance-нарушение (GDPR, PCI DSS). Решения: data masking (замена реальных данных на синтетические), anonymization, synthetic data generation. Тест-среда никогда не должна содержать реальные имена, email, номера карт."
},
{
id:40, category:"API Testing",
question:"Что такое Idempotency в REST API и как это важно для QA?",
options:["API всегда возвращает одинаковый ответ, независимо от входных данных","Повторный вызов с теми же параметрами даёт тот же результат без побочных эффектов — GET/PUT/DELETE идемпотентны, POST обычно нет","API не имеет состояния между запросами","Скорость ответа API не зависит от нагрузки"],
correctAnswer:1,
explanation:"Idempotency: повторный вызов не создаёт дополнительных побочных эффектов. QA должен тестировать: повторный DELETE возвращает 404, повторный PUT — 200 с теми же данными, повторный POST без idempotency-key создаёт дубль. Критично для платёжных систем."
},
{
id:41, category:"Contract Testing",
question:"Что такое Consumer-Driven Contract Testing и какую проблему он решает?",
options:["Тестирование, при котором заказчик (бизнес) пишет тест-кейсы","Тест на соответствие API документации Swagger","Подход, при котором consumer сервис определяет контракт (формат запросов/ответов), а provider верифицирует соответствие — решает проблему интеграционных breakages без E2E тестов","Нагрузочное тестирование API между сервисами"],
correctAnswer:2,
explanation:"Consumer-Driven Contract Testing (Pact) решает проблему сломанных интеграций в микросервисах без тяжёлых E2E тестов. Consumer публикует контракт, Provider запускает верификацию при каждом деплое. Ошибки интеграции обнаруживаются рано и дёшево."
},
{
id:42, category:"Performance Testing",
question:"В чём разница между Load Testing и Stress Testing?",
options:["Load Testing — ручное, Stress Testing — автоматическое","Это одинаковые типы тестирования с разными названиями","Load Testing проверяет поведение системы при ожидаемой нагрузке; Stress Testing — при нагрузке выше максимальной, чтобы найти точку отказа","Load Testing для API, Stress Testing для UI"],
correctAnswer:2,
explanation:"Load Testing: поведение при ожидаемой (нормальной/пиковой) нагрузке. Stress Testing: нагрузка сверх максимума до отказа — где именно ломается система? Есть также Spike Testing (внезапный всплеск), Soak Testing (длительная нагрузка), Volume Testing."
},
{
id:43, category:"Security Testing",
question:"Какая уязвимость OWASP Top 10 наиболее часто встречается и как QA может её проверить?",
options:["Buffer Overflow — проверить через ввод очень длинных строк","SQL Injection — попытаться ввести SQL-код в поля ввода, проверить параметризованные запросы и валидацию input","CSRF — проверить размер файлов cookie","XXE — протестировать скорость загрузки XML"],
correctAnswer:1,
explanation:"SQL Injection — одна из наиболее распространённых уязвимостей. QA: ввести ' OR '1'='1 в поля, передать payload через параметры URL. Проверить что backend использует parameterized queries, не string concatenation. Также проверить отсутствие детального error output."
},
{
id:44, category:"Accessibility Testing",
question:"Компания разрабатывает государственный портал. Какой уровень WCAG соответствия является минимально обязательным?",
options:["WCAG 1.0 Level A","WCAG 2.1 Level AAA — максимальный уровень","Accessibility стандарты применяются только для приложений для людей с инвалидностью","WCAG 2.1 Level AA — международный стандарт для большинства государственных и корпоративных сайтов"],
correctAnswer:3,
explanation:"WCAG 2.1 Level AA — минимальный стандарт для государственных порталов в большинстве юрисдикций. Включает: alt-тексты, keyboard navigation, достаточный contrast ratio, ARIA-атрибуты. QA должен проверять с screen readers (NVDA, VoiceOver) и автоматизированными инструментами (axe, Lighthouse)."
},
{
id:45, category:"Mobile Testing",
question:"Какой аспект является уникальным для мобильного тестирования и не присутствует в web-тестировании?",
options:["Функциональное тестирование","Прерывания: входящий звонок, push-уведомление, низкий заряд батареи, смена сети (3G→4G→WiFi) во время работы приложения","Тестирование форм","Проверка цветовой схемы"],
correctAnswer:1,
explanation:"Interruption testing — уникальный аспект мобильного тестирования: как приложение ведёт себя при входящем звонке, SMS, push-уведомлении, смене сети, низком заряде, блокировке экрана. Также: gestures (swipe, pinch), разные OS версии/производители, разрешения экранов."
},
{
id:46, category:"CI/CD Quality Gates",
question:"Что такое Quality Gate в контексте CI/CD pipeline и почему он важен?",
options:["Автоматизированный набор критериев (coverage threshold, test pass rate, code quality metrics), блокирующий деплой если они не выполнены","Ручная проверка QA перед каждым деплоем","Встреча команды перед релизом","Система управления задачами для QA"],
correctAnswer:0,
explanation:"Quality Gate — автоматический барьер в pipeline. Примеры критериев SonarQube: code coverage ≥ 80%, нет Critical bugs, test pass rate 100%, security vulnerabilities = 0. Pipeline падает и деплой блокируется если gate не пройден. Это shift-left quality enforcement."
},
{
id:47, category:"Defect Triage",
question:"Команда накопила 150 открытых дефектов. QA Lead организует triage-сессию. Что является главной целью?",
options:["Закрыть как можно больше дефектов за один раз","Коллективно переоценить приоритеты, severity, назначить ответственных и решить что войдёт в ближайший спринт на основе risk и impact","Назначить все дефекты разработчикам без обсуждения","Удалить устаревшие дефекты"],
correctAnswer:1,
explanation:"Defect Triage — это командный процесс приоритизации, не одиночное решение QA. Цель: обеспечить shared understanding приоритетов между QA, Dev и PO, выровнять ожидания и направить ресурсы на максимально важные дефекты для текущего спринта или релиза."
},
{
id:48, category:"Root Cause Analysis",
question:"QA Lead проводит Root Cause Analysis (5 Whys) после production bug. Что предполагает метод?",
options:["Задать вопрос 'почему' пять раз подряд разным членам команды","Провести пять встреч по 1 часу для анализа инцидента","Написать 5 версий отчёта и выбрать лучшую","Последовательно спрашивать 'почему это произошло?' к каждому ответу, пока не будет найдена системная причина, а не симптом"],
correctAnswer:3,
explanation:"5 Whys: каждый ответ становится следующим 'почему'. Пример: Баг в проде → почему? Не было теста → почему? Не было в acceptance criteria → почему? QA не участвовал в refinement → почему? Нет процесса. Root cause: отсутствие Shift-Left. Решение системное, не точечное."
},
{
id:49, category:"Incident Postmortem",
question:"Что означает 'blameless postmortem' и почему это важно для QA культуры?",
options:["Постмортем без участия виновных сотрудников","Анализ инцидента без обвинений конкретных людей, фокус на системных причинах и улучшении процессов — создаёт психологическую безопасность для открытого обсуждения ошибок","Постмортем проводится только если нет виновных","Документ без подписей участников для анонимности"],
correctAnswer:1,
explanation:"Blameless postmortem (Google SRE practice): люди не совершают ошибок из-за некомпетентности — системы создают условия для ошибок. Когда нет наказания, команда честно обсуждает что пошло не так, и fixes становятся системными. Blame culture → сокрытие проблем."
},
{
id:50, category:"Production Quality Metrics",
question:"Что из перечисленного является DORA-метрикой для оценки качества DevOps/QA процессов?",
options:["Число тест-кейсов в test suite","Количество автотестов в CI","Change Failure Rate (процент деплоев, приведших к инциденту) — одна из 4 ключевых DORA-метрик","Среднее время ответа API"],
correctAnswer:2,
explanation:"4 DORA метрики: 1) Deployment Frequency, 2) Lead Time for Changes, 3) Change Failure Rate (CFR), 4) Time to Restore (MTTR). CFR — прямой индикатор качества QA-процесса. High performers: CFR < 15%. Low performers: > 45%. QA влияет напрямую на CFR."
},
{
id:51, category:"Team Workload Planning",
question:"QA Lead планирует спринт. Команда: 3 QA, каждый 8 часов в день, 10 рабочих дней. Какова реальная доступная capacity?",
options:["240 часов (3 × 8 × 10)","Около 180–200 часов: нужно учесть meetings, reviews, onboarding, непредвиденные задачи — обычно 75–85% от теоретического максимума","120 часов — берём 50% на непредвиденное","Capacity не нужно считать — тестируем сколько успеем"],
correctAnswer:1,
explanation:"Теоретическая capacity: 240 ч. Реальная: 75–85% из-за ceremonies (stand-up, planning, retro), bug triage, code reviews, documentation, slack time. Для sprint planning использовать 180–200 ч — это industry best practice. Пообещать 240 ч означает срывать обязательства."
},
{
id:52, category:"Mentoring QA Engineers",
question:"Junior QA всё время просит помощи и не пытается решить проблему самостоятельно. Как QA Lead должен изменить подход?",
options:["Перестать отвечать на вопросы — пусть сам разбирается","Написать документ с ответами на все возможные вопросы","Применить метод 'вопрос на вопрос': вместо готового ответа спрашивать 'а что ты уже попробовал? Какие варианты видишь?' — развивает самостоятельность","Перевести junior в другую команду"],
correctAnswer:2,
explanation:"Socratic method в менторинге: направлять вопросами, а не давать ответы. 'Что ты уже попробовал? Какой documentation читал? Что говорит логика ошибки?' — развивает critical thinking. Если всегда давать ответы — создаёшь зависимость, а не профессионала."
},
{
id:53, category:"Hiring QA Engineers",
question:"На собеседовании кандидат на Middle QA Engineer не может объяснить разницу между Smoke и Regression testing. Как это оценивает QA Lead?",
options:["Это нормально для Middle — можно обучить","Это базовое знание для Middle, сигнал о завышенном уровне или пробелах в опыте — нужно глубже проверить другие базовые области","Немедленно отказать без дополнительных вопросов","Предложить позицию Junior вместо Middle"],
correctAnswer:1,
explanation:"Middle QA Engineer должен уверенно знать базовые типы тестирования. Незнание этого — сигнал к дополнительным проверкам: понимание test lifecycle, bug reporting, test design techniques. На основе одного вопроса не отказывают, но это влияет на оценку уровня кандидата."
},
{
id:54, category:"Conflict Resolution",
question:"QA и разработчик не могут договориться о severity дефекта. QA считает Major, разработчик — Minor. Как решить конфликт?",
options:["Обратиться к описанию severity в командном соглашении (definition), если соглашения нет — привлечь PO для оценки бизнес-impact","QA всегда прав в вопросах severity","Разработчик прав — он знает код лучше","Поставить Medium компромиссно"],
correctAnswer:0,
explanation:"Severity — не субъективное мнение, а оценка по заранее определённым критериям (impact на пользователя, частота воспроизведения, workaround). Если нет definition — это системная проблема. Компромисс ('поставим Medium') без логики — неверный подход."
},
{
id:55, category:"Communication with Developers",
question:"Как QA Lead должен представить разработчику дефект, чтобы минимизировать защитную реакцию и ускорить исправление?",
options:["Написать в общем канале чтобы создать давление","Указать что разработчик допустил ошибку","Отправить баг без объяснений и ждать реакции","Представить как 'я нашёл поведение, которое отличается от ожидаемого' + факты + шаги воспроизведения + expected vs actual, без оценочных суждений о коде"],
correctAnswer:3,
explanation:"Нейтральный, фактологический язык снижает защитную реакцию. 'Я нашёл' вместо 'ты сломал'. Expected vs Actual без оценок. Полные шаги воспроизведения. Разработчик воспринимает это как информацию для исправления, а не атаку — скорость фикса выше."
},
{
id:56, category:"Communication with Product Owners",
question:"PO не понимает, почему технический долг влияет на качество. Как QA Lead объяснит это?",
options:["Использовать технические термины — PO должен разбираться","Перевести в бизнес-язык: 'технический долг — это как кредит: получили функционал быстро, но платим процентами в виде медленной разработки, большего числа багов и дороже исправлений'","Написать длинный технический документ","Передать объяснение разработчику"],
correctAnswer:1,
explanation:"QA Lead должен уметь переводить технические концепции в бизнес-язык. Аналогия с кредитом понятна нетехническому PO. Добавить конкретику: 'из-за tech debt этот sprint занял X часов вместо Y' — это убедительнее любой технической аргументации."
},
{
id:57, category:"Stakeholder Reporting",
question:"QA Lead готовит еженедельный отчёт для менеджмента. Какие метрики наиболее ценны для нетехнической аудитории?",
options:["Release readiness %, критические открытые дефекты, test pass rate, defect leakage за период — всё в контексте бизнес-риска","Число тест-кейсов в Jira","Детальный список всех найденных дефектов с ID","Техническое покрытие кода в процентах"],
correctAnswer:0,
explanation:"Менеджмент думает бизнес-категориями: готов ли продукт к релизу? Какие риски? Что может сломаться у клиентов? Release Readiness % — прямой ответ. Critical bugs count — понятный риск-индикатор. Code coverage % для менеджмента — абстракция без смысла."
},
{
id:58, category:"Documentation Standards",
question:"Что должен включать раздел 'Scope of Testing' в Test Strategy документе?",
options:["Список всех тест-кейсов","Расписание тестирования по дням","Чёткое определение что тестируется (in scope) и что намеренно не тестируется (out of scope) с обоснованием — предотвращает недопонимание ожиданий","Имена всех участников тестирования"],
correctAnswer:2,
explanation:"Scope определяет границы ответственности QA. Out of Scope критически важен: если QA не тестирует 3rd party интеграции, legacy модули или performance — это должно быть явно зафиксировано с обоснованием. Это защищает команду от implicit expectations и scope creep."
},
{
id:59, category:"Knowledge Sharing",
question:"QA Lead уходит, и в команде никто не знает все процессы. Как правильно организовать knowledge transfer?",
options:["Написать один большой документ за последнюю неделю","Системно документировать процессы в ходе работы (runbooks, wikis), проводить регулярные tech talks, практиковать pair testing — bus factor должен быть >1 всегда","Обучить только одного backup-сотрудника","Нанять нового QA Lead с готовыми знаниями"],
correctAnswer:1,
explanation:"Bus factor = 1 (только один человек знает процесс) — критический риск. Knowledge sharing должен быть continuous: runbooks, wikis, regular knowledge sessions, pair testing. Написать документ за последнюю неделю — слишком поздно и неполно."
},
{
id:60, category:"Continuous Improvement",
question:"QA Lead хочет внедрить культуру continuous improvement в команде. Какой практический подход наиболее эффективен?",
options:["Проводить ежеквартальные аудиты процессов","Читать команде лекции о качестве ежемесячно","Внедрить регулярные retrospectives с action items, метрики для отслеживания прогресса, эксперименты с small improvements (Kaizen) и фиксацию результатов изменений","Копировать процессы успешных компаний без адаптации"],
correctAnswer:2,
explanation:"Kaizen (маленькие постоянные улучшения) + retrospectives с measurable action items — основа CI культуры. Ключевое: изменения должны быть измеримы (было X, стало Y), иначе непонятно работает ли улучшение. Лекции без практики и метрик — не улучшение, а иллюзия."
}
];
// ═══════════════════════════════════════════════════════════════════════
// STATE
// ═══════════════════════════════════════════════════════════════════════
let state = {
deck: [], // {q, shuffledOptions, correctIdx}
currentIdx: 0,
answers: [], // {questionId, category, question, chosen, correct, explanation, isCorrect, isTimeout, timeLeft, timeSpent}
timerInterval: null,
totalTimerInterval: null,
timeLeft: 30, // per-question seconds remaining
totalTimeLeft: 900, // total test seconds remaining
answered: false,
startTime: null,
candidateName: '',
normalizedName: '',
isActive: false, // true while quiz is running
finishReason: 'normal', // 'normal' | 'total_timeout'
settings: { numQuestions: 30, timePerQuestion: 30, totalTimeMinutes: 15 },
};
// ═══════════════════════════════════════════════════════════════════════
// UTILITIES
// ═══════════════════════════════════════════════════════════════════════
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function formatTime(totalSeconds) {
const m = Math.floor(totalSeconds / 60);
const s = totalSeconds % 60;
return `${m}:${s.toString().padStart(2, '0')}`;
}
function normalizeName(name) {
return name.trim().toLowerCase().replace(/\s+/g, ' ');
}
function totalElapsed() {
return Math.floor((Date.now() - state.startTime) / 1000);
}
function todayStr() {
return new Date().toLocaleString('ru-RU', {
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit'
});
}
// ═══════════════════════════════════════════════════════════════════════
// CANDIDATE STORAGE
// ═══════════════════════════════════════════════════════════════════════
function getCandidates() {
try {
return JSON.parse(localStorage.getItem(SK_CANDIDATES) || '{}');
} catch { return {}; }
}
function saveCandidates(data) {
localStorage.setItem(SK_CANDIDATES, JSON.stringify(data));
}
function getCandidateByName(normalizedName) {
return getCandidates()[normalizedName] || null;
}
// ── Check candidate name on input change ──
function onNameInput() {
const raw = document.getElementById('candidateName').value;
const norm = normalizeName(raw);
const btnStart = document.getElementById('btnStart');
const msgBlocked = document.getElementById('msgBlocked');
const msgInterrupted = document.getElementById('msgInterrupted');
msgBlocked.classList.remove('visible');
msgInterrupted.classList.remove('visible');
document.getElementById('candidateName').classList.remove('error-border');
btnStart.disabled = false;
if (!norm) {
btnStart.disabled = true;
return;
}
const candidate = getCandidateByName(norm);
if (!candidate) return; // Not found — OK to proceed
if (candidate.status === 'completed') {
const date = candidate.completedAt ? new Date(candidate.completedAt).toLocaleString('ru-RU') : '—';