-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1819 lines (1590 loc) · 149 KB
/
index.html
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="en-US"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1.0"><meta name="apple-mobile-web-app-capable" content="yes"><meta http-equiv="X-UA-Compatible" content="ie=edge"><meta property="og:type" content="website"><meta name="twitter:card" content="summary"><style>.bespoke-marp-note,.bespoke-marp-osc,.bespoke-progress-parent{display:none;transition:none}@media screen{body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border:0;color:inherit;cursor:pointer;font-size:inherit;opacity:.8;outline:none;padding:0;transition:opacity .2s linear;-webkit-tap-highlight-color:transparent}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button:disabled,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button:disabled,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button:disabled{cursor:not-allowed;opacity:.15!important}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button:hover,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button:hover,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button:hover{opacity:1}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button:hover:active,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button:hover:active,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button:hover:active{opacity:.6}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button:hover:not(:disabled),body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button:hover:not(:disabled),body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button:hover:not(:disabled){transition:none}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=prev],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=prev],body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button.bespoke-marp-presenter-info-page-prev{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI1IiBkPSJNNjggOTBMMjggNTBsNDAtNDAiLz48L3N2Zz4=") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=next],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=next],body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button.bespoke-marp-presenter-info-page-next{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI1IiBkPSJNMzIgOTBsNDAtNDAtNDAtNDAiLz48L3N2Zz4=") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=fullscreen],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=fullscreen]{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48ZGVmcz48c3R5bGU+LmF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6NXB4fTwvc3R5bGU+PC9kZWZzPjxyZWN0IGNsYXNzPSJhIiB4PSIxMCIgeT0iMjAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI2MCIgcng9IjUuNjciLz48cGF0aCBjbGFzcz0iYSIgZD0iTTQwIDcwSDIwVjUwbTIwIDBMMjAgNzBtNDAtNDBoMjB2MjBtLTIwIDBsMjAtMjAiLz48L3N2Zz4=") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button.exit[data-bespoke-marp-osc=fullscreen],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button.exit[data-bespoke-marp-osc=fullscreen]{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48ZGVmcz48c3R5bGU+LmF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6NXB4fTwvc3R5bGU+PC9kZWZzPjxyZWN0IGNsYXNzPSJhIiB4PSIxMCIgeT0iMjAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI2MCIgcng9IjUuNjciLz48cGF0aCBjbGFzcz0iYSIgZD0iTTIwIDUwaDIwdjIwbS0yMCAwbDIwLTIwbTQwIDBINjBWMzBtMjAgMEw2MCA1MCIvPjwvc3ZnPg==")}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=presenter],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=presenter]{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48ZGVmcz48c3R5bGU+LmF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS13aWR0aDo1cHh9PC9zdHlsZT48L2RlZnM+PHBhdGggY2xhc3M9ImEiIGQ9Ik0yMCA2MGgtNWE1IDUgMCAwMS01LTVWMjBhNSA1IDAgMDE1LTVoNjBhNSA1IDAgMDE1IDV2NU0zMCA4NWg2MCIvPjxyZWN0IHg9IjMwIiB5PSIzNSIgd2lkdGg9IjYwIiBoZWlnaHQ9IjQwIiByeD0iNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIi8+PHJlY3QgY2xhc3M9ImEiIHg9IjMwIiB5PSIzNSIgd2lkdGg9IjYwIiBoZWlnaHQ9IjQwIiByeD0iNSIvPjxwYXRoIGNsYXNzPSJhIiBkPSJNNDAgNTBoNDBNNDAgNjBoMzAiLz48L3N2Zz4=") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}body,html{height:100%;margin:0}body{background:#000;overflow:hidden}svg.bespoke-marp-slide{opacity:0;pointer-events:none;z-index:-1}svg.bespoke-marp-slide.bespoke-marp-active{opacity:1;pointer-events:auto;z-index:0}svg.bespoke-marp-slide.bespoke-marp-active.bespoke-marp-active-ready *{-webkit-animation-name:__bespoke_marp__!important;animation-name:__bespoke_marp__!important}svg.bespoke-marp-slide[data-bespoke-marp-load=hideable]{display:none}svg.bespoke-marp-slide[data-bespoke-marp-load=hideable].bespoke-marp-active{display:block}[data-bespoke-marp-fragment=inactive]{visibility:hidden}body[data-bespoke-view=""] .bespoke-marp-parent,body[data-bespoke-view=next] .bespoke-marp-parent{bottom:0;left:0;position:absolute;right:0;top:0}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc{background:rgba(0,0,0,.65);border-radius:7px;bottom:50px;color:#fff;display:block;font-family:Helvetica,Arial,sans-serif;font-size:16px;left:50%;line-height:0;opacity:1;padding:12px;position:absolute;touch-action:manipulation;transform:translateX(-50%);transition:opacity .2s linear;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap;z-index:1;will-change:transform}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>*,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>*{margin-left:6px}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>:first-child,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>:first-child{margin-left:0}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>span,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>span{opacity:.8}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>span[data-bespoke-marp-osc=page],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>span[data-bespoke-marp-osc=page]{display:inline-block;min-width:140px;text-align:center}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=fullscreen],body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=next],body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=presenter],body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=prev],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=fullscreen],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=next],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=presenter],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=prev]{height:32px;line-height:32px;width:32px}body[data-bespoke-view=""] .bespoke-marp-parent.bespoke-marp-inactive,body[data-bespoke-view=next] .bespoke-marp-parent.bespoke-marp-inactive{cursor:none}body[data-bespoke-view=""] .bespoke-marp-parent.bespoke-marp-inactive>.bespoke-marp-osc,body[data-bespoke-view=next] .bespoke-marp-parent.bespoke-marp-inactive>.bespoke-marp-osc{opacity:0;pointer-events:none}body[data-bespoke-view=""] svg.bespoke-marp-slide,body[data-bespoke-view=next] svg.bespoke-marp-slide{height:100%;left:0;position:absolute;top:0;width:100%}body[data-bespoke-view=""] .bespoke-progress-parent{background:#222;display:flex;height:5px;width:100%}body[data-bespoke-view=""] .bespoke-progress-parent+.bespoke-marp-parent{top:5px}body[data-bespoke-view=""] .bespoke-progress-parent .bespoke-progress-bar{flex:0 0 0;background:#0288d1;transition:flex-basis .2s cubic-bezier(0,1,1,1)}body[data-bespoke-view=next]{background:transparent}body[data-bespoke-view=presenter]{background:#161616}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container{font-family:Helvetica,Arial,sans-serif;height:100%;left:0;position:absolute;top:0;width:100%;display:grid;grid-template-columns:2fr 1fr;grid-template-rows:minmax(140px,1fr) 2fr 3em;grid-template-areas:"current next" "current note" "info note"}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-parent{grid-area:current;position:relative;overflow:hidden}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-parent svg.bespoke-marp-slide{height:calc(100% - 40px);left:20px;position:absolute;pointer-events:none;top:20px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:calc(100% - 40px)}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-parent svg.bespoke-marp-slide.bespoke-marp-active{-webkit-filter:drop-shadow(0 3px 10px rgba(0,0,0,.5));filter:drop-shadow(0 3px 10px rgba(0,0,0,.5))}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-next-container{background:#222;cursor:pointer;display:none;grid-area:next;overflow:hidden;position:relative}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-next-container.active{display:block}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-next-container iframe.bespoke-marp-presenter-next{background:transparent;border:0;display:block;-webkit-filter:drop-shadow(0 3px 10px rgba(0,0,0,.5));filter:drop-shadow(0 3px 10px rgba(0,0,0,.5));height:calc(100% - 40px);left:20px;position:absolute;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;top:20px;width:calc(100% - 40px)}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container{background:#222;color:#eee;grid-area:note}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note{margin:20px;width:calc(100% - 40px);height:calc(100% - 40px);box-sizing:border-box;font-size:1.1em;overflow:auto;padding-right:3px;white-space:pre-wrap;word-wrap:break-word;scrollbar-width:thin;scrollbar-color:hsla(0,0%,93.3%,.5) transparent}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note::-webkit-scrollbar{width:6px}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note::-webkit-scrollbar-track{background:transparent}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note::-webkit-scrollbar-thumb{background:hsla(0,0%,93.3%,.5);border-radius:6px}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note:empty{pointer-events:none}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note.active{display:block}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note p:first-child{margin-top:0}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note p:last-child{margin-bottom:0}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container{align-items:center;box-sizing:border-box;color:#eee;display:flex;flex-wrap:nowrap;grid-area:info;justify-content:center;padding:0 10px}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-page,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-time,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-timer{display:block;box-sizing:border-box;padding:0 10px;white-space:nowrap;width:100%}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button{height:1.5em;line-height:1.5em;width:1.5em}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-page{order:2;text-align:center}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-page .bespoke-marp-presenter-info-page-text{display:inline-block;min-width:120px;text-align:center}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-time{color:#999;order:1;text-align:left}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-timer{color:#999;order:3;text-align:right}}@media print{.bespoke-marp-presenter-info-container,.bespoke-marp-presenter-next-container,.bespoke-marp-presenter-note-container{display:none}}</style><style>@charset "UTF-8";@import url("https://fonts.googleapis.com/css?family=Lato:400,900|Roboto+Mono:400,700&display=swap");div#p>svg>foreignObject>section{width:1280px;height:720px;box-sizing:border-box;overflow:hidden;position:relative;scroll-snap-align:center center}div#p>svg>foreignObject>section:after{bottom:0;content:attr(data-marpit-pagination);padding:inherit;pointer-events:none;position:absolute;right:0}div#p>svg>foreignObject>section:not([data-marpit-pagination]):after{display:none}/* Normalization */div#p>svg>foreignObject>section h1{font-size:2em;margin:0.67em 0}div#p>svg>foreignObject>section video::-webkit-media-controls{will-change:transform}@page{size:1280px 720px;margin:0}@media print{body,html{background-color:#fff;margin:0;page-break-inside:avoid;break-inside:avoid-page}div#p>svg>foreignObject>section{page-break-before:always;break-before:page}div#p>svg>foreignObject>section,div#p>svg>foreignObject>section *{-webkit-print-color-adjust:exact!important;animation-delay:0s!important;animation-duration:0s!important;color-adjust:exact!important;transition:none!important}div#p>svg[data-marpit-svg]{display:block;height:100vh;width:100vw}}
/*!
* Marp / Marpit Gaia theme.
*
* @theme gaia
* @author Yuki Hattori
*
* @auto-scaling true
* @size 4:3 960px 720px
*/div#p>svg>foreignObject>section .hljs{display:block;overflow-x:auto;padding:.5em;background:#000;color:#f8f8f8}div#p>svg>foreignObject>section .hljs-comment,div#p>svg>foreignObject>section .hljs-quote{color:#aeaeae;font-style:italic}div#p>svg>foreignObject>section .hljs-keyword,div#p>svg>foreignObject>section .hljs-selector-tag,div#p>svg>foreignObject>section .hljs-type{color:#e28964}div#p>svg>foreignObject>section .hljs-string{color:#65b042}div#p>svg>foreignObject>section .hljs-subst{color:#daefa3}div#p>svg>foreignObject>section .hljs-link,div#p>svg>foreignObject>section .hljs-regexp{color:#e9c062}div#p>svg>foreignObject>section .hljs-name,div#p>svg>foreignObject>section .hljs-section,div#p>svg>foreignObject>section .hljs-tag,div#p>svg>foreignObject>section .hljs-title{color:#89bdff}div#p>svg>foreignObject>section .hljs-class .hljs-title,div#p>svg>foreignObject>section .hljs-doctag{text-decoration:underline}div#p>svg>foreignObject>section .hljs-bullet,div#p>svg>foreignObject>section .hljs-number,div#p>svg>foreignObject>section .hljs-symbol{color:#3387cc}div#p>svg>foreignObject>section .hljs-params,div#p>svg>foreignObject>section .hljs-template-variable,div#p>svg>foreignObject>section .hljs-variable{color:#3e87e3}div#p>svg>foreignObject>section .hljs-attribute{color:#cda869}div#p>svg>foreignObject>section .hljs-meta{color:#8996a8}div#p>svg>foreignObject>section .hljs-formula{background-color:#0e2231;color:#f8f8f8;font-style:italic}div#p>svg>foreignObject>section .hljs-addition{background-color:#253b22;color:#f8f8f8}div#p>svg>foreignObject>section .hljs-deletion{background-color:#420e09;color:#f8f8f8}div#p>svg>foreignObject>section .hljs-selector-class{color:#9b703f}div#p>svg>foreignObject>section .hljs-selector-id{color:#8b98ab}div#p>svg>foreignObject>section .hljs-emphasis{font-style:italic}div#p>svg>foreignObject>section .hljs-strong{font-weight:700}div#p>svg>foreignObject>section svg[data-marp-fitting=svg]{max-height:580px}div#p>svg>foreignObject>section h1,div#p>svg>foreignObject>section h2,div#p>svg>foreignObject>section h3,div#p>svg>foreignObject>section h4,div#p>svg>foreignObject>section h5,div#p>svg>foreignObject>section h6{margin:.5em 0 0}div#p>svg>foreignObject>section h1 strong,div#p>svg>foreignObject>section h2 strong,div#p>svg>foreignObject>section h3 strong,div#p>svg>foreignObject>section h4 strong,div#p>svg>foreignObject>section h5 strong,div#p>svg>foreignObject>section h6 strong{font-weight:inherit}div#p>svg>foreignObject>section h1{font-size:1.8em}div#p>svg>foreignObject>section h2{font-size:1.5em}div#p>svg>foreignObject>section h3{font-size:1.3em}div#p>svg>foreignObject>section h4{font-size:1.1em}div#p>svg>foreignObject>section h5{font-size:1em}div#p>svg>foreignObject>section h6{font-size:.9em}div#p>svg>foreignObject>section blockquote,div#p>svg>foreignObject>section p{margin:1em 0 0}div#p>svg>foreignObject>section ol>li,div#p>svg>foreignObject>section ul>li{margin:.3em 0 0}div#p>svg>foreignObject>section ol>li>p,div#p>svg>foreignObject>section ul>li>p{margin:.6em 0 0}div#p>svg>foreignObject>section code{display:inline-block;font-family:Roboto Mono,monospace;font-size:.8em;letter-spacing:0;margin:-.1em .15em;padding:.1em .2em;vertical-align:baseline}div#p>svg>foreignObject>section pre{display:block;margin:1em 0 0;min-height:1em;overflow:visible}div#p>svg>foreignObject>section pre code{box-sizing:border-box;margin:0;min-width:100%;padding:.5em;font-size:.7em}div#p>svg>foreignObject>section pre code svg[data-marp-fitting=svg]{max-height:calc(580px - 1em)}div#p>svg>foreignObject>section blockquote{margin:1em 0 0;padding:0 1em;position:relative}div#p>svg>foreignObject>section blockquote:after,div#p>svg>foreignObject>section blockquote:before{content:"“";display:block;font-family:Times New Roman,serif;font-weight:700;position:absolute}div#p>svg>foreignObject>section blockquote:before{top:0;left:0}div#p>svg>foreignObject>section blockquote:after{right:0;bottom:0;transform:rotate(180deg)}div#p>svg>foreignObject>section blockquote>:first-child{margin-top:0}div#p>svg>foreignObject>section mark{background:transparent}div#p>svg>foreignObject>section table{border-spacing:0;border-collapse:collapse;margin:1em 0 0}div#p>svg>foreignObject>section table td,div#p>svg>foreignObject>section table th{padding:.2em .4em;border-width:1px;border-style:solid}div#p>svg>foreignObject>section{background-image:linear-gradient(135deg,hsla(0,0%,53.3%,0),hsla(0,0%,53.3%,.02) 50%,hsla(0,0%,100%,0) 0,hsla(0,0%,100%,.05));font-size:35px;font-family:Lato,Avenir Next,Avenir,Trebuchet MS,Segoe UI,sans-serif;height:720px;line-height:1.35;letter-spacing:1.25px;padding:70px;width:1280px;word-wrap:break-word;color:#455a64;background-color:#fff8e1}div#p>svg>foreignObject>section{--marpit-root-font-size:35px}div#p>svg>foreignObject>section>:first-child,div#p>svg>foreignObject>section>header:first-child+*{margin-top:0}div#p>svg>foreignObject>section a,div#p>svg>foreignObject>section mark{color:#0288d1}div#p>svg>foreignObject>section code{background:#6a7a7d;color:#fff8e1}div#p>svg>foreignObject>section h1 strong,div#p>svg>foreignObject>section h2 strong,div#p>svg>foreignObject>section h3 strong,div#p>svg>foreignObject>section h4 strong,div#p>svg>foreignObject>section h5 strong,div#p>svg>foreignObject>section h6 strong{color:#0288d1}div#p>svg>foreignObject>section pre>code{background:#455a64}div#p>svg>foreignObject>section blockquote:after,div#p>svg>foreignObject>section blockquote:before,div#p>svg>foreignObject>section footer,div#p>svg>foreignObject>section header,div#p>svg>foreignObject>section section:after{color:#6a7a7d}div#p>svg>foreignObject>section table td,div#p>svg>foreignObject>section table th{border-color:#455a64}div#p>svg>foreignObject>section table thead th{background:#455a64;color:#fff8e1}div#p>svg>foreignObject>section table tbody>tr:nth-child(odd) td,div#p>svg>foreignObject>section table tbody>tr:nth-child(odd) th{background:rgba(69,90,100,.1)}div#p>svg>foreignObject>section.invert{color:#fff8e1;background-color:#455a64}div#p>svg>foreignObject>section.invert a,div#p>svg>foreignObject>section.invert mark{color:#81d4fa}div#p>svg>foreignObject>section.invert code{background:#dad8c8;color:#455a64}div#p>svg>foreignObject>section.invert h1 strong,div#p>svg>foreignObject>section.invert h2 strong,div#p>svg>foreignObject>section.invert h3 strong,div#p>svg>foreignObject>section.invert h4 strong,div#p>svg>foreignObject>section.invert h5 strong,div#p>svg>foreignObject>section.invert h6 strong{color:#81d4fa}div#p>svg>foreignObject>section.invert pre>code{background:#fff8e1}div#p>svg>foreignObject>section.invert blockquote:after,div#p>svg>foreignObject>section.invert blockquote:before,div#p>svg>foreignObject>section.invert footer,div#p>svg>foreignObject>section.invert header,div#p>svg>foreignObject>section.invert section:after{color:#dad8c8}div#p>svg>foreignObject>section.invert table td,div#p>svg>foreignObject>section.invert table th{border-color:#fff8e1}div#p>svg>foreignObject>section.invert table thead th{background:#fff8e1;color:#455a64}div#p>svg>foreignObject>section.invert table tbody>tr:nth-child(odd) td,div#p>svg>foreignObject>section.invert table tbody>tr:nth-child(odd) th{background:rgba(255,248,225,.1)}div#p>svg>foreignObject>section.gaia{color:#fff8e1;background-color:#0288d1}div#p>svg>foreignObject>section.gaia a,div#p>svg>foreignObject>section.gaia mark{color:#81d4fa}div#p>svg>foreignObject>section.gaia code{background:#cce2de;color:#0288d1}div#p>svg>foreignObject>section.gaia h1 strong,div#p>svg>foreignObject>section.gaia h2 strong,div#p>svg>foreignObject>section.gaia h3 strong,div#p>svg>foreignObject>section.gaia h4 strong,div#p>svg>foreignObject>section.gaia h5 strong,div#p>svg>foreignObject>section.gaia h6 strong{color:#81d4fa}div#p>svg>foreignObject>section.gaia pre>code{background:#fff8e1}div#p>svg>foreignObject>section.gaia blockquote:after,div#p>svg>foreignObject>section.gaia blockquote:before,div#p>svg>foreignObject>section.gaia footer,div#p>svg>foreignObject>section.gaia header,div#p>svg>foreignObject>section.gaia section:after{color:#cce2de}div#p>svg>foreignObject>section.gaia table td,div#p>svg>foreignObject>section.gaia table th{border-color:#fff8e1}div#p>svg>foreignObject>section.gaia table thead th{background:#fff8e1;color:#0288d1}div#p>svg>foreignObject>section.gaia table tbody>tr:nth-child(odd) td,div#p>svg>foreignObject>section.gaia table tbody>tr:nth-child(odd) th{background:rgba(255,248,225,.1)}div#p>svg>foreignObject>section.lead{display:flex;flex-direction:column;flex-wrap:nowrap;justify-content:center}div#p>svg>foreignObject>section.lead h1,div#p>svg>foreignObject>section.lead h2,div#p>svg>foreignObject>section.lead h3,div#p>svg>foreignObject>section.lead h4,div#p>svg>foreignObject>section.lead h5,div#p>svg>foreignObject>section.lead h6{text-align:center}div#p>svg>foreignObject>section.lead h1 svg[data-marp-fitting=svg],div#p>svg>foreignObject>section.lead h2 svg[data-marp-fitting=svg],div#p>svg>foreignObject>section.lead h3 svg[data-marp-fitting=svg],div#p>svg>foreignObject>section.lead h4 svg[data-marp-fitting=svg],div#p>svg>foreignObject>section.lead h5 svg[data-marp-fitting=svg],div#p>svg>foreignObject>section.lead h6 svg[data-marp-fitting=svg]{--preserve-aspect-ratio:xMidYMid meet}div#p>svg>foreignObject>section.lead p{text-align:center}div#p>svg>foreignObject>section.lead blockquote>h1,div#p>svg>foreignObject>section.lead blockquote>h2,div#p>svg>foreignObject>section.lead blockquote>h3,div#p>svg>foreignObject>section.lead blockquote>h4,div#p>svg>foreignObject>section.lead blockquote>h5,div#p>svg>foreignObject>section.lead blockquote>h6,div#p>svg>foreignObject>section.lead blockquote>p{text-align:left}div#p>svg>foreignObject>section.lead blockquote svg[data-marp-fitting=svg]:not([data-marp-fitting-math]){--preserve-aspect-ratio:xMinYMin meet}div#p>svg>foreignObject>section.lead ol>li>p,div#p>svg>foreignObject>section.lead ul>li>p{text-align:left}div#p>svg>foreignObject>section.lead table{margin-left:auto;margin-right:auto}div#p>svg>foreignObject>section:after,div#p>svg>foreignObject>section footer,div#p>svg>foreignObject>section header{box-sizing:border-box;font-size:66%;height:70px;line-height:50px;overflow:hidden;padding:10px 25px;position:absolute}div#p>svg>foreignObject>section:after{--marpit-root-font-size:66%}div#p>svg>foreignObject>section header{top:0}div#p>svg>foreignObject>section footer,div#p>svg>foreignObject>section header{left:0;right:0}div#p>svg>foreignObject>section footer{bottom:0}div#p>svg>foreignObject>section:after{right:0;bottom:0;font-size:80%}div#p>svg>foreignObject>section:after{--marpit-root-font-size:80%}div#p>svg>foreignObject>section svg[data-marp-fitting=svg]{display:block;height:auto;width:100%}@supports (-ms-ime-align:auto){div#p>svg>foreignObject>section svg[data-marp-fitting=svg]{position:static}}div#p>svg>foreignObject>section svg[data-marp-fitting=svg].__reflow__{content:""}@supports (-ms-ime-align:auto){div#p>svg>foreignObject>section svg[data-marp-fitting=svg].__reflow__{position:relative}}div#p>svg>foreignObject>section [data-marp-fitting-svg-content]{display:table;white-space:nowrap}div#p>svg>foreignObject>section [data-marp-fitting-svg-content-wrap]{white-space:pre}div#p>svg>foreignObject>section img[data-marp-twemoji]{background:transparent;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em;width:1em}div#p>svg>foreignObject>section:not(\9){background-color:white;color:black}div#p>svg>foreignObject>section img{max-width:100%;max-height:calc(100% - calc(var(--marpit-root-font-size, 1rem) * 3))}div#p>svg>foreignObject>section header{text-align:right}div#p>svg>foreignObject>section{justify-content:start;padding:calc(var(--marpit-root-font-size, 1rem) * 1) calc(var(--marpit-root-font-size, 1rem) * 1)}div#p>svg>foreignObject>section.title{justify-content:center;text-align:center}div#p>svg>foreignObject>section.title h1{text-align:center}div#p>svg>foreignObject>section pre code{line-height:calc(var(--marpit-root-font-size, 1rem) * 1.25);background-color:rgba(50,50,50,0.85);backdrop-filter:blur(10px)}
/* @theme i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6 */div#p>svg>foreignObject>section[data-marpit-advanced-background=background]{display:block!important;padding:0!important}div#p>svg>foreignObject>section[data-marpit-advanced-background=background]:after,div#p>svg>foreignObject>section[data-marpit-advanced-background=background]:before,div#p>svg>foreignObject>section[data-marpit-advanced-background=content]:after,div#p>svg>foreignObject>section[data-marpit-advanced-background=content]:before{display:none!important}div#p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]{all:initial;display:flex;flex-direction:row;height:100%;overflow:hidden;width:100%}div#p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container][data-marpit-advanced-background-direction=vertical]{flex-direction:column}div#p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split]>div[data-marpit-advanced-background-container]{width:var(--marpit-advanced-background-split,50%)}div#p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split=right]>div[data-marpit-advanced-background-container]{margin-left:calc(100% - var(--marpit-advanced-background-split, 50%))}div#p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure{all:initial;background-position:center;background-repeat:no-repeat;background-size:cover;flex:auto;margin:0}div#p>svg>foreignObject>section[data-marpit-advanced-background=content],div#p>svg>foreignObject>section[data-marpit-advanced-background=pseudo]{background:transparent!important}div#p>svg>foreignObject>section[data-marpit-advanced-background=pseudo],div#p>svg[data-marpit-svg]>foreignObject[data-marpit-advanced-background=pseudo]{pointer-events:none!important}div#p>svg>foreignObject>section[data-marpit-advanced-background-split]{width:100%;height:100%}</style></head><body><div class="bespoke-marp-osc"><button data-bespoke-marp-osc="prev" tabindex="-1" title="Previous slide">Previous slide</button><span data-bespoke-marp-osc="page"></span><button data-bespoke-marp-osc="next" tabindex="-1" title="Next slide">Next slide</button><button data-bespoke-marp-osc="fullscreen" tabindex="-1" title="Toggle fullscreen (f)">Toggle fullscreen</button><button data-bespoke-marp-osc="presenter" tabindex="-1" title="Open presenter view (p)">Open presenter view</button></div><div id="p"><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="1" data-paginate="true" data-class="lead invert title" data-footer="https://nils4cosee.github.io/darmstadt-js-visual-regression/" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" class="lead invert title" data-marpit-pagination="1" data-marpit-pagination-total="56" style="--paginate:true;--class:lead invert title;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1><strong>Visual Regression Testing</strong> with Storybook</h1>
<p>Nils Knappmeier</p>
<p>Press "P" to see the speaker notes.</p>
<footer><a href="https://nils4cosee.github.io/darmstadt-js-visual-regression/">https://nils4cosee.github.io/darmstadt-js-visual-regression/</a></footer>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="2" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="2" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Who am I?</h1>
<p><img src="https://nknapp.gitlab.io/avatar.svg" alt="height:60px" style="height:60px;"></p>
<p>Fullstack-Engineer @ Cosee since 2019</p>
<ul>
<li><a href="https://github.com/nknapp">https://github.com/nknapp</a></li>
<li><a href="https://www.npmjs.com/~knappi">https://www.npmjs.com/~knappi</a></li>
<li><a href="mailto:[email protected]">[email protected]</a></li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="3" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="3" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<p><img src="iviews-stack-2004.e9083da0.png" alt=""></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="4" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="4" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<p><img src="cosee-stack.506834f3.png" alt=""></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="5" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="5" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<p><img src="iviews-stack-2004-testing.a399083f.png" alt=""></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="6" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="6" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<p><img src="cosee-stack-testing.37d78e05.png" alt=""></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="7" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("diff.90e1ca34.png");background-size:80%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="7" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="7" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Backend testing</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="7" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="8" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("todo-list.c0899184.gif");background-size:50%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="8" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="8" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Frontend testing</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="8" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="9" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("application-layers.e29d8a85.png");background-size:80%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="9" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="9" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Typical SPA</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="9" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="10" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="10" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Utility Functions</h1>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap="">it(<span class="hljs-string">'returns false for 1'</span>, <span class="hljs-function">() =></span> {
expect(isPrime(<span class="hljs-number">1</span>)).toBe(<span class="hljs-literal">false</span>);
})
it(<span class="hljs-string">'returns true for 2'</span>, <span class="hljs-function">() =></span> {
expect(isPrime(<span class="hljs-number">2</span>)).toBe(<span class="hljs-literal">true</span>);
})
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="11" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="11" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Business-Logic</h1>
<pre><code class="language-jsx"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap="">it(<span class="hljs-string">'renders personalized greeting'</span>, <span class="hljs-keyword">async</span> () => {
<span class="hljs-comment">// Render new instance in every test to prevent leaking state</span>
<span class="hljs-keyword">const</span> { getByText } = render(<span class="xml"><span class="hljs-tag"><<span class="hljs-name">HelloMessage</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Satoshi"</span> /></span></span>);
<span class="hljs-keyword">await</span> waitForElement(<span class="hljs-function">() =></span> getByText(<span class="hljs-regexp">/hello Satoshi/i</span>));
});
</span></span></foreignObject></svg></code></pre>
<p><a href="https://react-testing-examples.com/">https://react-testing-examples.com/</a></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="12" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="12" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Backend-Mocks</h1>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap=""><span class="hljs-keyword">const</span> nock = <span class="hljs-built_in">require</span>(<span class="hljs-string">'nock'</span>)
<span class="hljs-keyword">const</span> scope = nock(<span class="hljs-string">'https://api.github.com'</span>)
.get(<span class="hljs-string">'/repos/atom/atom/license'</span>)
.reply(<span class="hljs-number">200</span>, {
<span class="hljs-attr">license</span>: {
<span class="hljs-attr">key</span>: <span class="hljs-string">'mit'</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'MIT License'</span>,
<span class="hljs-attr">spdx_id</span>: <span class="hljs-string">'MIT'</span>, <span class="hljs-attr">url</span>: <span class="hljs-string">'https://api.github.com/licenses/mit'</span>,
<span class="hljs-attr">node_id</span>: <span class="hljs-string">'MDc6TGljZW5zZTEz'</span>,
},
})
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="13" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("application-layers-checked.17e37d6c.png");background-size:80%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="13" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="13" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>What to test?</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="13" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="14" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("app--default-800x600-firefox-snap.af5bb40e.png");background-size:50%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="14" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="14" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Image snapshots</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="14" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="15" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("app--default-800x600-firefox-rotate.6619a902.png");background-size:50%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="15" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="15" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Spot the change</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="15" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="16" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("app--default-800x600-firefox-diff.126de22c.png");background-size:150%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="16" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="16" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>jest-image-snapshot</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="16" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="17" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("puppeteer-logo.f1e5f6a6.png");background-size:40%;"></figure><figure style="background-image:url("selenium_logo_square_green.5d63043f.png");background-size:40%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="17" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="17" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Creating snapshots</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="17" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="18" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="background" data-marpit-advanced-background-split="left"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("puppeteer-logo.f1e5f6a6.png");background-size:contain;"></figure></div></section></foreignObject><foreignObject width="80%" height="720" x="20%"><section id="18" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="18" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="content" data-marpit-advanced-background-split="left"><pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap=""><span class="hljs-keyword">import</span> puppeteer <span class="hljs-keyword">from</span> <span class="hljs-string">"puppeteer"</span>;
<span class="hljs-keyword">import</span> { toMatchImageSnapshot } <span class="hljs-keyword">from</span> <span class="hljs-string">"jest-image-snapshot"</span>;
expect.extend({ toMatchImageSnapshot });
<span class="hljs-keyword">let</span> browser;
beforeAll(<span class="hljs-keyword">async</span> () => {
browser = <span class="hljs-keyword">await</span> puppeteer.launch({<span class="hljs-attr">headless</span>: <span class="hljs-literal">true</span>});
})
afterAll(<span class="hljs-function">() =></span> browser.close())
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="18" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="19" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="background" data-marpit-advanced-background-split="left"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("puppeteer-logo.f1e5f6a6.png");background-size:contain;"></figure></div></section></foreignObject><foreignObject width="80%" height="720" x="20%"><section id="19" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="19" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="content" data-marpit-advanced-background-split="left"><pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap="">test(<span class="hljs-string">"page matches the snapshot"</span>, <span class="hljs-keyword">async</span> () => {
<span class="hljs-keyword">const</span> page = <span class="hljs-keyword">await</span> browser.newPage();
<span class="hljs-keyword">await</span> page.setViewport({<span class="hljs-attr">width</span>: <span class="hljs-number">1024</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">800</span>});
<span class="hljs-keyword">await</span> page.goto(<span class="hljs-string">"http://localhost:3000"</span>);
<span class="hljs-keyword">const</span> image = <span class="hljs-keyword">await</span> page.screenshot();
expect(image).toMatchImageSnapshot();
<span class="hljs-keyword">await</span> page.close();
});
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="19" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="20" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="background" data-marpit-advanced-background-split="right"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("selenium_logo_square_green.5d63043f.png");background-size:90%;"></figure></div></section></foreignObject><foreignObject width="80%" height="720"><section id="20" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="20" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="content" data-marpit-advanced-background-split="right"><pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap=""><span class="hljs-keyword">import</span> { Builder } <span class="hljs-keyword">from</span> <span class="hljs-string">"selenium-webdriver"</span>;
<span class="hljs-keyword">import</span> { toMatchImageSnapshot } <span class="hljs-keyword">from</span> <span class="hljs-string">"jest-image-snapshot"</span>;
expect.extend({ toMatchImageSnapshot });
<span class="hljs-keyword">let</span> driver;
beforeAll(<span class="hljs-function">() =></span> {
driver = <span class="hljs-keyword">new</span> Builder()
.usingServer(<span class="hljs-string">"http://localhost:4444/wd/hub"</span>)
.withCapabilities({ <span class="hljs-attr">browserName</span>: <span class="hljs-string">"firefox"</span> })
.build();
}, <span class="hljs-number">20000</span>);
afterAll(<span class="hljs-function">() =></span> driver.quit());
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="20" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="21" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="background" data-marpit-advanced-background-split="right"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("selenium_logo_square_green.5d63043f.png");background-size:90%;"></figure></div></section></foreignObject><foreignObject width="80%" height="720"><section id="21" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="21" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="content" data-marpit-advanced-background-split="right"><pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap="">test(<span class="hljs-string">"page matches the selenium-snapshot"</span>, <span class="hljs-keyword">async</span> () => {
<span class="hljs-keyword">await</span> driver.get(<span class="hljs-string">"http://localhost:3000"</span>);
<span class="hljs-keyword">await</span> driver
.manage()
.window()
.setRect({ <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">width</span>: <span class="hljs-number">1024</span>, <span class="hljs-attr">height</span>: <span class="hljs-number">800</span> });
<span class="hljs-keyword">const</span> imageBase64 = <span class="hljs-keyword">await</span> driver.takeScreenshot();
<span class="hljs-keyword">const</span> image = Buffer.from(imageBase64, <span class="hljs-string">"base64"</span>);
expect(image).toMatchImageSnapshot();
});
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="21" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="22" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="vertical"><figure style="background-image:url("docker-selenium.6068ad03.jpg");background-size:60%;"></figure><figure style="background-image:url("selenium-services.a290d6dd.png");background-size:60%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="22" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="22" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content"></section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="22" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="23" data-paginate="true" data-class="lead title" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" class="lead title" data-marpit-pagination="23" data-marpit-pagination-total="56" style="--paginate:true;--class:lead title;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h2><strong>Common pitfalls</strong></h2>
<p>How NOT to get frustrated?</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="24" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="24" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<p><img src="changes-by-time.8d85abb1.gif" alt="width:2000px" style="width:2000px;"></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="25" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("app--default-800x600-firefox-diff.126de22c.png");background-size:150%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="25" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="25" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content"></section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="25" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-marpit-fragments="1" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="26" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("diff-gitlab-ci.e1aca4b0.png");background-size:100% auto;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="26" data-marpit-fragments="1" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="26" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<ul>
<li data-marpit-fragment="1">Mac, Linux, Windows and CI/CD</li>
</ul>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="26" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="27" data-paginate="true" data-class="lead title" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" class="lead title" data-marpit-pagination="27" data-marpit-pagination-total="56" style="--paginate:true;--class:lead title;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h2><strong>Isolating UI components</strong></h2>
<p>with Storybook</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="28" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="28" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;"><pre><code class="language-jsx"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap=""><span class="hljs-keyword">import</span> {action} <span class="hljs-keyword">from</span> <span class="hljs-string">"@storybook/addon-actions"</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> { <span class="hljs-attr">title</span>: <span class="hljs-string">"buttons"</span> };
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> SimpleButton = <span class="hljs-function">() =></span> <span class="xml"><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{action(</span>'<span class="hljs-attr">click</span>')}></span>simple button<span class="hljs-tag"></<span class="hljs-name">button</span>></span></span>;
</span></span></foreignObject></svg></code></pre>
<p><img src="storybook-screencast.69afd52e.gif" alt="height:480px" style="height:480px;"></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="29" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="29" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h2>Screenshot as a Service</h2>
<ul>
<li>Chromatic</li>
<li>Percy</li>
</ul>
<h2>Self hosted</h2>
<ul>
<li>Storyshots</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="30" data-paginate="true" data-class="lead title" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" class="lead title" data-marpit-pagination="30" data-marpit-pagination-total="56" style="--paginate:true;--class:lead title;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h2><strong>Storyshots</strong></h2>
<p>an attempt to self-hosted screenshot comparison</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="31" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="31" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap=""><span class="hljs-comment">// storyshots.test.js</span>
<span class="hljs-keyword">import</span> initStoryshots <span class="hljs-keyword">from</span> <span class="hljs-string">"@storybook/addon-storyshots"</span>;
initStoryshots({ <span class="hljs-attr">framework</span>: <span class="hljs-string">"react"</span>, <span class="hljs-attr">suite</span>: <span class="hljs-string">"All"</span> });
</span></span></foreignObject></svg></code></pre>
<p><img src="storyshots-snapshot.bb7a2db8.png" alt="height:400px" style="height:400px;"></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="32" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="32" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap=""><span class="hljs-comment">// storyshots-puppeteer.test.js</span>
<span class="hljs-keyword">import</span> initStoryshots <span class="hljs-keyword">from</span> <span class="hljs-string">'@storybook/addon-storyshots'</span>;
<span class="hljs-keyword">import</span> { imageSnapshot } <span class="hljs-keyword">from</span> <span class="hljs-string">'@storybook/addon-storyshots-puppeteer'</span>;
initStoryshots({ <span class="hljs-attr">suite</span>: <span class="hljs-string">'Image storyshots'</span>, <span class="hljs-attr">test</span>: imageSnapshot() });
</span></span></foreignObject></svg></code></pre>
<p><img src="diff-gitlab-ci.e1aca4b0.png" alt=""></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="33" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("reverse-tunnel.61207930.png");background-size:60%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="33" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="33" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content"></section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="33" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="34" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="34" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap=""><span class="hljs-comment">// storyshots-selenium.test.js</span>
<span class="hljs-keyword">import</span> initStoryshots <span class="hljs-keyword">from</span> <span class="hljs-string">"@storybook/addon-storyshots"</span>;
<span class="hljs-keyword">import</span> {imageSnapshot} <span class="hljs-keyword">from</span> <span class="hljs-string">"@knappi/addon-storyshots-selenium"</span>;
initStoryshots({
<span class="hljs-attr">framework</span>: <span class="hljs-string">"react"</span>,
<span class="hljs-attr">suite</span>: <span class="hljs-string">"All"</span>,
<span class="hljs-attr">test</span>: imageSnapshot({
<span class="hljs-attr">browsers</span>: [
{ <span class="hljs-attr">id</span>: <span class="hljs-string">"chrome"</span>, <span class="hljs-attr">capabilities</span>: { <span class="hljs-attr">browserName</span>: <span class="hljs-string">"chrome"</span> } },
{ <span class="hljs-attr">id</span>: <span class="hljs-string">"firefox"</span>, <span class="hljs-attr">capabilities</span>: { <span class="hljs-attr">browserName</span>: <span class="hljs-string">"firefox"</span> } },
],
<span class="hljs-attr">seleniumUrl</span>: <span class="hljs-string">"http://localhost:24444/wd/hub"</span>,
<span class="hljs-attr">storybookUrl</span>: <span class="hljs-string">`http://localhost:<span class="hljs-subst">${REMOTE_TUNNEL_PORT}</span>`</span>,
}),
});
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="35" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="35" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<pre><code class="language-tsx"><svg data-marp-fitting="svg" data-marp-fitting-code=""><foreignObject><span data-marp-fitting-svg-content=""><span data-marp-fitting-svg-content-wrap="">export const Default: Story = () => <MyComponent />;
Default.parameters= {
storyshotSelenium: {
sizes: ["1024x768", "800x600", "360x480"]
}
}
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="36" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="36" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>S l o w ......</h1>
<ul>
<li>Wait long enough, but not too long</li>
<li>Animations</li>
<li>Browser specification ambiguous</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="37" data-paginate="true" data-class="lead title" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" class="lead title" data-marpit-pagination="37" data-marpit-pagination-total="56" style="--paginate:true;--class:lead title;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Services: Chromatic</h1>
<p>by the authors of Storybook...</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="38" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="38" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<p><img src="1F52C_color.0df73c09.png" alt="height:60px" style="height:60px;"><br>
Storybook is uploaded to Chromatic and tested there</p>
<p><img src="1FA99_color.e97af546.png" alt="height:60px" style="height:60px;"><br>
Free: 5000 Snapshots/month (Chrome only)<br>
Paid plans with IE11 and Firefox and more snapshots</p>
<p><img src="2699_color.39abc212.png" alt="height:60px" style="height:60px;"><br>
Github integration<br>
Command fails if snapshots do not match</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="39" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("chromatic-setup1.0225c4c4.png");background-size:60% auto;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="39" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="39" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Chromatic</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="39" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="40" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("chromatic-setup2.ead595ea.png");background-size:60% auto;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="40" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="40" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Chromatic</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="40" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="41" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("chromatic-published.c5b8947a.png");background-size:60% auto;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="41" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="41" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Chromatic</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="41" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="42" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("chromatic-diff.c03dd2ad.png");background-size:60% auto;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="42" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="42" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Chromatic</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="42" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="43" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("chromatic-diff2.dcb45042.png");background-size:60% auto;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="43" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="43" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content">
<h1>Chromatic</h1>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="43" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="44" data-paginate="true" data-class="lead title" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" class="lead title" data-marpit-pagination="44" data-marpit-pagination-total="56" style="--paginate:true;--class:lead title;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Services: Percy</h1>
<p>now part of BrowserStack, a Selenium as a Service company.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="45" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="background" data-marpit-advanced-background-split="right"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("integration.6094bc35.png");background-size:100% auto;"></figure></div></section></foreignObject><foreignObject width="80%" height="720"><section id="45" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="45" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:20%;" data-marpit-advanced-background="content" data-marpit-advanced-background-split="right">
<p><img src="1F52C_color.0df73c09.png" alt="height:60px" style="height:60px;"><br>
Create a DOM snapshot using puppeteer, re-render in Chrome and Firefox</p>
<p><img src="1FA99_color.e97af546.png" alt="height:60px" style="height:60px;"><br>
Free: 5000 Snapshots/month (Chrome AND Firefox)<br>
Paid plans with more snapshots (but no IE11)</p>
<p><img src="2699_color.39abc212.png" alt="height:60px" style="height:60px;"><br>
Asynchronous rendering and inspection<br>
Integration for many CI-Tools to mark builds as "failed"</p>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="45" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="46" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("Selection_015.fc96ca4a.png");background-size:auto 90%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="46" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="46" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content"></section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="46" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="47" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("Selection_017.54b959b5.png");background-size:auto 90%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="47" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="47" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content"></section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="47" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="48" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("Selection_018.59781361.png");background-size:auto 90%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="48" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="48" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content"></section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="48" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="49" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="background"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("Selection_019.bfaeb113.png");background-size:auto 90%;"></figure></div></section></foreignObject><foreignObject width="1280" height="720"><section id="49" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="49" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;" data-marpit-advanced-background="content"></section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="49" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="50" data-marpit-fragments="1" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="50" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>SPIEL.digital</h1>
<ul>
<li>Use Percy's free plan</li>
<li>Schedule CI-job every morning at 7am</li>
</ul>
<ul>
<li data-marpit-fragment="1">Didn't really have time to look at the screenshots<br>
<img src="1F626_color.fba08b70.png" alt="height:240px" style="height:240px;"></li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="51" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="51" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Conclusion (1)</h1>
<ul>
<li>Non-Sensitive Data? Supported browsers OK?</li>
<li>Use a cloud service
<ul>
<li>Easy setup</li>
<li>Prevent false positives</li>
<li>Animations</li>
<li>Speed</li>
<li>Probably cheaper than custom setup</li>
</ul>
</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="52" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="52" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Conclusion (2)</h1>
<ul>
<li>Want to help create "storyshots-selenium"?<br>
<img src="261D_color.d402c032.png" alt="height:240px" style="height:240px;"></li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="53" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:50%;" data-marpit-advanced-background="background" data-marpit-advanced-background-split="right"><div data-marpit-advanced-background-container="true" data-marpit-advanced-background-direction="horizontal"><figure style="background-image:url("1F914_color.5f4ac3e1.png");background-size:70% auto;"></figure></div></section></foreignObject><foreignObject width="50%" height="720"><section id="53" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="53" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;--marpit-advanced-background-split:50%;" data-marpit-advanced-background="content" data-marpit-advanced-background-split="right">
<h1>Further research...</h1>
<ul>
<li>Try storybook-puppeteer for functional tests</li>
<li>Visual feedback on functional tests</li>
</ul>
</section>
</foreignObject><foreignObject width="1280" height="720" data-marpit-advanced-background="pseudo"><section style="" data-marpit-advanced-background="pseudo" data-marpit-pagination="53" data-marpit-pagination-total="56"></section></foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="54" data-paginate="true" data-class="lead title" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" class="lead title" data-marpit-pagination="54" data-marpit-pagination-total="56" style="--paginate:true;--class:lead title;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<p>Thank you for listening. Questions?</p>
<h1>?</h1>
<p><a href="https://github.com/nknapp">https://github.com/nknapp</a><br>
<a href="https://www.npmjs.com/~knappi">https://www.npmjs.com/~knappi</a><br>
<a href="mailto:[email protected]">[email protected]</a></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="55" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="55" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Attribution</h1>
<ul>
<li>Selenium sponsors: from <a href="https://www.selenium.dev/projects/">https://www.selenium.dev/projects/</a></li>
<li>All emojis designed by <a href="https://openmoji.org/">OpenMoji</a> – the open-source emoji and icon project. License: <a href="https://creativecommons.org/licenses/by-sa/4.0/#">CC BY-SA 4.0</a></li>
<li>Slides have been created with <a href="https://github.com/marp-team/marp-cli">https://github.com/marp-team/marp-cli</a> and bundled with <a href="https://parceljs.org/">https://parceljs.org/</a></li>
<li>I can't find the source of this image anymore <img src="man-computer.85631237.png" alt="height:30px" style="height:30px;">. Please contact me if you know where it originates.</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="56" data-paginate="true" data-theme="i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6" data-marpit-pagination="56" data-marpit-pagination-total="56" style="--paginate:true;--theme:i1deocr39fa39zglestyd8cj1hm0fmrn9jjw8a6t3i6;">
<h1>Packages</h1>
<ul>
<li><a href="https://github.com/elgalu/docker-selenium">Docker-Selenium</a></li>
<li><a href="https://storybook.js.org">Storybook</a></li>
<li><a href="https://github.com/storybookjs/storybook/tree/next/addons/storyshots">Storyshots</a></li>
<li><a href="https://github.com/nknapp/addon-storyshots-selenium#readme">@knappi/addon-storyshots-selenium</a></li>
<li><a href="https://github.com/americanexpress/jest-image-snapshot">jest-image-snapshot</a></li>
<li><a href="https://github.com/nock/nock">nock http mocking</a></li>
<li><a href="https://github.com/mswjs/msw">Mock Service Worker</a></li>
</ul>
</section>
<script>function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e3) { throw _e3; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e4) { didErr = true; err = _e4; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
!function () {
"use strict";
var t = "marpitSVGPolyfill:setZoomFactor,",
e = Symbol();
var o, r;
function n(n) {
var i = "object" == _typeof(n) && n.target || document,
a = "object" == _typeof(n) ? n.zoom : n;
window[e] || (Object.defineProperty(window, e, {
configurable: !0,
value: !0
}), window.addEventListener("message", function (_ref) {
var e = _ref.data,
o = _ref.origin;
if (o === window.origin) try {
if (e && "string" == typeof e && e.startsWith(t)) {
var _e$split = e.split(","),
_e$split2 = _slicedToArray(_e$split, 2),
_t = _e$split2[1],
_o = Number.parseFloat(_t);
Number.isNaN(_o) || (r = _o);
}
} catch (t) {
console.error(t);
}
}));
var l = !1;
Array.from(i.querySelectorAll("svg[data-marpit-svg]"), function (t) {
var e, n, i, s;
t.style.transform || (t.style.transform = "translateZ(0)");
var c = a || r || t.currentScale || 1;
o !== c && (o = c, l = c);
var d = t.getBoundingClientRect(),
u = t.children.length;
for (var _o2 = 0; _o2 < u; _o2 += 1) {
var _r = t.children[_o2],
_a = _r.getScreenCTM();
if (_a) {
var _t2 = null !== (n = null === (e = _r.x) || void 0 === e ? void 0 : e.baseVal.value) && void 0 !== n ? n : 0,
_o3 = null !== (s = null === (i = _r.y) || void 0 === i ? void 0 : i.baseVal.value) && void 0 !== s ? s : 0,
_l = _r.firstChild,
_u = _l.style;
_u.transformOrigin || (_u.transformOrigin = "".concat(-_t2, "px ").concat(-_o3, "px")), _u.transform = "scale(".concat(c, ") matrix(").concat(_a.a, ", ").concat(_a.b, ", ").concat(_a.c, ", ").concat(_a.d, ", ").concat(_a.e - d.left, ", ").concat(_a.f - d.top, ") translateZ(0.0001px)");
}
}
}), !1 !== l && Array.from(i.querySelectorAll("iframe"), function (_ref2) {
var e = _ref2.contentWindow;
null == e || e.postMessage("".concat(t).concat(l), "null" === window.origin ? "*" : window.origin);
});
}
o = 1, r = void 0;
var i = function i(t, e, o) {
if (t.getAttribute(e) !== o) return t.setAttribute(e, o), !0;
};
function a() {
var t = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var e = "boolean" == typeof t ? function (t) {
var e = !t;
return console.warn("[DEPRECATION WARNING] Usage of observer() with boolean option has been deprecated. Please replace with the usage of option object: observer({ once: ".concat(e ? "true" : "false", " }).")), {
once: e
};
}(t) : t,
_e$once = e.once,
o = _e$once === void 0 ? !1 : _e$once,
_e$target = e.target,
r = _e$target === void 0 ? document : _e$target,
a = "Apple Computer, Inc." === navigator.vendor ? [n] : [];
var l = !o;
var s = function s() {
var _iterator = _createForOfIteratorHelper(a),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _t4 = _step.value;
_t4({
target: r
});
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
!function () {
var t = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
Array.from(t.querySelectorAll('svg[data-marp-fitting="svg"]'), function (t) {
var e;
var o = t.firstChild,
r = o.firstChild,
n = r.scrollWidth,
a = r.scrollHeight;
var l,
s = 1;
if (t.hasAttribute("data-marp-fitting-code") && (l = null === (e = t.parentElement) || void 0 === e ? void 0 : e.parentElement), t.hasAttribute("data-marp-fitting-math") && (l = t.parentElement), l) {
var _t3 = getComputedStyle(l),
_e2 = Math.ceil(l.clientWidth - parseFloat(_t3.paddingLeft || "0") - parseFloat(_t3.paddingRight || "0"));
_e2 && (s = _e2);
}
var c = Math.max(n, s),
d = Math.max(a, 1),
u = "0 0 ".concat(c, " ").concat(d);
i(o, "width", "" + c), i(o, "height", "" + d), i(t, "preserveAspectRatio", getComputedStyle(t).getPropertyValue("--preserve-aspect-ratio") || "xMinYMin meet"), i(t, "viewBox", u) && t.classList.toggle("__reflow__");
});
}(r), l && window.requestAnimationFrame(s);
};
return s(), function () {
l = !1;
};
}
var l = Symbol(),
s = document.currentScript;
(function () {
var t = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
if ("undefined" == typeof window) throw new Error("Marp Core's browser script is valid only in browser context.");
if (t[l]) return t[l];
var e = a({
target: t
}),
o = function o() {
e(), delete t[l];
};
Object.defineProperty(t, l, {
configurable: !0,
value: o
});
})(s ? s.getRootNode() : document);
}();</script></foreignObject></svg></div><div class="bespoke-marp-note" data-index="2" tabindex="0"><p>When I started working at i-views in 2007, I was assigned to the "Frontend"- or "Java"-Team.
The ironic thing is that "frontend" at that time included the Tomcat-Webserver, Servlets, JSPs to render HTML, which was then enriched with a little javascript
and styled with plain CSS .
Later we switched to a single-page-app, but I still did a lot of work with Java-Backends.</p></div><div class="bespoke-marp-note" data-index="3" tabindex="0"><p>Now "frontend" means "React", "styled-components", "Typescript". And "Backend" is mostly "Spring Boot" with Rest-Services. So when I applied for a job at Cosee, I applied as Full-Stack-Engineer.</p></div><div class="bespoke-marp-note" data-index="4" tabindex="0"><p>What does this have to do with testing? There are parts of an application
that are easy to test automatically, others aren't. For example the business-logic and servlets are easy to test. JSPs give you quite some pain and the generated HTML in combination with styles and injected JS... Let's not talk about it. The only feasible way is to do end-to-end tests, which can be very painful.</p></div><div class="bespoke-marp-note" data-index="5" tabindex="0"><p>With React/Vue and other SPA frameworks, we have moved some parts of the application into the browser. The backend is easier to test now.
But why actually?</p></div><div class="bespoke-marp-note" data-index="6" tabindex="0"><p>In the backend, we a working mostly with clear structures that are easy to compare
to each other. Also, the backend is designed to be used programmatically,
which makes it easy to automate tests.
When doing backend development, I WANT to write
tests, because it is a much easier way to test then
do manual testing.
Of course, there are other challenges: Integration with other services, complex algorithms and data-structures. It is not ALWAYS easy.</p></div><div class="bespoke-marp-note" data-index="7" tabindex="0"><p>The closer with get to the human user, the harder it is to write automated tests.
We need to simulate a human user.
"Wait for this element to show up".
"Type this text".
"Click here".
"Is this element visible now?"
It's very tempting to test your code manually, because the code you are writing
is meant to be used by humans, not machines.</p></div><div class="bespoke-marp-note" data-index="8" tabindex="0"><p>When we look at the typical Single-Page-Application (for example a React app),
we usually also have different parts. And if you look closely you see that
the transition from Tomcat/JSP to Spring-Boot/SPA is actually an attempt to
reduce the parts of the application that are difficult to test.</p></div><div class="bespoke-marp-note" data-index="9" tabindex="0"><p>We can extract complex logic into utility functions. Those are
easy to test as long as they are pure (i.e. stateless)</p></div><div class="bespoke-marp-note" data-index="10" tabindex="0"><p>Even for React components, there are test-helpers. They are a bit more
complicated to test, because we need to simulate the user, but it is possible.
But: My experience is that this part can be very frustrating. The React testing library
uses jsdom, which has no way to give visual feedback of the current component state.
We simulate a user and it takes a long time to write such tests, even for very simple
components.</p></div><div class="bespoke-marp-note" data-index="11" tabindex="0"><p>We can test the Http-Client by mocking the backend.
Use real responses as fixtures and a library like `nock` or `mswjs` to
create fake responses for your tests.</p></div><div class="bespoke-marp-note" data-index="12" tabindex="0"><p>So, if we structure our code well, we can test a lot of things, from the backend-access
up to (excluding) the presentation layer. We can do this in unit tests, withoug real backend.
But what about your CSS. That's the missing link and that's what I want to talk about now.</p></div><div class="bespoke-marp-note" data-index="13" tabindex="0"><p>The talk is named "Visual regression testing" and "regression" means that we want to ensure
that something that has previously worked, is still working.
This is not Test-Driven-Development. The idea is that we create a screenshot, verify it manually and then
commit it as "baseline" screenshot.</p></div><div class="bespoke-marp-note" data-index="14" tabindex="0"><p>Then, as we change our code, it may happen that screenshots do not match the baseline anymore.</p></div><div class="bespoke-marp-note" data-index="15" tabindex="0"><p>jest-image-snapshot is on of many library that compare such snapshots.
The reason why I show this as an example is, because we mostly use jest
and I've had good experiences with it.
When the test is executed the first time, the snapshot is saved into a `__image_snapshots__` directory next to the current test.
Afterwards the current snapshot is compared to the baseline the test fails if they differ.
The diff is saved into the __image_snapshots__ directory as well.
Configuration options include.
- How much as a pixel allowed to differ?
- How many pixels are allowed to differ?
- Structural comparison (SSIM algorithm)
After verification, we can commit the new snapshots of fix the bugs.</p></div><div class="bespoke-marp-note" data-index="16" tabindex="0"><p>In order to capture screenshots of a website automatically, we need a
remote-controlled browser. Nowadays, there are more or less two ways of capturing image snapshots from the browser
- Puppeteer: A remote-controlled Chrome browser.
- Selenium: A generic protocol to control a variety of browsers.
- There are other ways, like drawing HTML on canvas, but this is more complicated.
Short conclusion towards those methods:
- Selenium works in a variety of browsers including IE and Safari
- Puppeteer is only for Chrome (Firefox experimental), but is faster than Selenium</p></div><div class="bespoke-marp-note" data-index="17" tabindex="0"><p>This is the code to setup snapshots with puppeteer
- we need to register the image-snapshot-function for use with expect
- run puppeteer in the beginning
- close puppeteer in the end</p></div><div class="bespoke-marp-note" data-index="18" tabindex="0"><p>The test just navigates to the page, takes the screenshot and calls
the image-snapshot function. Don't forget to set the viewport to a fixed
size and yes: We also need to start a web-server.</p></div><div class="bespoke-marp-note" data-index="19" tabindex="0"><p>The selenium code is pretty similar to the puppeteer code.
The principles are the same.</p></div><div class="bespoke-marp-note" data-index="21" tabindex="0"><p>While puppeteer is relatively easy to install (it is installed automatically
by the npm-package), the selenium-server must be started as well.
* There is a "selenium-standalone"-package on npm
* There are lots of SaaS solutions that provide selenium as a service
* There is a docker-image (Docker-Selenium) that includes chrome, firefox, a VNC-server
and video-capturing capabilities</p></div><div class="bespoke-marp-note" data-index="23" tabindex="0"><p>What can possibly go wrong?
Of course, we have to make sure that screenshots are not changing. In this case,
the "18 days ago" changes every day...
So we need to design the test in a way that the image does not change</p></div><div class="bespoke-marp-note" data-index="24" tabindex="0"><p>Common components change.
This snapshot shows a different problem: When the logo changes,
almost every page in the application will change. This is the cause for a lot
of work an frustration.
We should have a way to ensure that we can take screenshots of isolated components.</p></div><div class="bespoke-marp-note" data-index="25" tabindex="0"><p>Different environments may have different browser-settings
(Anti-Aliasing, Fonts etc). This may cause false-positives.
Since we want to run tests locally on different team-members' laptops
AND in CI, this is bad.</p></div><div class="bespoke-marp-note" data-index="27" tabindex="0"><p>Storybook is a tool to create design systems. It allows you to create components
isolated of other components, without the context of the whole site.
It also allows you to run a web-server that shows the components on there own.
At i-views, we had built a testing-setup with visual tests based on nightwatch.js,
for our frontend. We sometimes had the problem that components were not isolated
in that setup. When I heard about storybook, the first thing that came to my mind
was: Let's use that to take screenshots of isolated components.</p></div><div class="bespoke-marp-note" data-index="28" tabindex="0"><p>Then I found out that there are already services and projects that do exactly that.</p></div><div class="bespoke-marp-note" data-index="30" tabindex="0"><p>Storyshots is an addon/plugin for storybook that creates snapshots of all stories.
The sad thing about the basic version of storyshots is that it uses the
React-Snapshot mechanism, to create diffs, so you won't get a visual output
and styling changes do not have any effect on test.</p></div><div class="bespoke-marp-note" data-index="31" tabindex="0"><p>However, there is another addon "addon-storyshots-puppeteer".
This plugin uses puppeteer to create screenshots.
It uses "jest-image-snapshot" to compare the screenshots.
A lot of options can be provided in the "imageSnapshot"-function.
The problem is, that different plattforms may produce different screenshots.</p></div><div class="bespoke-marp-note" data-index="32" tabindex="0"><p>Using a unique browser environment.
There should be a way to take screenshots from the same browser environment.
At i-views, we used a dockerized Selenium-Hub with Firefox and Chrome-browsers.
In order to enable access from the browser to the local-webserver, we started an SSH-client with a reverse-tunnel configuration.
I recently found the project "chisel", which is a small Go program that can do exactly that.</p></div><div class="bespoke-marp-note" data-index="33" tabindex="0"><p>There was a package `addon-storyshots-selenium` on npm, but it was pretty much
unmaintained and, as always, was missing some things that I considered essential.
So I started writing one myself. And now there are two plugins on npm that are
unmaintained and unfinished. That's how it works on npm.
The idea was to do the same as "addon-storyshots-puppeteer", but with Selenium.
That way, I would be able to use self-hosted selenium docker images as well
as cloud services with lots of different browsers.</p></div><div class="bespoke-marp-note" data-index="34" tabindex="0"><p>And I wanted to be able to specify viewport-sizes for each story. Because sometimes
you have components that need to be responsive, but for others it doesn't matter.</p></div><div class="bespoke-marp-note" data-index="37" tabindex="0"><p>Of course, when using a service, you will expose your code to a third party.
In this case, the whole storybook is uploaded to Chromatic (probably to the US).
You get a couple of screenshots for free, but only in Chrome
CI-Integration works by invoking the "chromatic" command. It will return a non-zero
exit-code when a screenshot differs.</p></div><div class="bespoke-marp-note" data-index="38" tabindex="0"><p>First we create a project</p></div><div class="bespoke-marp-note" data-index="39" tabindex="0"><p>Then we follow the instructions. And receive a project-token that must be
used in CI (remember to NOT commit the token to git, but use a secret variable)</p></div><div class="bespoke-marp-note" data-index="40" tabindex="0"><p>Once the command is executed, you can see the screenshots in the UI</p></div><div class="bespoke-marp-note" data-index="41" tabindex="0"><p>Further executions allow you to inspect the diffs and accept them.</p></div><div class="bespoke-marp-note" data-index="45" tabindex="0"><p>Setup similar to Chromatic, this is the screen after creating a project.</p></div><div class="bespoke-marp-note" data-index="46" tabindex="0"><p>Instructions on how to execute percy</p></div><div class="bespoke-marp-note" data-index="47" tabindex="0"><p>After the build</p></div><div class="bespoke-marp-note" data-index="48" tabindex="0"><p>Inspection and acceptance</p></div><div class="bespoke-marp-note" data-index="52" tabindex="0"><p>What I would consider interesting as a next step is:
There are ways to use storybook with puppeteer to implement functional tests.
This would be a great way to have tests that you can watch running and
maybe get a better understanding of why they fail.</p></div><script>function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function (_e7) { function e(_x) { return _e7.apply(this, arguments); } e.toString = function () { return _e7.toString(); }; return e; }(function (e) { throw e; }), f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function (_e8) { function e(_x2) { return _e8.apply(this, arguments); } e.toString = function () { return _e8.toString(); }; return e; }(function (e) { didErr = true; err = e; }), f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
!function () {
"use strict";
var e = function e(_e, t) {
var n,
r = 1 === (_e.parent || _e).nodeType ? _e.parent || _e : document.querySelector(_e.parent || _e),
a = [].filter.call("string" == typeof _e.slides ? r.querySelectorAll(_e.slides) : _e.slides || r.children, function (e) {
return "SCRIPT" !== e.nodeName;
}),
s = {},
i = function i(e, t) {
return (t = t || {}).index = a.indexOf(e), t.slide = e, t;
},
o = function o(e, t) {
s[e] = (s[e] || []).filter(function (e) {
return e !== t;
});
},
l = function l(e, t) {
return (s[e] || []).reduce(function (e, n) {
return e && !1 !== n(t);
}, !0);
},
c = function c(e, t) {
a[e] && (n && l("deactivate", i(n, t)), n = a[e], l("activate", i(n, t)));
},
d = function d(e, t) {
var r = a.indexOf(n) + e;
l(e > 0 ? "next" : "prev", i(n, t)) && c(r, t);
},
u = {
off: o,
on: function on(e, t) {
return (s[e] || (s[e] = [])).push(t), o.bind(null, e, t);
},
fire: l,
slide: function slide(e, t) {
if (!arguments.length) return a.indexOf(n);
l("slide", i(a[e], t)) && c(e, t);
},
next: d.bind(null, 1),
prev: d.bind(null, -1),
parent: r,
slides: a,
destroy: function destroy(e) {
l("destroy", i(n, e)), s = {};
}
};
return (t || []).forEach(function (e) {
e(u);
}), n || c(0), u;
};
function t(e) {
e.parent.classList.add("bespoke-marp-parent"), e.slides.forEach(function (e) {
return e.classList.add("bespoke-marp-slide");
}), e.on("activate", function (t) {
var n = t.slide,
r = !n.classList.contains("bespoke-marp-active");
e.slides.forEach(function (e) {
e.classList.remove("bespoke-marp-active"), e.setAttribute("aria-hidden", "true");
}), n.classList.add("bespoke-marp-active"), n.removeAttribute("aria-hidden"), r && (n.classList.add("bespoke-marp-active-ready"), document.body.clientHeight, n.classList.remove("bespoke-marp-active-ready"));
});
}
function n(e) {
var t = 0,
n = 0;
Object.defineProperty(e, "fragments", {
enumerable: !0,
value: e.slides.map(function (e) {
return [null].concat(_toConsumableArray(e.querySelectorAll("[data-marpit-fragment]")));
})
});
var r = function r(_r) {
return void 0 !== e.fragments[t][n + _r];
},
a = function a(r, _a) {
t = r, n = _a, e.fragments.forEach(function (e, t) {
e.forEach(function (e, n) {
if (null == e) return;
var s = t < r || t === r && n <= _a;
e.setAttribute("data-bespoke-marp-fragment", s ? "active" : "inactive"), t === r && n === _a ? e.setAttribute("data-bespoke-marp-current-fragment", "current") : e.removeAttribute("data-bespoke-marp-current-fragment");
});
}), e.fragmentIndex = _a;
var s = {
slide: e.slides[r],
index: r,
fragments: e.fragments[r],
fragmentIndex: _a
};
e.fire("fragment", s);
};
e.on("next", function (_ref) {
var _ref$fragment = _ref.fragment,
s = _ref$fragment === void 0 ? !0 : _ref$fragment;
if (s) {
if (r(1)) return a(t, n + 1), !1;
var _s = t + 1;
e.fragments[_s] && a(_s, 0);
} else {
var _r2 = e.fragments[t].length;
if (n + 1 < _r2) return a(t, _r2 - 1), !1;
var _s2 = e.fragments[t + 1];
_s2 && a(t + 1, _s2.length - 1);
}
}), e.on("prev", function (_ref2) {
var _ref2$fragment = _ref2.fragment,
s = _ref2$fragment === void 0 ? !0 : _ref2$fragment;
if (r(-1) && s) return a(t, n - 1), !1;
var i = t - 1;
e.fragments[i] && a(i, e.fragments[i].length - 1);
}), e.on("slide", function (_ref3) {
var t = _ref3.index,
n = _ref3.fragment;
var r = 0;
if (void 0 !== n) {
var _a2 = e.fragments[t];
if (_a2) {
var _e2 = _a2.length;
r = -1 === n ? _e2 - 1 : Math.min(Math.max(n, 0), _e2 - 1);
}
}
a(t, r);
}), a(0, 0);
}
var r,
a,
s = (function (e) {
/*!
* screenfull
* v5.0.2 - 2020-02-13
* (c) Sindre Sorhus; MIT License
*/
!function () {
var t = "undefined" != typeof window && void 0 !== window.document ? window.document : {},
n = e.exports,
r = function () {
for (var e, n = [["requestFullscreen", "exitFullscreen", "fullscreenElement", "fullscreenEnabled", "fullscreenchange", "fullscreenerror"], ["webkitRequestFullscreen", "webkitExitFullscreen", "webkitFullscreenElement", "webkitFullscreenEnabled", "webkitfullscreenchange", "webkitfullscreenerror"], ["webkitRequestFullScreen", "webkitCancelFullScreen", "webkitCurrentFullScreenElement", "webkitCancelFullScreen", "webkitfullscreenchange", "webkitfullscreenerror"], ["mozRequestFullScreen", "mozCancelFullScreen", "mozFullScreenElement", "mozFullScreenEnabled", "mozfullscreenchange", "mozfullscreenerror"], ["msRequestFullscreen", "msExitFullscreen", "msFullscreenElement", "msFullscreenEnabled", "MSFullscreenChange", "MSFullscreenError"]], r = 0, a = n.length, s = {}; r < a; r++) {
if ((e = n[r]) && e[1] in t) {
for (r = 0; r < e.length; r++) {
s[n[0][r]] = e[r];
}
return s;
}
}
return !1;
}(),
a = {
change: r.fullscreenchange,
error: r.fullscreenerror
},
s = {
request: function request(e) {
return new Promise(function (n, a) {
var s = function () {
this.off("change", s), n();
}.bind(this);
this.on("change", s);
var i = (e = e || t.documentElement)[r.requestFullscreen]();
i instanceof Promise && i.then(s).catch(a);
}.bind(this));
},
exit: function exit() {
return new Promise(function (e, n) {
if (this.isFullscreen) {
var a = function () {
this.off("change", a), e();
}.bind(this);
this.on("change", a);
var s = t[r.exitFullscreen]();
s instanceof Promise && s.then(a).catch(n);
} else e();
}.bind(this));
},
toggle: function toggle(e) {
return this.isFullscreen ? this.exit() : this.request(e);
},
onchange: function onchange(e) {
this.on("change", e);
},
onerror: function onerror(e) {
this.on("error", e);
},
on: function on(e, n) {
var r = a[e];
r && t.addEventListener(r, n, !1);
},
off: function off(e, n) {
var r = a[e];
r && t.removeEventListener(r, n, !1);
},
raw: r
};
r ? (Object.defineProperties(s, {
isFullscreen: {
get: function get() {
return Boolean(t[r.fullscreenElement]);
}
},
element: {
enumerable: !0,
get: function get() {
return t[r.fullscreenElement];
}
},
isEnabled: {
enumerable: !0,
get: function get() {
return Boolean(t[r.fullscreenEnabled]);
}
}
}), n ? e.exports = s : window.screenfull = s) : n ? e.exports = {
isEnabled: !1
} : window.screenfull = {
isEnabled: !1
};
}();
}(a = {
path: r,
exports: {},
require: function require(e, t) {
return function () {
throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs");
}(null == t && a.path);
}
}, a.exports), a.exports);
function i(e) {
e.fullscreen = function () {
s.isEnabled && s.toggle(document.body);
}, document.addEventListener("keydown", function (t) {
70 !== t.which && 122 !== t.which || t.altKey || t.ctrlKey || t.metaKey || !s.isEnabled || (e.fullscreen(), t.preventDefault());
});
}
function o() {
var e = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2e3;
return function (t) {
var n;
function r() {
n && clearTimeout(n), n = setTimeout(function () {
t.parent.classList.add("bespoke-marp-inactive"), t.fire("marp-inactive");
}, e), t.parent.classList.contains("bespoke-marp-inactive") && (t.parent.classList.remove("bespoke-marp-inactive"), t.fire("marp-active"));
}
document.addEventListener("mousedown", r), document.addEventListener("mousemove", r), document.addEventListener("touchend", r), setTimeout(r, 0);
};
}
var l = ["AUDIO", "BUTTON", "INPUT", "SELECT", "TEXTAREA", "VIDEO"];
function c(e) {
e.parent.addEventListener("keydown", function (e) {
if (!e.target) return;
var t = e.target;
(l.includes(t.nodeName) || "true" === t.contentEditable) && e.stopPropagation();
});
}
function d(e) {
window.addEventListener("load", function () {
var _iterator = _createForOfIteratorHelper(e.slides),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _t = _step.value;
var _e3 = _t.querySelector("[data-marp-fitting]") ? "" : "hideable";
_t.setAttribute("data-bespoke-marp-load", _e3);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
});
}
var u;
function f() {
var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref4$interval = _ref4.interval,