From d99c4aaff9fbbda0e72bfcc3f9eb42491509bc99 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 30 Nov 2024 21:21:15 -0600 Subject: [PATCH] posts: finish draft --- .../column-chart.png | Bin 0 -> 20890 bytes .../index.md | 190 +++++++++++++++++- .../stacked-column-chart.png | Bin 0 -> 27303 bytes 3 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/column-chart.png create mode 100644 src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/stacked-column-chart.png diff --git a/src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/column-chart.png b/src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/column-chart.png new file mode 100644 index 0000000000000000000000000000000000000000..ea8831f00492cb563557b352d09b086fe84f7d95 GIT binary patch literal 20890 zcmeIac{tQ-_&BViQdtT`mZ7Z4mL)Pm*>_1L*|KM^G1-?wBpO*K*|X)y9>O50&@h%{ z$y&x3*^O-&=J$+F=X8$V>-T=&|K98RcGcC)eD3Fd?&sd0`+4q9#3g+V`a|r8sHmvu zwKP>PQ&CYvsi^)CJFp-8;&ngZG5Bwf$7Ky=sv->M6!@~&;iBF}Dyq_GT9Va1@crOz zO%o3)Di(gqzdc%)`4*_C48^rnFJAGsm>qWZHl0YqZ*AEHc^7Ye9rhqC2lj_2ZF$A>xV>6&7h*xMp<(JQ37 zS6K3GYzw2+@vt9%yQIk{z4tVoq1*YZ+aKQf^O5@S5$KO!>6%_}D2M<2{jLiBFKD_f zw*!#B@L2V~JuEAD{2&=uevA0Jn1zwBgB2Qkv8{CJc4lVGF0&{?T4261C*$`6sB@(ftd>f|R$ zz$pN41SpR~8NkPT)k)|0=O0J=O?E^@Hwfl^Cp|B@Ssu@>OhbVi;`F~DCO0i2hfdbP z-VbTy=($#6qD`Nb!FJkJ(R zsDO1nkZl>`$iwz*4==rtd+xE6F;hRe3G3<;k9}8jaR3SB&npY}apLJ0$qDP+1IRt& zOf%nLCe|>?#>PD=c-YLN-fX#P80O8xaegwL!&@rYC=Yvci9yTEJB^PE%DJxjnN~(8 z|Ktuo+eSiL{KU_n=(F&S7+QSiy;>GKUsGx7M)_JoWMfmBIBU?p_aDx<`fpaiEavQ- z#|_T&X5rq#GNpXPI*2e8NmjM9M_dvwtKcadd|I(coh6y(tIoAPLWK32$fDgmzUpQ+ z)fQ0*E*bD%3LC0EXMX@f0X}pTRVTe8mIZ4caC z5}XCaYp+$-TZ#E=&pk!W_*W;6W>F+cHSv%1l==VW%#qXoXv?L8e=j;z8>m8iR3_nT zIJe|aO}{Qhf&CPQF?Fxw7c@wVA8(FE`_0!GuTb#cHZ5WNlkF3Ku7Pge-a-Czb}E7i z+O3%?;lb(Ll;_e3Q%64uH-;tM#dDs&UOQ?*@gh_eH=%a=nS_n$Vj3lZpI|?hc>24F zpoh2W{%dzNO<$SDZVRs-*6bs(M5yvGnfD8;IFb`6@Nad!@SJ!g;(>X0m17}XovFT4 ztM$#d7krgTHLWf9**g(MoMP(+6sCqE=Q@+(<5e%>_ip=VSbn43uya}Rr693V#~(iy zig`En>E~K|tehe`B~Bkm&1%+fA&56L{`3Nm^AZn0vVwQi>ZX#0O1La91%tGQG~CBLIwqw*5e*NiqM|Zqs85PDKD^YQ1xG1hF8pVNPyLV*0HQ=xgDfU>m z@Ag$D^0nxfqpwB1nW^LuVfxzp6WMijC$j2xi4f?uB#g)LsK(%-Hw-@GPA zmap4awdZDo$k~4ak{;zPZXTJ^ z#jBDuMz5WGF5sEfaRF%pR!Y28j#d0{pfEv|{zmqt1}WU(~!l`5=tSKPf5QJ6B!xMOCt#i-UZbVgOxfrQ>??Nu9d@xAUA z5YjaP%bC~L5?^jRcFh)~4pu5$hASNp%b)diGw&9imT?iij`kX=RjcnO);%A@*v$=8 z*Wv@u=w{_6a?+<}^YI)^IxjqVq2V3tGWz=0$5a_Yy^1wPia|Q=?KmULT4lGoY2!F< za9K)=;cadzTXXWXsM>VS7b3SStER>`TyS*Ic9q{m#~}2Pn>>;@ z+d{i9h~~&OL>Q)I`NNGI2d_EKR6qG_yViTz!Lro%1YX?5lcq++pOCj6MDhnt9mWt7=rGZtxOJC^4Mu0rew%MqO7Tk~ z4JcLkA(N*=$a)nj_zSceD$S@mcmuq>qrMIfw~D4)LKIMm7@|9g<>gS6UT4telC0eb zzwyWB`V|OqNUs1Jt$3%%%cszKXD&57mbbZ0qrOa4adEg{VjLM@tXTchRBdX;n=i33`pG-fWhV!smesvAp#v0urR$c95HMd0fpM>A*#<4^9#SrFo9u>(NO@m)D z_5nwhn6HNS_a&~?>2;7-kd{w^)-j*e`X(o%l}a-NN`d3cY3GOAGs_Y4?I?3ld?>ny z2fn1?yuCp}KPsZt7yUt?4Ezx9kZHm@d*2R$7` z_aA^{ymC@*=9UYRXo^W0>*4V{0J(J$Pj$}@#{`Xu6kC%yH-SMXx#5`PLm4jJ$jOvK zwzPJb+5E(r`=yh(yjDbzk&#M8N)3}1m9DHw$|%#EDva&SSHof*!~oS7Xn}JUF>=LU zfmvYSMtXqs3y$UVslhEK=c-y-4J!VD(ZiG0q#CAN?N7^zJJjS$19CE64TS0|@CYh> zeg}NLDDdnhx;dGIDvxsjf*5CjQQQGaQqD_@iA&f1hqTyE;-vFrAed5WaEIl&v|vi2 zlS9`^MT+*{Y{><1O^M}okI`lwV5My_&y#j|D9j3d9F;<9llhw^_8d_1tWJf(k9qZi zY$>`Rd<@$D5P%8%1I7&HuFF6VzUbLNrA*n^?93gUIUb$T;Bp(VB=>J=Cker~^D&bX zy0Q}%AJl<#i7L%ufWPp+86stbCp9)t2T}NTdS{*f`2M4EL?Konk5?z)RBt3dJ-`H| zj=mDtGCy{V)L&2ttRobsnMR&+km1E+!R@U>AObi`~tXt=5NBa zALuVXoA*h6VxK~hTB zUiO2(J!9GMUBH^=G)N6@Ci&x4iz^AJl*@5JQhxwAT6F~DhfWE1_1m-UUX1bw6e#)^ zcQ}=NeqO{q&T8C8u5qzp`ioMtxS;1)%G(wq5j$Z;4+-hN*v|;?u`vTmX;;&cI4wwK1 z>}I=pa^2&mJWRE&+g7{#BN?vB4&5EB8L=%AuO3k56#?_!D$7d?XdOI5YP3ujidG={ zH;U8IquT`FQ9&l_uGTb}6VWMGz0My&)$l#03_n0gnxBaPJ&d07>D(;GQ5GC(cs9o7 z7KLy3XDK+J7h8DNRcXD(|M@u%>3EpJ&!<;7`0@iBJv=ZvV+t>BasX=CXGX1?}a@cs-LLj8Mu->OaTSnBST-2w4g#vFC9olj%(nL@_1f4 z{2<0?5LrNEgc^}b%YB~oU)%z`z9cv)Labj4RZQN&Y5k7Dsg+xwUXP%9NoH7J}V!g7MheF+XLS!PN{EMebu)x=W zMhoA;fiU^ z5V|Owf)TD{nekbA^%g`(w$3a^(p`gIph_{ar@>btg)acAEl zs9h-XOOg+onOTRFh73@6{Ri&db%L$w^p56yIq3-(ewY%tNukUMC@yX-aZfqOINpfV zd40I-|Q{_MKVWS2q*5Y8iDy=Qb>S;f}U zZcg9X;gH`ugqT$XARu2fXofHSgy$KG=eM^ae)mHO_WJHcYG+h#!KUk5rqZBJDGoi^!Ft^&;KymIue&swxQt79M^XLPD0B3GY;Jm8J z4zIsQ_8-i9+fqu_)aS}HQrhnKAqiR2yr-yxde#q!c{D4WS^ng==oIy>H<{NCq;4!V zW9IUj2tHX70Pa-QW@2rfV23)06dHKx{iVzdRuneoD+SGv=GJ%{3%;HKb84@zUI6Q;^243Q8SaikiNN{|j<2{e(Oa?!UPC2g>*Z|m+>?;nl0R%2o<8lM% z50-W9FsA+DO#ekAiZdA}+Dzs_M2SqEja= z{6)Rb#@~^Kt7jKi=t0F?6B0F5dAb;;(kBetB0lA@g&Ce1t^1lc!aOY4QLT&M@+-z~ z*kd5_UPoQeyQFWX4;jIgUg>SOVB~a)gliBD@WudRXG;=?sIGWv7x7OAhX;PbZ}=hdZ#C|7L4nK=gu zW(^e$+gob7-=@{`5O|RtX80*X+R3ZDfHWaL`Ytu}sKuKx4TQ z>)#uKGFZ1{gU)_?9|Xg_g&UP0i$;+~44KRf?=Um3IVppH{h?lyZ!S26KCR89ej`ME zSP7>PF|fP|`*;%mHl#l(4TK?Bx*tVvzK%twB3C`{Ulg`qIAP5oTojj-7o~ znK`f(AJDakAnAU&@vI0P2|rLn28z)DU>MT4)-D})c@ zoX~rXy!`RfLKxMNg<+1s?+QW>cC1B%#@IT%C5bDOIdHpQ?#$}71*hw6-JNWSX}5rF z9M7cA;8|06{mD=A(BO`oxxUzdpzP&F(pzvk0(AoZZAIms{`KqZP_3yn)|hvQu?eb6 zX+*6-4aM>nKc2rviQTXPlR%U;4hPLVC0wvl(oqrQZ7TS}=c(2}=f<(zeqg?4f5yu* zJ01d`CF*&IHyl}@7v9;?}&fuWlm_Qcr`DY8zQ@J3`L*pBS2J328}qTu;3rJ5H$1ni({)JN9)?6Y^>okCmPXAyX+GZsFxPj-@5NL4?( zJjjXtZu%`awJ~LmGX%|KfB4i`(6tv>g|_;kz#vQp-5h(&vg(2-m09*8h5BZN%J&R0 zM)IKuQ&Oo1_EhO7m&7ncOiZcyR`+z>;Em$tdG_(boINjg;@zD%QNYX|mdtT3pqL)Z z*B6|mpHyT@)hMO~RNs&mD803sJhEJy0N~6+-E7<2ddOFPnV-ZWR#alYfjK8%!zckO1m)WdSQ!+`Ko>Og zdVyNr6etawR7|d;m>nnbrr4qsR4x31RtCpDzhG3R@d5FB{#`5h zLcOUxxxk{8!Wh#M9LRz;ArTCVvW| zl?8<&G*y{$Mn1#*lzC$I0@FPA<~=2+gv3kr^)G5*!rJA7jk6fQcMwh#!O_AS`i_R>XK_2ieyR_uPFQK0%n%shdLZt~f>X~uyG&k5l%zn6k70!P zY~sj@sV#cLVyeDO#hSpwWZGIxyYE$ULx20uq;1*V{X$Wk7Dc_9p$xeo#RjQ=u(1u| zB`sv$HOKfJKUAD2M$=7q{pE4&Y|fquvmHZkbOH4wQVfAWBNzMWRw#o#Ak~QbhFb4GW{oFxm(nX z$(Jzo>(J`)k?>&I_aY$ad-jL`d&CHR(X&9iIT1x*f-SV?dx_S~g|BInOob1*g@53M zU&`-wHxun!*>YLxDCY9l!PKooedcnEY2Ct;Ojw{Q-Gg6F7uw|+hXs=l-s}8^O6fiT z+0wU_JezuU?fj`xi|V4W`E&lZL-O6ss9Cf93B2%S+M>8>j0vkK(x%yfg#Mb7XizI7wwWyOSRTtP|t3WzGjxJ-}6V`XEeN>7X}{F*k`>zrjj zDy4Z<`>*C+RjVrWJdm3E!=3O25}rimT#nU#+}hcGC!%dS(RLx1N_q0*bl)8dj5JuB z69j6%eaEDVq(?FZbvdz^28Al4?e8d^M{ZKclWV*EBf#vH!Pw}|)(!{v?+G2laGS0P z4~TLeG!zlIasVP_FPePN!Xok{{23=y9k=k1uZsTTSY>up_Ru-UL!C2IlNLBsipdGU z6$gBe^MO=wy0`IJ82()@RNqKNQ0UEty<@j{7gG5MoMs7~mDI`Ar3@d1bzz{Ch$1yQO@10FnySLHc45=C_&bK8;> zMu45Z@Ly!F*4&&MwP+o_Q+-_mo1Hrr`oJvyg-6wvhd-nMn%%znAk($G)alY?=0=V^ zCBr0lizXkrV?1zBOc=UI!P}4S?rhbB`RMWxo_Bj%DKoKFYQZg3tbbJA;ZsPCp&(uNm}h1VocdN%+h?N z5eBk{n-EZx=TO!Fp%Jn(qE3V!64SM^_}t4pvY4BGgznx0z!q?U;+N&fz}pN{N)Ry;`4F$CoiX@Q-@MbPP9QXv$#Ph-a%-Q7>>z^;$(>#&ysxQ0IGH(z+Y7CEb^x$ zKrlr~;_A#moKZ9kdKOqE!M_$H{9#Ip9~W7 z_)6La!K&e{jG65%+A{x@Iwl2&d`|s3W|R>7zwE$E#esYyLY(VKeALyWQ06=Xsi*9|zzclotANUe1^=$Cn;$!zH7=_~XU^Iv3wkTLB;9~X!BNPUcUu!*5moFmJ(l|q z`g~20Wy=*JYz7pgK_#HqWt-?cn#eoed}#Zz5sk8IovxWOetpb*a<%Uk)0>uM|jQ-at`}vk-7viDZME?>e!gRoXOh>_{H4ton@dYkAc;;WU|{#HSc%-9oS9jpE`4Qs|ABKspk%p9_+V{6L3+zv z{|%A^m{>QZP7H~bB~LhQ|Fy29-m=I4hMSVnkWv3hak87}AoO1p3suQ2@iYg^a-BN+ zm%IST`G12{$ZfdHpB9eBV*e77cA?$izq!}g9s`Pnf5|A=S&r^sL@<(Z_;T52Epq7Z ze@p1^piU7ER#s!6rF2tMKkx_V{|S_UYmDV`(D4r(%FI9I@~@{cyz^YNSAX*SpZI?O za)M7krLDy>wTto3-_Ck~Q)#iDwBjIs!2EZ^-@rg+ve(a{OICRBuXX>OQGt?jB&g0~ zZL#oCm~1ky22j`1#Ni)_PbA0?j)B9S_81PJe%H}|MF%xJ70c;ka{H5cW&aE9F|!^K z8)H)<2cyJi=YhXm`T@uro@NA6F*dFl*Fq1p`la1p86B8kCwo*lh)*#3H?us|!yb1* z3<3Are8cmvghQF;R+(2cB>n1dA3fLRX};-!-4D#6x9qQE3&^RZg94? z+U(}m;~x3n()(s`zGr^IDR8$)o@S}5{cd_W@9CDVu-`s9eHQw=Nv?do?S%UJI~B@M zZU=?`-CA<&7C77c4M{n&qi$}$eX@_^cieqLH{v}F5&rEXX_wzlZHHQ{H2*EFp_C%A z*zXp&xBs`Q=+6MrH~Fo~!5AQE&{z*}-(g>IJ)c*uQvEjq7( zo%x+i#$JZFqaeV??ADf)qaS|zGhrjMN}269mU7?dEhx0_qV~R&h0;+BID{_UY=Ue^6G2VME;N}<(8Qsb8tBS zU+t7tnOuKCH_LS_PA*MBfr%fk-2PxUQEp*~QPms!6F+26PK_PQ(MwTmzb+$Q2NfV& ziouGi^BeHKdLA>_*5~fmDx(KD_s!EtZSACm8WTU^#4BMJ+mKwtVZs zWF#e3w2+TI4U#c#W@zK46}l&OJTop4TlVx_U-bUsQ`7iT=B2m7?vjaZmF68S8j2qE ztl<2KXxO##1-G_oA--X6x0&KI{T-m!RqAsexv_lii(-|lUpBZ{I1yekgAwr?W2MaZ z<7_BINJ)OJ{yHT!optVG89r2^KeId;xO!hRf2D{PHnazvk-&Bv6*4O`E?$*yv$xxb8M#f}IM}FOwA&cKf!L92 z;tf!o%Wion(`0`2{q4^S-GRSz2qj!GQV({yqBl;e`X_hmsdL>q-zO~xcZa2t?#RuE zl*_x7@nyOD@?D<1-{^e75xGyhJ$A{57Gau)DQvsr?sY5dF>qS3+Ym2)Vw5Z29gRD$ zh@;xjpHJ@&C7sf$`V+fJefEurwBUvP`DRz_>AbC~zp{G(`=gd5j$KR&2|EeHA$D;z zKA9+;@C({*%$)jx?^mS&-> zM!H$-69bX!r_;%}t4sOCA_@5SqoOtKW*}>}15D5Cb*^Rv`vJON5?i(JrE(j2bMt7y z`hW_5Cljx(yA_n(rj-Xa%=hJl?_uX^$zQVFqgEc-+<=0T#FbyN#H<6!yjv8hR=oT* zX;$m!oT8|}OS@l_?^ADano5AJH$|s^$%-`?Wi&P@q0`yq*Ua}(iCi~DL9e5KP3sLq z+hZtM+?yM{zodi+9v8$i#kusB{hFZvFUkMxJ^q)=f9o<#7XI&^qp30ax8c9hs~;^5 z8vLd0J$mZu?$J8%iQ5b6nN7dZVhwOg5TU6YOH|^*BiF(4qcF-4|62{FbRU5$G=-`1 zo^z)*!d?DOc+zbI4OkSy{!WO1p&fP}NHwWm$)psd2DaZ4K!r+3sTiVli~@Erb!@A| z?w5Wh&}iW}F;qDlb4h;zBs7zhl*gC!_Dmxs-DzwV7hy(m>k@(IkZNo`L-(H?C(J5% zttl~|U%B}OxPuA)^mlZ1b`kCOQa+b)(nC_+#p%Jv6X;=Z9~!jruz(AGpe^eEU60c# zEfYH>9&}R^?Cx{2mc5ohVwUC@D&A#V>Y_t}Ljc3Ob z?&lOzj+nD^uGaJ()~T1(koDAZ)=>qg8BCUErG2V74O&}rEvC*%ogS8lAt89=YvS>J zlthY1KA<%)ELh0)r0Nl11om3nn%3N|yYo^5yw(YzpcMUtN^} zR56u&{onws-;4j*>AF1QZ(TDRkzzA3jif#S1-?4ktT;>Z{VgJyhj$7Is-rK>pYIzD zLX&GRloRV^aDIeG7quqR8_-*^gwwrB&t5*7#Fb4e#U*x`Z^fPyKRr7=V3(kbKPB_( z`BmT5z2b!x5>u9E2)SOicMIUI=)L0VkjI75IxYhl+3m~|>lu8{tBZ}CE1|50};SbIW2T)vyHVwQsD1 z|LCrgHL?NMms}DCbTg+A(H}eKT-v->mouhzqw#Tu7?TvR^p@dWONZ(sIgc!_XzaHzO*4*%k)>~uP=T; zaUY1%B0nE8IMnv`(L_-28Id{yZ+DG#%chnJZpy;Z{OQ(lTrw7GKwl-u$1)o;!}4Mw zqF1D{_v}FWiXgH=*Y%-bn}O&OnVwy=yvq2CKv1_SE;-*|K=A!|NcXYnm`wWyP9}a^ z+jX4GRR7cA)x8Zx?=3%FPX0#Uz1TW*SR8|oIl0=fnxu(-)7{@=*ndc+d5~`+s5{7C zwO1_aXkpOxoBkJS0F>3dpKEpJVgY=sX@7#EEGt+JzdP;W%&gBvbI>|Bmg>RQmZ*CtPQ~|5n!2=|DFIK;`=?HLlF8s}Y@! zrpBmq!L-hp%yCGVYeIdkABtDSNc;SrtnIexppLoI=kxtPxaT$&dtu&{ET?qwhcr_o zv!>b%dn<)2byHuPeVe{PI!j;Fnj~vFFEc@di$cL`t0vcbL(AQ^zg`EQQ}`;`EXQOqhKseT9HMwb1}Wa$fAnYFRkh4BWx=@wfLAs{dxCV)bx6*DIIGQ@_L(PkT3YYtx_!F z*gIjDCnGUW`rmGU^D<6Zw8f6)dSCaoDeNwn_d;OmAl+qrYq5u<%@+h$ee6b~PKd9Y zJwa-10SyejI%wNQT0al z-Kh(!95(!!<)_x|_nI^nSU+%=P2qZix!JQ|zn9+l`@|z+@!eM0+yx>9n4npEU%!Ey_@`CKxwX0nh4Z4iUA8#qV^pH<~E-Z-ws9b z8Si^b>5_jT1+MXe$3Oe6!Py%415P|B|NRepdT*Qgx;Ma6*%CDQcw%muF(s?>GouXe z0~dcJrlkzrxqYT17OBY>`^1E-N3+4t6`|?Bb^YsvJk!ADq+X z2-WAY{V_psjcoTo5axj0@Z7ecdXoa09mdLir6B~jqHtbcraaRClRemcZyDfbUc(3!lgSj2rR2RUZ z4At%a{*BkFJhNq1OZX2Dmn+oy%1SIhz`8mE)s%0T@-QW_#=W~%uGaAY6YM$l&|Z9g z=-ji-`A?FldSSm!&B#|`kSB&7udJ=F>`$azD_DJ@4Yw+$OKMrzf|UB)nNm6-StGGF zTJ=h&mgu*XDU}vK^1w(V&36rZnqNWM$H&Fc>_*Vl<{I2EhBZM^n755Xp7Vt zO_N|7{yeTAV3b~#$?^HEtD&zCCP_A*8jb@-a0lFfn!rKMlOUcCi!TlCB zt7hp{jo= z;f&Ybk0hGLXr!zwKAt%AhqyV1iEHrb1-ZSJ^^;1hr@YPeS6?9JZy{`L(cNPK=;83? zl4Wt%1eVbWoT9CYpx<6Ps!Qx38DvOq*XeU&f>sa0-K51)OKR$8bWxoV+8v(;l=Qb# z^Y=`(aPRv_u#iclO?^*2ki`z>WTk_UXFmqG-S2E2sS+E2onMpCu5%HMTTSDr(?ZIv z<>EcsAcaot;T=8Am=9O;=%QFhXn^V3gC>}JXqdbD5tHlSYK8rwRN_Illv$m=G)$WJ zXyDq5sM)FZdb7eSc+QS`xTvSvqD~*mO= zQx~%e9WvTR67I1&;Y8`x0F91R2s8?nxeRVq9)T7{%IAo#3G9=qXS)v+m}iseWg-#M z?S?TjsZo>6k=Zi!a1ZS47D$b3XQUrZW=vsiua6RDN2bkIOBr=HbyuBRv)_yKWC=Lm z@ycD?9Oog`H-k@n${K9suHI=sa<8%0eM|CuQ*C9!m2Zj;qfr^=&qv1D64pUBn$#?s z8lOfK9cY+;yE@~eU@AqS?~ngU&Q5WK|HBs=gpO<`9X=jb>Oz$<06a{l?DpJ2HnxB` ztK&K!UTT3`Aj&is@JV`~ox>4(#rJpawHIZAQn_5fQ&GAhOK8A#c9u|vd!3*`#}Bw- z7tn$8|NNj%TiKzbU~}T@WLiSIU--o5E1;X{o*X!q>j#}cM|=}PF<-Fy#LJ_iObuHy z1H^CjW1o99fhV@sSh#$}+9d#l^yXmJj`nvi=*$Mic^lEmJUd&s#>nZ}iNL9PKWXnN zK4qGFl&ZBfj6g}3yWu^wwhPiw>7S(3EV+UU%+-BmR61_!TyJZ@@Rh--`kC-qEv5lk- z-^yEC)tO`)NbhMDn?+g`3z3pPQKB|7&;#kkmwoq+!)i+>>M7if9j&gvoww!jFjX+k z$Iv%$*`w8e;bP;2m-`nnFU69=u`*@eZduT^;XxA^i?;4A7u+O&^7HjbcfJCJi6<>_ z)$*Hne~Ar1ZKPoYVyaz}t6eN~zhI*D!w_;zICCz?n95@zu}g@L{QO<)$W+C6#I0z$&0?jrM0in~b^r#ni)`!I3^cDt=}(O&Z_@6#i|^(m zl;)APh8#(9lDwx#Pmaw#qCiSP02=vFID*L5xxEOyH+R9^?{@x*z!94Q@22ct z(^)+qakAQtAa3_Qv3-Owl_t%fXLB-K#!uW_ercq%Rn8N}IbW2au9Kf;#tJeS0n`esy~SPHhcJvudiaIBtIp6sFF_GXkK zC-=2DY;VDc@0-B2J)YvnkVFqLi@9OkOIjp)B!+3SoO9&FtWK4tlPU7m1lMFRuU_@k z$kn-0VSi2m%NiDxW!#$5!fU$^JJEftOed6%8G0Ma9ngZ&oo4>d7l$VvT_tY1#O^Z>u zs92G2^aZAIw9PO$-fKknE8~N&XCd9?!sEl0JqDAsGKKpz;2*Ms4DaA|)3U}P_ZQN$ zq(tgUB{kq}jI;!5;yAeJ4UO!qhLwyJzwEIwP!R7M4YXXauJ$4@RBw%lHbzokF0(Q; z`$jvMi@B>suQ@rOAQ>EL<~BxdLz zXaLRvB}dTH-wMJ#$otDQo}fDN+a}MWPRoolrnY?w=q{BMa*rp<8r@a}uQGWxsod;8 zgYauE)1Z+O(wf+w%LwiiBKRZ*a>mA<^DESy5CRt@Z-NGk3wS+NXw|H@DtO;XaS9>p z?9w~u&AKY2OpxMcrQ*QKl1?|dU(9jqt6sO~v=gOr*m&6RIMm*+s#L`qXLoT#?&#u- z9JJnR==3=ahk?YI>JxUDssr0SBS8p#5~TxXSisBTkr%_IZL<{EB|3wjL;^Lqi7y%}A+BP2#XEMh zN4{Mzb?piieyF1>A{~-U0C&4^FmHsm9L<3!4g$}c5s zq&RpX^pz_)#mMft3!JI1U$vuCvuqSSw>=_0HoHux)ytnC}5^ zvoXZIVEmax6!5`&_o+-SvMJLXlMmQX89)V84K0js+Ub#t!5z~K0U|HIn;3}kN zf4}dD1^Yd4FB#-4=J=PySze2ll+CQOlFQ#;3@zdr<~t^bdIh8Fm!A(+3kz9Z4=7K$ z>zq!f*ulUeRq~xROr{mhk-a&>G}tx?2mjVl;P&#|LX$>nXAQ| z-pL@}0&hsW3Eq!z0bl3E({v`vj+&|+Iazq{_6`ipxjG! z!Khsh&H#b(3F@aD2sERRbL-LCXwc|%yxao}hdb)zJc^*dV6#(MHhQWkk$U+q{;AJ& zVHI}aYZGjyzjUm5XWt&Co%d-8m+bDF%H)C^)8&BX#_Hmqs;K0qfLHH@jEz!WN=S9r z&Z5ZP_nj(YDlQ3uYda1l_Dj6CWjLHPCLy@})XT=^=Nkd@*Pd>lagPPC@KzU|=C-KJ zIBvoowx93$dv>-_J;}`;^7p#|&seej-(L<$pZW6@gHhmp06(WPlnrJ0`TO^){{gpZ@__&V literal 0 HcmV?d00001 diff --git a/src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/index.md b/src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/index.md index 319786a..826572a 100644 --- a/src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/index.md +++ b/src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/index.md @@ -3,17 +3,203 @@ "title": "Making Visual Regression Testing More Manageable with Playwright", "lead": "Dealing with dynamic content in visual regression testing can be tricky, but Playwright's feature set makes it so much easier. Learn how I used response interception and modification to ensure accurate screenshot comparisons and more reliable test results.", "isPublished": true, - "publishedAt": "2024-11-13", + "publishedAt": "2024-11-30", "openGraphImage": "posts/making-visual-regression-testing-more-manageable-with-playwright/og-image.png", } ``` -Recently I've been working to add a set of automated end-to-end smoke tests to cover reports within [Onspring](https://www.onspring.com/). Reports are a key feature of the platform and one of the key aspects of the reports is the ability to present the data within the reports in visually appealing and interesting ways using a variety of chart types. Initially I had taken the approach of building the reports and then asserting that particular aspects of the report were present in the DOM with the expected data. It worked, but then I remembered that [Playwright](https://playwright.dev/) has a whole set of features for doing visual regression testing and I hadn't yet had a good opportunity to put them to use. This use case seemed like a perfect fit. +I've been working to add a set of automated end-to-end smoke tests to cover reports within [Onspring](https://www.onspring.com/). Reports are a key feature of the platform and one of the key aspects of the reports is the ability to present the data within the reports in visually appealing and interesting ways using a variety of chart types. Initially I had taken the approach of building the reports and then asserting that particular aspects of the report were present in the DOM with the expected data. It worked, but then I remembered that [Playwright](https://playwright.dev/) has a whole set of features for doing visual regression testing and I hadn't yet had a good opportunity to put them to use. This use case seemed like a perfect fit. + +> [!NOTE] +> This post is going to talk about the Node.js API for Playwright. However, Playwright also has APIs for other languages like Python and C#. The concepts should be similar across all of the APIs, but the specifics will be different. ## The Plan +Playwright provides a couple different tools that you can use for visual regression testing. Ways that you can capture the state of a page or part of a page and compare it to a previous state of that same thing. They primarily offer two ways to accomplish this. The first involves taking what they refer to as a snapshot which allows for comparing both text or any arbitrary binary data. This makes snapshots a bit more flexible and can be used for more than just visual regression testing. The second way is to take a screenshot which specifically involves comparing screenshots of the page or part of the page. I opted to use screenshots for my use case as our users are primarily concerned with the visual appearance of the reports and ensuring that they continue to display the same across releases given the same underlying data is important. + +Playwright makes it pretty easy to accomplish this. You essentially write your test as you normally would and then include an assertion on a particular locator that you want to use for the screenshot. You can read more about the particular assertion [here](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1) along with details about the specifics of performing visual comparisons with Playwright [here](https://playwright.dev/docs/test-snapshots). Once you use this assertion the first time you run your test Playwright will take a screenshot and store it along side the test. On subsequent runs it will use that screenshot to compare against the current state of the page and if there are any differences it will fail the test. + +Here is an example of what this looks like in one of my tests: + +```typescript +test( + 'Configure a bar chart', + { + tag: [Tags.Snapshot], + }, + async ({ appAdminPage, sourceApp, addContentPage, editContentPage, reportAppPage, reportPage }) => { + test.info().annotations.push({ + description: AnnotationType.TestId, + type: 'Test-608', + }); + + const fields = getFieldsForApp(); + let records = buildRecords(fields.groupField, fields.seriesField); + + await test.step('Setup source app with fields and records', async () => { + await addFieldsToApp(appAdminPage, sourceApp, Object.values(fields)); + records = await addRecordsToApp(addContentPage, editContentPage, sourceApp, records); + }); + + const report = new SavedReportAsChart({ + appName: sourceApp.name, + name: FakeDataFactory.createFakeReportName(), + chart: new BarChart({ + visibility: 'Display Chart Only', + groupData: fields.groupField.name, + }), + }); + + await test.step("Navigate to the app's reports home page", async () => { + await reportAppPage.goto(sourceApp.id); + }); + + await test.step('Create the report', async () => { + await reportAppPage.createReport(report); + await reportAppPage.page.waitForURL(reportPage.pathRegex); + await reportPage.waitUntilLoaded(); + }); + + await test.step('Verify the bar chart displays as expected', async () => { + await expect(reportPage.reportContents).toHaveScreenshot(); + }); + } +); +``` + +Most of this test is setting up the data that I need to create the report and then creating the report. The important part is the last step where I use the `toHaveScreenshot` assertion. This is what tells Playwright to take a screenshot of the element that I'm asserting on. In this case it's the portion of the page that contains the chart that I'm interested in. Crazy straightforward right? + ## The Problem +My plan was working great until I encountered a small hiccup with a couple of the chart types that we support. The chart types in question are complex charts that are actually built from two separate charts - either a column chart or stacked column chart with a line chart overlaid on top. Something like these: + +![Column Chart](posts/making-visual-regression-testing-more-manageable-with-playwright/column-chart.png) + +![Stacked Column Chart](posts/making-visual-regression-testing-more-manageable-with-playwright/stacked-column-chart.png) + +The problem with these charts is that they use the name of the data source - we call them apps - for the line chart as its label in the legend. This means each test run when I create the line chart's underlying app I can't really use a different name each time like I normally would. The reason being that means that the current screenshot that Playwright captures during the test is never going to match the previous one. + +The simple solution was just to use the same name for the line chart's app each time the test runs. This would ensure that the screenshot comparison would work as expected. However, this approach breaks down when I want to run the test in parallel. That is when I want to be able to run the test multiple times at the same time within the same instance. I found this out the hard way because as part of my CI process when adding a new test I require that the test runs 3 times successfully before I consider it stable. + +What to do then? I found myself in this rock and a hard place. I've got these immovable constraints: + +- the data source name has to remain stable across runs so that the screenshot comparison works +- the data source name has to be unique across parallel test runs as each test run is creating its own data source + +Lucky for me Playwright has got it covered. + ## The Solution +In my mind the ideal solution involved me being able to just modify that one piece of the chart that is rendered on the page without really affecting the rest of the test flow. I mean what good is an end-to-end smoke test if I start to do hacky things to make it work? + +The answer for me was to use Playwright's [response interception](https://playwright.dev/docs/release-notes#response-interception) feature that combines the API testing capabilities of Playwright with it's request interception capabilities. This allows me to intercept the response from the server and modify it before it gets to the browser. + +This is perfect for my use case because I can intercept the response that contains the data source name and modify it to be the same each time the test runs. This way the screenshot comparison will work as expected and I can run the test in parallel. However I still can test the full end-to-end flow of the test including asserting that the data source name I get back from the server is what I would expect. + +Here is what a test looks like using this approach: + +```typescript +test( + 'Configure a column plus line chart', + { + tag: [Tags.Snapshot], + }, + async ({ appAdminPage, addContentPage, editContentPage, reportAppPage, reportPage, sysAdminPage }) => { + test.info().annotations.push({ + description: AnnotationType.TestId, + type: 'Test-617', + }); + + // The name of the source app needs to be unique to avoid conflicts...but + // it also needs to be consistent across test runs so that snapshots can be compared. + const projectName = test.info().project.name; + const appName = FakeDataFactory.createFakeAppName(); + const mockAppName = `configure_a_column_plus_line_chart_${projectName}`; + const sourceApp = await createApp(sysAdminPage, appName); + appsToDelete.push(appName); + + const fields = getFieldsForApp(); + let records = buildRecords(fields.groupField, fields.seriesField); + + await test.step('Setup source app with fields and records', async () => { + await addFieldsToApp(appAdminPage, sourceApp, Object.values(fields)); + records = await addRecordsToApp(addContentPage, editContentPage, sourceApp, records); + }); + + const lineReport = new SavedReportAsChart({ + appName: sourceApp.name, + name: FakeDataFactory.createFakeReportName(), + chart: new LineChart({ + visibility: 'Display Chart Only', + groupData: fields.groupField.name, + }), + }); + + const columnReport = new SavedReportAsChart({ + appName: sourceApp.name, + name: FakeDataFactory.createFakeReportName(), + chart: new ColumnPlusLineChart({ + visibility: 'Display Chart Only', + groupData: fields.groupField.name, + seriesData: fields.seriesField.name, + lineChart: lineReport, + }), + }); + + await test.step("Navigate to the app's reports home page", async () => { + await reportAppPage.goto(sourceApp.id); + }); + + await test.step('Create the line report', async () => { + await reportAppPage.createReport(lineReport); + await reportAppPage.page.waitForURL(reportPage.pathRegex); + }); + + await test.step('Navigate back to the app reports home page', async () => { + await reportAppPage.goto(sourceApp.id); + }); + + await test.step('Create the column report', async () => { + await reportPage.page.route( + /Report\/\d+\/GetReportDisplayConfig/, + async route => await mockLineChartSeriesName(route, appName, mockAppName), + { times: 1 } + ); + + await reportAppPage.createReport(columnReport); + await reportAppPage.page.waitForURL(reportPage.pathRegex); + await reportPage.waitUntilLoaded(); + }); + + await test.step('Verify the column plus line chart displays as expected', async () => { + await expect(reportPage.reportContents).toHaveScreenshot(); + }); + } +); +``` + +Again much of this test is setting up the data that I need to create the report and then creating the report. The important part is the route that I set up to intercept the response from the server and modify it. In my case the response is a JSON object and since I need to do this same thing for another test I just created a helper function to do the actual modification. Here is what that function looks like: + +```typescript +async function mockLineChartSeriesName(route: Route, appName: string, mockAppName: string) { + const response = await route.fetch(); + const body = await response.json(); + const lineChartSeries = body.chartConfig.chartConfigData.dataset.find( + (d: { seriesName: string }) => d.seriesName === appName + ); + + if (lineChartSeries === undefined) { + throw new Error(`Series with name ${appName} not found in the chart config.`); + } + + lineChartSeries.seriesName = mockAppName; + + await route.fulfill({ response, json: body }); +} +``` + +How lovely is that? I can now ensure consistency across test runs when comparing screenshots, maintain the ability to run the test in parallel, and still have confidence that the data source name is what I expect it to be despite modifying it. Just a really great example of how Playwright as a framework makes it so much easier to do visual regression testing and make it manageable. + ## Conclusion + +I feel like I'm becoming quite the Playwright fanboy, but I can't help it. It's just such a great tool for doing end-to-end testing. The API is so easy to use and the features that they provide are just so powerful. I'm really glad that I was able to find a solution to my problem that didn't involve me having to compromise on the quality of the test or come up with a bespoke, hacky, and difficult to maintain solution. And I hope that this post has been helpful to you if you find yourself in a similar situation. diff --git a/src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/stacked-column-chart.png b/src/Blog/wwwroot/posts/making-visual-regression-testing-more-manageable-with-playwright/stacked-column-chart.png new file mode 100644 index 0000000000000000000000000000000000000000..1950e1ece7a0afc4b53e03016646b6ef2eaa1e73 GIT binary patch literal 27303 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 z�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