From fed5a101328eaa6ae8869ebde360d8df4c381046 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:18:37 -0500 Subject: [PATCH 01/11] feat: visual regression --- .github/workflows/ci.yml | 18 ++ playwright-ct.config.ts | 4 +- src/core/gosling-component.test.tsx | 71 ------ tsconfig.json | 2 +- visual-regression/gosling-component.test.tsx | 222 ++++++++++++++++++ .../marks-1-chromium-darwin.png | Bin 0 -> 23102 bytes 6 files changed, 243 insertions(+), 74 deletions(-) delete mode 100644 src/core/gosling-component.test.tsx create mode 100644 visual-regression/gosling-component.test.tsx create mode 100644 visual-regression/snapshots/gosling-component.test.tsx-snapshots/marks-1-chromium-darwin.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40308274c..5dd16f7de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,24 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true + VisualTest: + name: Visual Regression + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + - uses: pnpm/action-setup@v2 + - run: pnpm install + - run: pnpm test-ct + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 10 Lint: runs-on: ubuntu-latest diff --git a/playwright-ct.config.ts b/playwright-ct.config.ts index 1fbc116b1..bdb8d3d3a 100644 --- a/playwright-ct.config.ts +++ b/playwright-ct.config.ts @@ -4,10 +4,10 @@ import { defineConfig, devices } from '@playwright/experimental-ct-react'; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './src/core/', + testDir: './visual-regression/', testMatch: '*.test.tsx', /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ - snapshotDir: './__snapshots__', + snapshotDir: './visual-regression/snapshots', /* Maximum time one test can run for. */ timeout: 10 * 1000, /* Run tests in files in parallel */ diff --git a/src/core/gosling-component.test.tsx b/src/core/gosling-component.test.tsx deleted file mode 100644 index d0da78bdd..000000000 --- a/src/core/gosling-component.test.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { test, expect } from '@playwright/experimental-ct-react'; -import { GoslingComponent } from './gosling-component'; -import { spec as JSON_SPEC_VISUAL_ENCODING } from '../../editor/example/spec/visual-encoding'; -import { JsonExampleSpecs } from 'editor/example/json-spec'; -import React from 'react'; - -test.use({ viewport: { width: 1000, height: 1000 } }); - -async function zoom(direction: 'in' | 'out', page: any, steps = 15) { - const zoomDirection = direction === 'in' ? -1 : 1; // Zoom in or out - for (let i = 0; i < steps; i++) { - await page.mouse.wheel(0, zoomDirection * 50); - } -} -/** - * This tests the zooming performance of Gosling. It zooms in and out 15 times and records the time it takes to zoom. - */ -test('Zoom visual encoding', async ({ mount, page }) => { - test.setTimeout(60000); // 60 seconds - await mount(); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(5000); - - const centerTrack = page.locator('.center-track').first(); - await centerTrack.hover(); - - // Start timer and zoom in - const zoomTimes: number[] = []; - for (let i = 0; i < 15; i++) { - const startTime = Date.now(); - await zoom('in', page); - const endTime = Date.now(); - const zoomTime = endTime - startTime; - zoomTimes.push(zoomTime); - console.warn(`Zoom time ${i + 1}: ${zoomTime}ms`); - await zoom('out', page); - } - console.warn('Minimum:', Math.min(...zoomTimes)); - expect(Math.min(...zoomTimes)).toBeLessThan(330); - - // const screenshot = await component.screenshot(); - // await testInfo.attach('gosComponentScreenshot', { - // body: screenshot, - // contentType: 'image/png' - // }); -}); - -test('Zoom multiple sequence alignment', async ({ mount, page }) => { - test.setTimeout(60000); // 60 seconds - await mount(); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(5000); - - // Hover over the third track - const centerTracks = page.locator('.center-track'); - const thirdCenterTrack = centerTracks.nth(2); - await thirdCenterTrack.hover(); - - const zoomTimes: number[] = []; - for (let i = 0; i < 10; i++) { - const startTime = Date.now(); - await zoom('in', page, 5); - const endTime = Date.now(); - const zoomTime = endTime - startTime; - zoomTimes.push(zoomTime); - console.warn(`Zoom time ${i + 1}: ${zoomTime}ms`); - await zoom('out', page); - } - console.warn('Minimum:', Math.min(...zoomTimes)); - expect(Math.min(...zoomTimes)).toBeLessThan(500); -}); diff --git a/tsconfig.json b/tsconfig.json index a99a47df5..142090715 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -41,7 +41,7 @@ "src/index.ts", "editor/index.tsx", ], - "include": ["src", "src/**/*.d.ts", "editor", "e2e"], + "include": ["src", "src/**/*.d.ts", "editor", "visual-regression"], "exclude": [ "node_modules", "public", diff --git a/visual-regression/gosling-component.test.tsx b/visual-regression/gosling-component.test.tsx new file mode 100644 index 000000000..be5056945 --- /dev/null +++ b/visual-regression/gosling-component.test.tsx @@ -0,0 +1,222 @@ +import { test, expect } from '@playwright/experimental-ct-react'; +import { GoslingComponent } from '../src/core/gosling-component'; +import { spec as JSON_SPEC_VISUAL_ENCODING } from '../editor/example/spec/visual-encoding'; +import { JsonExampleSpecs } from 'editor/example/json-spec'; +import React from 'react'; +import type { GoslingSpec } from 'gosling.js'; + +test.use({ viewport: { width: 1000, height: 1000 } }); + +async function zoom(direction: 'in' | 'out', page: any, steps = 15) { + const zoomDirection = direction === 'in' ? -1 : 1; // Zoom in or out + for (let i = 0; i < steps; i++) { + await page.mouse.wheel(0, zoomDirection * 50); + } +} +/** + * This tests the zooming performance of Gosling. It zooms in and out 15 times and records the time it takes to zoom. + */ +// test('Zoom visual encoding', async ({ mount, page }) => { +// test.setTimeout(60000); // 60 seconds +// await mount(); +// await page.waitForLoadState('networkidle'); +// await page.waitForTimeout(5000); + +// const centerTrack = page.locator('.center-track').first(); +// await centerTrack.hover(); + +// // Start timer and zoom in +// const zoomTimes: number[] = []; +// for (let i = 0; i < 15; i++) { +// const startTime = Date.now(); +// await zoom('in', page); +// const endTime = Date.now(); +// const zoomTime = endTime - startTime; +// zoomTimes.push(zoomTime); +// console.warn(`Zoom time ${i + 1}: ${zoomTime}ms`); +// await zoom('out', page); +// } +// console.warn('Minimum:', Math.min(...zoomTimes)); +// expect(Math.min(...zoomTimes)).toBeLessThan(330); + +// // const screenshot = await component.screenshot(); +// // await testInfo.attach('gosComponentScreenshot', { +// // body: screenshot, +// // contentType: 'image/png' +// // }); +// }); + +// test('Zoom multiple sequence alignment', async ({ mount, page }) => { +// test.setTimeout(60000); // 60 seconds +// await mount(); +// await page.waitForLoadState('networkidle'); +// await page.waitForTimeout(5000); + +// // Hover over the third track +// const centerTracks = page.locator('.center-track'); +// const thirdCenterTrack = centerTracks.nth(2); +// await thirdCenterTrack.hover(); + +// const zoomTimes: number[] = []; +// for (let i = 0; i < 10; i++) { +// const startTime = Date.now(); +// await zoom('in', page, 5); +// const endTime = Date.now(); +// const zoomTime = endTime - startTime; +// zoomTimes.push(zoomTime); +// console.warn(`Zoom time ${i + 1}: ${zoomTime}ms`); +// await zoom('out', page); +// } +// console.warn('Minimum:', Math.min(...zoomTimes)); +// expect(Math.min(...zoomTimes)).toBeLessThan(500); +// }); + +const spec = { + layout: 'linear', + data: { + values: [ + { + chr: 'chr2', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 1 + }, + { + chr: 'chr4', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 2 + }, + { + chr: 'chr6', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 3 + }, + { + chr: 'chr8', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 4 + }, + { + chr: 'chr10', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 5 + }, + { + chr: 'chr12', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 6 + }, + { + chr: 'chr14', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 7 + }, + { + chr: 'chr16', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 6 + }, + { + chr: 'chr18', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 5 + }, + { + chr: 'chr20', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 4 + }, + { + chr: 'chr22', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 3 + }, + { + chr: 'chrX', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 2 + }, + { + chr: 'chrY', + start: 100, + end: 20000000, + start2: 30000000, + end2: 50000000, + value: 1 + } + ], + type: 'json', + chromosomeField: 'chr', + genomicFields: ['start', 'end', 'start2', 'end2'] + }, + x: { field: 'start', type: 'genomic', axis: 'none' }, + y: { field: 'value', type: 'quantitative', axis: 'none' }, + width: 600, + height: 50, + assembly: 'hg38', + tracks: [ + { mark: 'point' }, + { mark: 'line' }, + { mark: 'area' }, + { mark: 'bar' }, + { mark: 'triangleLeft' }, + { mark: 'triangleRight' }, + { mark: 'triangleBottom' }, + { mark: 'rect', xe: { field: 'end', type: 'genomic' } }, + { mark: 'text', text: { field: 'value', type: 'quantitative' } }, + { mark: 'withinLink', xe: { field: 'end2', type: 'genomic' } }, + { + mark: 'betweenLink', + xe: { field: 'end', type: 'genomic' }, + x1: { field: 'start2', type: 'genomic' }, + x1e: { field: 'end2', type: 'genomic' } + }, + { mark: 'rule' } + ] +} as GoslingSpec; + +test('marks', async ({ mount, page }, testInfo) => { + const component = await mount(); + await page.waitForTimeout(1000); + expect(await component.screenshot()).toMatchSnapshot(); + // const screenshot = await component.screenshot(); + // await testInfo.attach('gosComponentScreenshot', { + // body: screenshot, + // contentType: 'image/png' + // }); +}); diff --git a/visual-regression/snapshots/gosling-component.test.tsx-snapshots/marks-1-chromium-darwin.png b/visual-regression/snapshots/gosling-component.test.tsx-snapshots/marks-1-chromium-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..75406b548b85a14aba290a05e030624531cdfdac GIT binary patch literal 23102 zcmeFZXH-*Bw=NutC?KMupmaqAMM03>ZGfVnfPi!X=~a3OMMXrUiGTtD1QC%YQbNZ@ zmrm#qdJiZqkdWlg_`c^I=bn4+x#x~?e|$f_9~s2Gch+8IuK7IknR6L_M^lZ3iIWL| zK(O3Uzov~q&~_pabV~ae;L0UusUi47y?{XQBW_%~tm~7qJc{%%#{2SZ z+x!+k)pUaK@++2Mu{~KHaTnbroDXN+=(Yb?mXqY+M9>{Z_vEMF9`12YeqryOTr$k^ zv8)O=n96q3e`w`H)rAEozcSIS7he9_rFOT3?pB9utKB(#Nwe+g!MNVf7ee?YPklWR zJgD3hf;i~H@#d}n>PW{NH;+R@=lI*V^7z2HusJ1kucK5l4Fa*OZZ@9>Sp*Mb|IN2POPBb< zD>?!d*ln`AMRMlOW3nx`X4w9eYAJEAR&NvZ$<>m>f;wu*sh6`(>r@ zyQ?4Gk>F*g_#j7##|1enApCBpp`OEHqGiYDcTTHaB16t+_)&qR^_kWCZq8DF$ETB< z(?3$ZJ*g<%uCcjb`|H*RJ+}8Zf*JVx6Qu1<4=e``H9Fk7%h-vS9vWBo{Gk{5&QdE| zTT7Ia8@ZcGyg4&5V57Zn&uJC97za{(QTb|kqTUsc90l(9#iwGfP9>E2W|`gjw=jNz zmD*XnvhRZ#sUwkb`(U^svGxkvy)y=%wB+4BUp>IBeDrE#gJp%-oaBe!^tQpYhM%+= z+ag38w2^WQs-N6`Zx<^TB?%39>Ot$Ivo3-W#X4?ND~uV$sK8${?KeUZEO#!pH5mV@ z4VWC&P&1R|W7cnPq~k*>;=0W>BHuQ@LGJu)!R=_wWoV0${mVw_4#s?iSR}3AJFO-p?V=p22Im_$XV|s}9d=BZuxCT9BjROgCpx z-Z|5*HF``bt2VDL;I zwH0~xMyI_PF4pNXJASF=vGIK2%lf7`4huK0a|vAT*2p+c8cuVze=JFZHmOZ0T4~IE z?~=A}`CKYn{l$ygxR%xXpR@{7-Va0&5nfus3l6TwyPhCeCbu~%gTOVVsZ-`^e(%_xBL+EaID5f4|B#2njJ5WZaz!HInb$%^lLkY zUYePkY_ha5SvhXb>TDWtqM&YTqy@KTdtt?YGV6-So$0r{w&qu*`y<^Pz{pYePg2i!uYq?hue|&=evp*M3U*II?r9zJy_AS+52C0&+ zX7Q{w*NvS^0$&Rpy$)x@v8}7Yb=@4zk)Akj+b2ZCz*};Qf$k?Z{D?PLXAb7)icI*y zfjdDD&yTJ5&b+Df5J}+b&rl$g7uVBlQI~wC%TU7FG@RUWl{zDDnoiIpeRueEOVtb} z!}!4pFT9VsNZ?WYZu*v#W$p>@^A&s|aPNe?CA}?^1L;i}Y?=}LPYu5=gt`fG(W5sz z)vUrKNAp^ipByI1Cz02dX=y08Bk!$Hk&Jw)wap%c-N3w$ZknGG=cPF4pdqb9XlYke zQ#)gDiIddIN?*K|cMZGL85JFalrbDOd8QEo_yH!8Z|fjcJk}Sb}f)jAs@$M#8=YPxm3}@2%r*INm#RF^X?nz{?m5 zaEv=ANq;}0Wb~a{$2D+E;BnMoEuTdxI*~URV93Xg!!R;?4tIVB z*!~Abt4tWmoDqz)ph4IsHa9oFc_XCPs+Ke8DAglEi$JVhVcv^CoZ?rZL#WWwO8j@e z-YM7cP5T#!-MNUi$%dsd+QDMLgMy=pAh!&(bO50A?~Z&`mt^PA*!AqBjLgI z9XceO`x6XmAZU5v*vFSn$6uklp_A_{r|`RE`3HXI*ln=SLxt0#914|+*Q)laeGi#M;P4D@vA^G1D(wb%b{zp(OY zlW=E@3DZgixaz~wBDKe$+wnsyCE*H3X1bE34h{&=a0Y)jG`@c$723!j&S#JKR`tyt z+ZFA3;nb?C1IE7wf1>Nzqd7lSSc@XKPc!rESF$&5~>zcK+N&#FOBu{rBr zZhAf=$CXFkc^mqLO#S(Hkp8pr@i+Kkg@mH}EL^8!<0~*@Tz;|8lm0xHQiG~b3doLd z|0kKzt9JXjQN?T5txY~UqdxnrcMIxiHjGFc_7E;>9ZILM&5hI?8vclxY;X3D{@FPi zZ*@YJqL=pB#}-reSw{Y7@4fB-jv~sVD z#jlikyjF7%#^)>Db(`oGoE-XfNqAVzcf&28Z~8HU<*TBhWqY5Qx>c24#7V6(a~r+1 zk3QquInO=w`SN4^a)~*unUX>8_;skbI^?`Dyr>spii*7@U{G8mS%Ds?kj| zy_9LFENOQ*oyM=%I=5*l+t9X@KccTA#}uoDB$du(tghH^eQgpbqRihIzF;DtLN`5N zUDE`1urIf1BW9D-t2VjZa&B`sOY7%mf2z^_ckP6S{msfWocuq^Dt*+8#n2x(Ds*|v zUfgWjnW#NV&V58C1(4I~YJ ztr|0Vmuj$cezbCJA)a@!FP;t0qqwmCgG$|bdKa&beQLAt{sRkWYLgMz5+dWGcIFLkq2`J`%KHwV%h| z@XeVk!3=))yIn`@b8PeZnVat1T2h;p^iH5W3^u)3G1Z`iRtkJ7)&d}TP zM|g2Y5rr)s{30WL=;UrwucZ%faYjXY(|xh1y6AMxboFq0NZEbmb)O11Tx$5y?_!-1 zQcOL$qGC!O%XSYc!}fK4jdDC1WrTM+5@p0D{M0gC=;^|kpvu*6`3D;EnL|eWu?@nF z-^IT6^@S~j3eQj1}sa0Jf?wz#EmVQD& zim3rVCU}f);}sB0Fnj96T2Q*;?&fIDN4C?@aLPGuhI{1~IpwYzebQ0`qdK!)My|nF zRuhyEej2_d?5qJBn{TL50C9%R4a+7vh_y*UuvER4I96E8nQFfOt350?l>)%JF^dt` z7zK}6!vjkt0`LGU!fWyCADyDr`Q~!MV02^tTYZb|qT;C2$4{yBtm~zjqvehi)|p*o z;Af`V040utIvoL>Nu;!~Y?*(0%p za{pf@<&Tu8Sdg5kbO?4Vz;461x+Ha9wZvQ6iP9uF;Uo^Cotb|kI(hPQxm2%lSNi{| zRs8Fss3Qbucmck4nJd5kpDrMzl7)Vr_V9C>bm{n&Ep@Ej%$w&_a`;|pQHm|yKUify z!qGZYX_2~}aD&ntcUNB_P?w}dYY#H(D_uB9ho}OiGIEbih$MV$%`BzHJ=*z~S^@IU zg3~pBe-0jTD!R+J3C4u`B7Qr#{VhOYBv1*d2Q&S$G!iDw7|SIvI3yfa0lB41T90s5NLsc{?e0EX9NYOzK1f~ zC3f8lW$r9B%y;c~*vroYPNK*lEvkM{T90-eH<)89HS4v#uji1P2!wysxCPdNL5(-O zwu&NRa8-_pBb_E4rB`@L6oY*UC{OiDz~Y7j)z(+0MB6p%cRvv|W+tlMRdNEE(9_F> zf0U7&$4y}>95a2rXUc&mEcSToCrG>0&Fh(fpM|irprS4a!?X#@H>8r@MsJHSIQnWe z?@V1$cObk#v-43bOLBfN+V4|o$k#FOs7G2BYp3-(Z}lann1RUv8DB~L3%Fne*Mnx> z9V;I`oWVjbC8$Ez=`OXXG3GpZ{ciqBp{Wy+OMMBu#{uG-rn4t1ioLoSw0Xg9)FtBy z{I{WxZ%b+gD~`?&DNa1L>O+U*rP5D6z5W8+AHQUNOn~5#x{B8O&F}3pZ}7uTU9PH< zQ-pm5Z?z+(XG6z4dI2KyUIu5n0|_+%!=7x}i6`oz17+^UR)c3BhyzZ1`iLmjIrkbyZF&2jid zTHl6_S-=U4d&-`Fan|Y?#Ks4g^ke(S`l4@1l@f9|`Zje|mHosaf(<9BX)*PBpb#vb z0){nBda(RCbH7DIM#&_^X^h#%A4(>zXbnZ zXy$c0n8CI<^22@G;$TNGt(9MywH_h`^_PSNPXV!5c@e(+2Eh{3IaKySx_|SuLvgUe zec2Ok_b40V>KMjwN6;z^Y)IjL4Kcmcea|JvAD^3@b%)`wCZN*`sK@Y%(t* zAtKaN`2|rclU0@Z@)dprs-LNcDoFrdSY4M3qqm)>c`T+vJ`X#>C~Zfb`;+ERfYol1 zFnW61rq7sf`WG4I6(K67I@xl`^_%;Zz4SEHGy#hqV+66Ij6YqR4<_uf49+O`oCyFV z%8^+c7=-ab3r_nwJ5_U^{CR|wg9x;5#O8w-A{oN3#^qMQf2totGoSa+aC)g+WJouH zD2hrn;P>|dO4@Mqs?-H)%$qv@DqY?vBSBnEXRwK8UQM{6H!Z31ovH)jtvYBpqrA8X@lRmeoXy?)ml)CVLQ_tv6>XWVS=+ zyf=#D;^M@_#Jc_@eR)q6aP2q%1AO0w(jgFsVVfZMfg${Nz6yCb!r%WmlWjcKK3yG7^_ zD^zT$qL<=V(eZ&vE_Z4dLma2dDY(M}|Fy_0YdOM+=(JmF=mSnr0Tdp_h435s9=~pV z$K~>Ww3~ltLJZv^>BE4;Jt7aZK(x8bd%Nnz*du?hbA*;3z`#%Aw8ia!JAna*j;*j4 z+^Q#2yCW`_0YS?cKGyf6GP9%z>?3&QiGa)OmN#yrII+)WV~Z-sj6JVi`(36iFDrYT zjg8GDM_Bk~n|1M9uS2lB9ELJr*#Aun{A;Zt-%VPGKf~GX=3-#7f?xhsycT=p$vePx zsLnt~@-$U-fPkLIce(8pQi&Lq`VxY2HHQr3e@Zsgb&AC}05*pnj!|duyN3Mfn!rks1Jf_`?VZb;kH7PJh96a* z^s339%)Ls&CmK#z$4xbvpaf=^M6*viu>!YsVq@tEd6hs(j z8s|K3qCa>6Kd_u`W)@qU4i$=?ovLK=O=G&U1>5H=RYWh=O^($L>q(@fZS#eJnAL^<2qAf_&|cKJAvSrdlx~&{rMT_Vqc4k5%YAx`4?7 zP}oRGs5SVfc;_B{&&nGl1#l|{pA$)Y7UE2?0;6ZWAA4ND-~?F}+iSLW&Yc7|z)U~q zc<_CrJemCA(EB|$m4Ns2{NH4gUvR&#!dQ1j@?VgNV+T!|Cnk({u3UaAziBW2BwpQH zBirx&oG2;+)ak^*-+7G{Xi!|7UMG%^XrE)XS)uu)gG9egieh#EFb7%R-%GY7LUIg~pE1h*Ih(4)SrDHm$i6b$JX zjOFGe{Hor#^}g9cW7prXlfbOqKB=^szMi0MV zg=`3O%}Fc^E&w@v{C#m5j=6mSLfxqOtgjV-O)hl-85i9jqfe^Gsrj>XduulNf5?Dz>gK#@Pk9|6E%~DZ{AIv z)AW2~te0Bqe(JbKE1_duv*fWymRwOba*C>}>}I+1EV+11y7)78epb8A`1-nUYSn3V zmtW^3e0vEBf<^uOCTVi0&n>7!De_mD_bzM48XhwY(q7)28$4hrpFYqnH|Qz=ic5*T z2Y?ad(X%pWP(J#5l4AH->JviwafRk60hMBE%81?MgV)vT``dG6WgWfg?S#ru3=2;h zwFZ@^pCfMntaSA;3O3E0|K_WB+3)Xd+~bXqsg5D)Z4=k;6SLJ)Mr`lB)ncAm44+dU zR-O(+nB4h$xp`lA)szZj{TcPSWHGNjmPZERS^r|cmBQE?^5|r@`zoupdln+|c%ywc zi}}!IZ3k&8`JevCuK_8V%7ei8*y!zow;&<$bZ&q5_pYfDf#q9SauXd^P9I9?%vTL_ zJGU|iPpaUBzR4aZS)&Bh>6UNz;WJT{yFLkwor zwSDcl)9T)ueFuP1#BBwZ98D<%5#xk@+STB8+E>Lx1+s5p z<_j>HPye-tBP&W;7a8+&co55)+1Y$;;5{K5#>|(bUrdX-q?gk3@H%I3o<06W$k2XZl>>$ zTS_Gp=^C|4Ys&J3N4V#ko449ke^t&j_<)B`w6*?I+po+6Xy;WTf=HL%-I)8(E%I*Z z{~@#;C|C5khHv<{S^)W@=z7;7CnBM4fzH~y?ME2do4?@tB-%w z@mCf(@UAhSIa3~JACM1)v{sy5;;STUOOZvL?5W(e02wOfFBVJmy5V#Q+$V3q;vFRUB;S#krMu^t~0Yvunhr7n9)f{?^m02F1Bj?y;2BQq)w@0V`t10`p2tN`EOGLbH^JowV9AF0!Db= zhCgQWrmJZo$U^IhN?i$^!s;~?WUy3|+zpvDNzp-d<7bD8gIiDX1 zd+6S7*=QKpsnDwRMw z>vOe+8~SfhtgR;aesjc&?L9~gR9Z0UkQQJNLsc&aWEJDNmH@Nkzb3`Y$(<1HxE*6H z3exFQaEdds>Ncl)H}xCD@2Uw}%}BzCt)=0f$>I0X3EBLp(mVZT$6K%Q;9i&Z`&@b~ zaRWx`e@fjYPwdj$!F~bJcOo5Y1WvS42W%+sUF^Q@dYHhHaDGw&q%Bobs}BljnKtz{ zeXf31snASx82Lo$%(OX7K|l6z1C_Iksz=UJj-;KwexN(|iF?BAwK>J!nag8lQ(k>_ zar`wh^7qO~S49t{hd9mTWJ_wiIP!ka+++(h)J#A#bvIbfCL>su-|*MS!!-Q!P8;=3 zO?A-xLy;MlQ+m@Ha;w)xO|)flS4kk~P)$OF&ct+Co={|Lrm4`@`VKk+ago20d`(G^ z5Up_M*Mj@8Vk?azMu7*Nj6-*3tDW!p4W1F5fZbBjNDwxA5+K zx|w0ow5Hw5;!EF-OT}f4r#$6HInhdVN8WdHTW(`+oWG!fm zRet*~%W>VB2(C(@8I`@BHbkYQH$6`lzmpYLxJOqOH>(?oTKqZJDkf|~E0JfJ-79;Y zxf@!hN}4ek>qoY*ok19=bOp3%%=56F2)@02>Hge?pMx5&p`;;HE}T|~)3|TyshN4S zMJ%_9-ge*_N_(#4@dhxj8&$8%nsi(lhD4d?4(2IYLx_~6ef1DD!n0=cWvlgS#1ATk zKzIo{IN3p{OH~rZtFB;~^B@ehwC-lIZOKz#H7U&N-TM0!vv51teaL%JQ)1cd71ovo zf=0_j*Fj(0(MUTuB7P-Cv*0gjVKw*u!kMB!yi{=p^uAOj{fCA0^W2B2KDVgGlraT} zPw;5-RaaA&!poKYT7(dkeOg@mJxY6V?2 zh9AX4U1%r?No9?Dd`X2kT5)S}B|||g*!ghEx<;_b#mj#97wkf6sns!Fj0nXn1;ZtS&qq__ zwSrY+G}CRr9S`0;m{O${Y<(TlxddTW#tyE7DKt&zzEu$EZJ++Q;Nt`+_Dxoi)+2;5 zEO~thQ0}oifvPAUke(~0;m@U?TRxc1d`)H~rJo@vIC7%xxle6e77aqt0wOtS(UoNzWqiG+ew<>K zb@AY>s(pa|5w#`E2t=>~a$Do}Qrf$k&Sq4-YSCMKnFF zAIyP(#&g4af0S-!?*u>kZ6WfJ_}%%DUrli~845`&lNy$@f2ajHQGsQkKgSAocvOGL zBJ@y;oT}#<&&G#MDQcl^m!L|7%Uw?6@F~mZjDs1*O~8_Q!oG}_v%M7YUjBpD$C3+fz4MtLaaAg~`DSMy$#M!K3Tk`Wh3DnM)W|sLLz3;(Slv@S6)d_Z#)0@O$%l zwX0SU5G4*@y{e~|X5>}Ogqpe66lt#^zkM26Gqia+cXU-J0qgzc$B_fUPLNdQ;oI!f z=hakn)%(N-nV~VYCl8%AD^53Giz@js?9j*`#{~$PGnF=XqrTr6df*|b7c|j)bG%Sj z(CdiRFzK{IcAd9iba5;M%qGX)>gO@XPNUeNHzk%whV;H1k?G;Q(RblKHyzq2cj?#7 zajc!zqbl@{*T$9XL8H`od^8GX)hoSUt)_HYQo(wm^{cAa+Cw3&2OqRiO~&KcmCdDI zsk(l=Q!Zt!NulrW!#cU0_xGpRMV?wpI3E=^DJ({XsTFZ@9&C#j?J&#KO^?b>W?IL*>ibAs%{wXB1l2S`sSTcp+&$K=h8P`ndRQel<7a1 zUFu+En$0e_R+iQb#T5gwn#=R+)p1zdIS*dCzwe?l1}{d%e5hG$k*dQe zPlqwd_Bnx z4ZYMC)O+XewpsBIEl)6I`sZsvqSLAk+NjkmSSf?0LhHZt_Y4|g0S45);zFq@2ltlD zgDebf#z+xqXAyhqI|~@OM&+D0*2v{t^tJ|BsNBw)YQo4X4>j!4gYJ*gLIqxtzn1#W zc@(Zj%eNhgO1TfoEozn+>=z=FBDTdf^K8pzuQiz(5%5VTpEhr@$#SQs8&F4S!X_(K z+(Y`K86C>3|KVf?KH4`IBFsxN+L2HSTWuL_#+!dsPC@N)k(JTe@8DQ9y*I8i#^sBe zU*GjMH^btbb(P@ucbZO}wRQ!iiDRW1gZ9biyP;12pUj(o8*ZHU;?wdfRu-8Qa}tD5 z*B;Mc^7CE~ZrS>1WmDml^{`c!UK-8N!{YjID1#9540?87xYQlZ5~foG>l;5+B1Qg4)$7(&c9Ol&&RMr-quz9 z&D6*CTjh2jZ~(@R$DX2%rtRj;=y;aKPOIR9N1Z$MZhJfD^1SZlAN$}-?{8rydE$P|c9He7HD)=`zDR`5=G?0O3QIY;|JEg5* zwA83e|J|N>-r`XPlMR=bxJT1P#gOVi;Gqg~tW28*yS@p6&STecIdgwaT?3;oU{-4uF|1J&ipMPcY zKD){$sCg}4Poru-LT(&H{gKK?fiqrEficsyySxrjEocx>4f{_89+T8(^tX5-ThE!5 zhbtPgbe|++|4+;R(w#s0QGRnma6^ihOKPG5$0TZd8|MC-+(bzJFTQTL!6 zbMO;oD3|)uQH0{!bWcve4LfSK9+fS6bJ}bIGA*l>ko}MpPK5~f;I*bu2wbRT&GEyT zQ*F9S?+id&{@>O&esG0~04QL*w3uJ0kg{~wMMXR}8+q+%ML%$?;R^DQyK2qlkha=@ z@4ycJU14P~`vFALa~P}cr)+&^)|U#e>=nN@z?3#Um#6WAiv3`xe?Rxkdq3xI3Zzg& z-cOr|=~1Y6q}H73J5y8S2NeCzV3G!80mJ&Vk%UxcM#N`-;GvR442HwuBoWdM(0?f4 zpFa+f4Om6(qv2!aTf)&wwXiCVxfQkmOU(S{W>b< zbm5=jukI2}@N=knoSTBP2hepWtzy2-u&?tVHM0>PW0y!1E~_6Z0ml;lWpv;n zsVNd_A)hm!0@)Gr2s-(Ui1`cuOgqO9Lxo0$wj#>@_WhI8cyu-%ay-s6Tb)JO@EXLt zB6T8se{5Hm{%ab_pzzYvJ%lH7EZxA^JYthtd&cbt)#vbmlND!=`>tIXIvpeSf6O*r zY&bBGa7Yt@lLQdhn^sx*8eT}L44(0Pi(K3PowlhUT5K$%vz*fBAw%9NZ~4GRs;y2OtMRi zHFlR}51cZ)w)1WEMTh`4#!J%3R@Hq$Z|J(pBFx4k7q=DtsYR_ov+B+6KR!#lVU}d+ zK!uEo*IxJK%RHvq464cX1Kt~%hwk32N@0<(oRC&#bD<=!K1a9nMWcfz@jr2Bvd+-; z%l6dP@+&0x`jzlDnZ87n%$f;Bs3lWy5|tJtlHhE7qpAcY%<{7{jO_SYt=KoJeX`Un zVkwR2F6K&6n{4u?ObKnW{M<~Ve0x6oXejIQIi`L(78Mt}fKABQdW)&n;+21n(_z`^ z1nPtdtkt3!g(+Tk@_j+SpWOAG4!TwK^7aK|GRIv`?x-KwKbdzqKmME`u{Pvm3HUBY z5!Ia6Y5m7R1)&~W!R|!PFY&$Ls$0Wbx@{$9=qerFtGs))OIaex%dX~K#SxAEzzYNh zVKICQUv&LaqQ&^{)t6VXTXomYs+no0qrj4595k5CBC~cx)!cWybU!H{hHWrO=pd_{ zZ%3ZMb^Bd`%Wyu(;?GcF11#T*7J;Bk3C?6MV}DPDqzLJ55mxO7dpLuQm$($Tp$=DO zmnTS>z)Ffb=JQ+La!a`I+LW*|7abnyFf=n`mZN-=bXCk18#dL6rqn#|R(Z`J>_);+ zq}U6QVhpR4vJ&B^1;x~ZJDa0U7+a~;mxW7Q$6j=g#x#z_6BsgG=4%eMu4+o2Wzp4p zEO{gr+`(fzk+J85xgePY=k~l2>FuxR#&4{YJD)spdcGUiHh6r3sD<%GlqHJK2q8c1 z`Mj`=SH^W-7E+Wl4|5{rVf8w*@)0_`@?q(qO}?MwPq0TxVL>+fO9s`1R=24{im5~* zr!bypyP@vj3|_GdBlnd_Lgm|eY(Zn28RgG&gBP{mu69ZL2=xnxBZ+*^1?upPnYv2c z(sjQres*8k-%q~2!M9skqOo-R39AyP8ZR-s$Op&lwjMzdf&p*ItSNdo40`t9&}Pv_ zkOJpC=I1?(<65?7IS79CMK-9%+Ger`f;tB0xfG7wZK1p~hZ0;kT*c8=&vlnmMo72R zC`WmVNAdU!tHSXj58<0n!cN{CHGAS9^6O_U=!N7|itx>>5MBFV$YZ~-aa&>Tsy2yU z0%U4MW5Yv;wX)~<$nWO4{LF^3a5r*XE`k&QH5GPQWYQrLQHMxE7Y3f-bU`W=kb4_X zp0uFsrMT`#&hK66-D5JH%LP+0T7EcdEp}dhL*4?rmu!CiR?xZGKx^1}Z@lb2XJsXu zMQKxN2k>O&gLGlaM^<;U>)6-pwk!{I-KvscYM{>M{zy`hJdM?MvcSoV)vtTO{79j& zU9-Ru+_p>Hn=H4B5>++ZQKzovHHplM-=zlSYufvfKhjz~`IL7o_i&mUwNdxl^^(Rl zoWXWm5L-llZTTa8vz_eT`ljtY|E^8C zX-phcW@B~KoYTRVRcK5^mO9+z53t_ zHZ8jxjUybC*RwOo4cKr<*f)l_Y_EIkWy-(*6Q35oq>N9@{9_h)E~BF43FYz0&uIFA zYwmxndxQSCTswc^+D4~AN$tC?V`N3j`Gnaz?ksJy4P21c(=C=q>APc%7SpU`tMhF^ zvXJAsdya3x*2lDVQ_q-E!6z{q>Tdu&S9h146(#kQleU&%yyUQwT)R4GgKzsQs>}2H zCExBkQ6RkDSDog*yGtZaqRh5eGb!VHEp3;hJy!5B8jzr1aIPcml>ZS{<_xPP1x1pd z@ZGB7PH$xoB&9)JR`%3yZ?emo09Pn}F-Of#ww05agCrTA)yWc`@xH8gsAF%-3N43X z*S31Up&1$;@as2?DZ6G7Bvwk70rd9#=ytw={jUbd%UjK)saEKQOS^=^GaFo``Ok+@x?*w;&n|NQke+ za;OEaDo1pG{U)V%D;oy(jJmi3%3HeJfgAO*3EEllA|!J7ildG`LW)!m)KVU7p@{+_bG8T5YuW zT~jA`lWwoY!mNwh`qt+BFvWZ*o@QDPOYZs@Fzr(R70FlYey@|uMb&QHJgo4&x*+6( zYHrfGjVF_}uU(^N_o&tbj*y$kM~<`8XNWNZWYO3^g1^wUjSJ#)S0-g!>}-|FY)2~F z>RKN-Zn@WSp)(U~jZcxSOZu2};LfV5L}HH!BY!^wUx36jKFXZf#wO+V$ZofC_^^J^ zS==jiC}3DA@*ycVj%ZPG4E*N$EnTMt#5UgXJ%7il$Og4YunBKhDFhoQQQ$`et|0lF zPt-DY$*Yl9>y})ugB46}^#>~9x}l{HnYKAVYCeJ+7#oJoCJKf5D^?et$CM?MLg z+|pO(kPgC8o*Ov-T+2si{F!0ylHMRcr5eXuk04O=dsah?xM~mR zatuHQhHe8(hb^9+OVU_}#>o~V1X0Qf0&Aejc~i>f0OcFy+<_pxav5v;KANN}_9E?# z@|1-zOYGh{GIh*XVR8d!}pE47ZYr0 zFV_5P;;khRqI+#P>=+NOG6uFH>d$uNDAO4k5Vq;x)DOJ@;O8e+?-om8L~|2mWzsfFXX=4LY?IL`qYyx`=pD;BwKz zPRyCSsQjGEY`aSc7sS6J|3@^_h2q~`@DhYxrccM2dnj@R;P|4}=EINej8P6!_rcJ0 z8Wo8|_DRAVIY6RzR|ob@8nqy;3%yD%X|*_NxxDrsD4}!#6%>Zzr^irn`zU_P{**Yk z`C+p22ChyyVXlrF0-~WTnmexOKq9vBJI{?ftq8>Naq9mIu#r94nJN0S^GDERyG$3A zGFEhzassXvc}$@zw}@*EOdi0OfQm-GAw`3K;g#niD*X=YE)eR6mJe%4f0*hEsyH55 z2!~QgK&K9=)e;-E^I3u|i~@)pnS<3wR|CjleR-a|)Y;rZj{D1#iOCVtQ>YHgW;qfz z5_y&QHi}ZK05T8NuYtP>I?V~Cg3yUWZ6(4jb+shcpCl~4!hc0H9*%kPn@~?$WBZX< zMLeF5e5Wx8zgtN0IloJw@Sv^Fpwrm3&EXj2YeI|I_IM^WT}ma>JtEnHuNheD$Ddf+ z(Z(o}!l3x$Dr@$771;bTahxw|W_VLKFvxU1q&=j2{GH72$RM$2N)o(x&t;h$Ky+q; z`N;gf9VE+uGN?QKzt(ZCH_#tI1na*29xH$(^1c*^CdD0tpcbbMCAd;lPXnYY%S6>0 zR6%Xdyr1MSiVyf*gkt;0{`{jNDq5PFP-}`%>3EZp^73yUYW|aU@nG*g$dJiTpStp| zc>6yFiT_It@81W~T@%-o+$OK}+LuB`3o+05pH|i)I+gy9VeJ1+UGM*QpFO5th46B5 zb{6^Y;e&;rpWG*my>2e1L<7o&`Xx?g=;hY`{_Q$+`eDn)hR3bjx9dknjA81IadBx> zy)Q^gO*P%g?pN}zDc5=n%d@bs0CytnOOvWP-20X~TBLf0M6Yvf4d({wml`?^E4d1; zdL|X3EvMw#9?K%3boli0ASPEod1`8^ZDL}=I$zHrDmq%av7sReV_)j~x8k`qzco?x z?B5jtm4#(x{7fNZN=CWm=uWbhkq~1} z$bc!886#0|-q^yj+H853);cNy!#ot|pgY>)^@l`LKfB@I{X>_J-gOYHGznguxi^nepn%NN+A->7#+&S2 zr}E+Wz^h|?eAq3mAy`ExqtEu}o@^}_{}eg@EvSGpD0b!cBW?C4|M>A^m^`9KBZ*{5WRg+lg=Dj*VgT+)vc#_wvwc^A|-A1*07FPR-@b z@@Dc}5jk`sSLb5$19n<=+UqsuN95^k*iU8tl-pQYaGQWdQ zjIPO`G^=>O(kgs6-v-(^xEo?EBi9L=6}{{RC!j^YZpyt;v44!b^nHW)AXRjJ!iwEOyoJAOdlRiAdFh z>8bc|5=36a!N0Zg`>2xIwZHBW&I}_Ehp0~a`QOt6ProU)5Eru34j363nXpcU)ND^i zOo*l#?c(!?Pwe*8MSq7ml;^!S_@T$He4Q*2u!&|btfNdF44e|~2yM3N-t7Ex5k|R0 zd2}{l$swZO#e{W&WOn8rC}o4{TDMD2`D&yO%(bJI{A?Y% z(Ad`+i6Ws`()aTzz5#y-GyPY#dz9tt`qa3yOJMA&+r6gSYjF|%SmNbsMec9)O0_V z-0*FBcgYR`hiE}oS&Ffr9n{Ap`{&#nlD&mmya63!P~+}X(bosTuD4um1629bA&O1bd3 z!g{>j_M(f3Y0&E4h@eD5hP|bw!0t40ulp1%UBJSUnJ9tQAN05**5YrU;+;jv{!)NVhpd;rg&3;3$Xd`|6Ox#Ycl>Cp$9wCF!R++($F zzx}2>2ItTOB)RnaSjX(_Z0w)8ZQtDXtLwhR^*hOPvY4WVsXsM!mIjXs)H3gG|K3^* zLOWyd$k{&!3rwmKcFVJ%n&%Q{hPZ5>37~f^du_AYxweS(9YLBfOC=HjrNz1eW{w6 zAzXIvG=1m8M~vC6)8ee9zBWSjMM;5rRe36W2UfID$NN^v=V(11EIMl?=$bTRti+Fd zgnGswKC}5+N?JPl)+rlamu%YJXT>%hTZ__J8xg2VuaJ=|8q|JqXq=6@r`X>(O3LG{ zYNKtKSdH`jBSceqk-a*rYlc;q@#d zt4?~>I@}HtoS|WwJi5NoM0qKep|dX=r-+164Wd%kNfnL{E^#Iz&rBbfWNp-T$8kog z;zz%%w4Um2>@1JJQl>)ZWyzhCYpvEI3({N$vAX&x_dQMfTmIf zv&Tj0m!R|p)6%)Cg;z#M?VfjO>_IfHC}2)MjWlIlE#_9*sh#oCd}QD=YQ1Fg$r7_? zeQN9RiUsqqF=5pGlnO`FWAr8$_2q}zrLq!!{1DJ?u6f9>CmEcu6}ULG=MnP-*hNlT z4B2veCKo&G1f%}l?~1WA_F4G&RNVut(dt<)95J`~?!@MPxYsN)bMc=e1}sw?I^Zt% z)Ceq>w=Sa28ym>gcOzJ&v(qIz+q|w7L(t25iJ4n9VGOyTT53~Mg6}CgH<)Ey%SwHz zj9!m2r_Gi@B98Rg>M15hnc1W8I&zGmlQ6cf6U?5n)H@`>sp1-d3m9q!#$i`Zw9L zQ5X-76cJ%ErlpFuhx!}E8+Ox=G{p5WwcXvhuZsxPZ_b1T_IaK=5nRxXDSLQ(Mx6DY zaPe96`CIq)IDcW*OdP%)rJ8vey~+bcb$76zu2;;jUm$o2)f8^Lf5f=|>}%7a{5XGv zNmlMZ-ukKP((6ysGWYl%^=zItagvtZz>=r-b?*5xej!o`n_(8#O}wdfE-_5)eP`GU zr+ZXWGNxU0d-OJJab7;Yx~ugox>w4Z!$Vb%AT)wUVkU+iNr3!)OOhGdw*+BRrKFLQ%WYa52m-?Lkl|n zk9My7FUfR^qtT1- zxb$r$sU*U?(U0O0C&^fGgGIkmYmax>{N=8wBEU~M>>#pn zTyn)JSeRag;-r^H=~JH!e>;xPO35?s+HH$~@2d4)^deeSm*Dw;0OEO>gYvD=Nt;^B zCe#JoS2R@ES`=|q#U4vI&8U5Rp24pc)6=%a9?|BR$zrDEwL;DdT7#2pbRgQc&{+)3 z|K(W|j|b6vqpA!s_pjMNj)R$N?Ct*4JiWzc@l~0QMU*#va}E*oeK(C95!q&;%$hta zfAvOiL@H|kc^B`QiIJ8d5OBRUw9Y4}@S1)SoqxQ?To~pf<*sdLNo#WPn(%KCdCdGo z$5pNkwC#AGwhQ-KEQ>f^ob-GZ;5aPcxI7B;=QL2Z#2Z=OJWLa&d~RTgZY?6@Wrhcf zMPG8E+l9bw;_%(UG|{~#L!q{`PrpcD;VbLoD%txWR$^~Fa&|8~V+TszIo;`SJ!eYc zc5j0u?Zk{EV5KYSA%K}j06)iXf&QgE7D7zmMI*mY_V=P)2@h80qF28+7nNt>F7B+8 z*e~+uRnP)uhZeT)O2S^AA0aQ$7LuSXOjaRPb*;=R3Yp7EzZq)BjJ+|$EWD;^eqv)d zg@O(@btKn_p6>=Ty6i>xG{m>*1fU+s9c?gGg>abUc>0!S0h}C&hy+uFB^T5L6Hv*b zh19Np>fQo-1Qp-3%rdchYYq0_69Ll3Z@OMD*~hnXU>nX7x)kd`LwW8x_Ew+@&&)n= zl&fDL4V?D!C0NcfWo*orh3_f-yr%Ho@B@ImN$Tgt{^YxjrQ3fGiKIh)b}|aU!3i<*R-*+6OUzn z@NEd25t7^{Lat}%l9RMM3(3Fp2f`$`uS5D6G{n=2>QP@eu=+-0I@g32v@~=vbwPC> zgX*5B7LU%tc>oYW=66n>`4G(+o~|45aNh#B^^7-9|1sNwdI{wPXdq?Ijy*}fz>UIH zz}dhaNjWHpYujg$-(`J&BZQ*iL971Jn1p~!LB#mY$Z_7gby@=5wJ}5-jFbsBCj)Qx zXLS-aP%<|we+j0vREKrtQK@Vqt%RE{57RyS6@2aqFgPUQ_ak<;UFSw@5E>4yfg{8r zcQ}DctYl$k>QLLIO6}M6^ZL>EL*`R+l+$4TJn=SXD(#`i4`e)6)O%YYi2HF(ukoUq zKH4%4bo*tLIu<$Qt4&2Ji~Z75>JzIS@xJ6)Fia$L`@y2jZdY?EHmC#r?apwA4f6Zl z87<7hauMC1C-UFXl~0?XigiLIRHvahy+c6lp%@cA$HWl<$nJtnE21*m?P}|q1odkd zsD{9%ThFRW;bwe#8yShwFY8#FFcNpi{#MtWC3TjiRYoNwJ`i7i)+~HIOO5pZM~w4v z7K`QYaz7NP)hQ)wadmv`s6g43KAwPnUd>Q2D4hmDBSPU#p-`%7Sz1Y(44mRotR8=l z?`aM}XFWR5-HUV|#qNj>Nt=AYx7I zQ)eoHm<#V+Zw)r`0nTAY? zte_r9HE}&SL74pTiLbq`-_W3<}R39*LMkLY3 z2IUemGLHuC_#u}6w{FI)APv@855*ZH5W+yz8Le4$Ty9_WnIYnA9ZSv3c2Q+V3IwsiPmCE866tZ@wWp0O7qX$eyc z(E={S8EIDaM77S(?hay_Z^c5^%rt0LZWf0B^~$rt-?$U|6>jpuT}92igt=5SGi;D% z28WnsAd%avW3Vo79|BQmfJpL>d5NP7=G@c5K1VaCovL-<-TUsn1ptTtBO6m=QqS&V zgaS=YH!+Wnlj;pCQp-cz1Ya;0rs}Bc@=_zD~>gSqoy0`bi*pBJ9 zvd|ySJN^S;DS8FJ9Td9*Y@G(!!jg>JH$)?cMn*>H8^H@AYm(u^QJQ0tkIeq=MCJcK d)ns4_mu+h>SkB!GUYkWAc5KCODRB!t`UhoPANv3R literal 0 HcmV?d00001 From 928de17e7de8e12869abc15c7d81b7974865f339 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:23:35 -0500 Subject: [PATCH 02/11] fix: CI --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dd16f7de..1bb4e1caa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,11 @@ jobs: with: node-version: 20 - uses: pnpm/action-setup@v2 + - name: Install dependencies - run: pnpm install + - name: Install Playwright Browsers + run: npx playwright install --with-deps chromium + - name: Run Playwright Component Tests - run: pnpm test-ct - uses: actions/upload-artifact@v3 if: always() From 03e64bb2590e971b82441ce4ddd18619e2b8d43c Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:26:48 -0500 Subject: [PATCH 03/11] fix: visual regression only --- .github/workflows/ci.yml | 104 +++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bb4e1caa..c21fbdeef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,44 +11,44 @@ env: jobs: - Build: - name: Build Editor - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: 20 - - uses: pnpm/action-setup@v2 - - run: pnpm install - - run: pnpm build-editor - env: - NODE_OPTIONS: '--max_old_space_size=4096' + # Build: + # name: Build Editor + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - name: Use Node.js + # uses: actions/setup-node@v3 + # with: + # node-version: 20 + # - uses: pnpm/action-setup@v2 + # - run: pnpm install + # - run: pnpm build-editor + # env: + # NODE_OPTIONS: '--max_old_space_size=4096' - Test: - name: Test - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20.x] - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - uses: pnpm/action-setup@v2 - - run: pnpm install - - run: | - pnpm build-types # typecheck - pnpm coverage - env: - NODE_OPTIONS: '--max_old_space_size=4096' - - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true + # Test: + # name: Test + # runs-on: ubuntu-latest + # strategy: + # matrix: + # node-version: [20.x] + # steps: + # - uses: actions/checkout@v3 + # - name: Use Node.js ${{ matrix.node-version }} + # uses: actions/setup-node@v3 + # with: + # node-version: ${{ matrix.node-version }} + # - uses: pnpm/action-setup@v2 + # - run: pnpm install + # - run: | + # pnpm build-types # typecheck + # pnpm coverage + # env: + # NODE_OPTIONS: '--max_old_space_size=4096' + # - uses: codecov/codecov-action@v3 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + # fail_ci_if_error: true VisualTest: name: Visual Regression runs-on: ubuntu-latest @@ -72,18 +72,18 @@ jobs: path: playwright-report/ retention-days: 10 - Lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 20 - - uses: pnpm/action-setup@v2 - - run: pnpm install - - name: ESLint - run: pnpm eslint src/ editor/ - - name: Assert schemas are all up to date - run: | - pnpm schema - git diff --exit-code + # Lint: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - uses: actions/setup-node@v3 + # with: + # node-version: 20 + # - uses: pnpm/action-setup@v2 + # - run: pnpm install + # - name: ESLint + # run: pnpm eslint src/ editor/ + # - name: Assert schemas are all up to date + # run: | + # pnpm schema + # git diff --exit-code From bcc43716dbe722951ceec5cef89abef8836de770 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:28:41 -0500 Subject: [PATCH 04/11] fix: typo --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c21fbdeef..a0c9258c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: - name: Install dependencies - run: pnpm install - name: Install Playwright Browsers - run: npx playwright install --with-deps chromium + - run: npx playwright install --with-deps chromium - name: Run Playwright Component Tests - run: pnpm test-ct - uses: actions/upload-artifact@v3 From a22d7b51dfd9512c904fa467a0d81c4882fa6981 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:29:53 -0500 Subject: [PATCH 05/11] fix: ci --- .github/workflows/ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0c9258c1..b57a27ece 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,18 +59,9 @@ jobs: with: node-version: 20 - uses: pnpm/action-setup@v2 - - name: Install dependencies - run: pnpm install - - name: Install Playwright Browsers - run: npx playwright install --with-deps chromium - - name: Run Playwright Component Tests - run: pnpm test-ct - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 10 # Lint: # runs-on: ubuntu-latest From 6733ccef9823c1e94b30e8ea31f7a95768f0c3d1 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:37:39 -0500 Subject: [PATCH 06/11] feat: update snapshots --- .github/workflows/update-snapshots.yml | 69 ++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/update-snapshots.yml diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml new file mode 100644 index 000000000..137f6b894 --- /dev/null +++ b/.github/workflows/update-snapshots.yml @@ -0,0 +1,69 @@ +# This workflow's goal is forcing an update of the reference snapshots used +# by Playwright tests. It runs whenever you post a new pull request comment +# that strictly matches the "/update-snapshots". +# From a high-level perspective, it works like this: +# 1. Because of a GitHub Action limitation, this workflow is triggered on every +# comment posted on a issue or pull request. We manually interrupt it unless +# the comment content strictly matches "/update-snapshots" and we're in a +# pull request. +# 2. Use the GitHub API to grab the information about the branch name and SHA of +# the latest commit of the current pull request. +# 3. Update the Playwright reference snapshots based on the UI of this branch. +# 4. Commit the newly generated Playwright reference snapshots into this branch. +name: Update Snapshots + +on: + # It looks like you can't target PRs-only comments: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment + # So we must run this workflow every time a new comment is added to issues + # and pull requests + issue_comment: + types: [created] + +jobs: + updatesnapshots: + # Run this job only on comments of pull requests that strictly match + # the "/update-snapshots" string + if: ${{ github.event.issue.pull_request && github.event.comment.body == '/update-snapshots'}} + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + # Checkout and do a deep fetch to load all commit IDs + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Load all commits + token: ${{ secrets.GITHUB_TOKEN }} + # Get the SHA and branch name of the comment's pull request + # We must use the GitHub API to retrieve these information because they're + # not accessibile within workflows triggered by "issue_comment" + - name: Get SHA and branch name + id: get-branch-and-sha + run: | + sha_and_branch=$(\ + curl \ + -H 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ + https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} \ + | jq -r '.head.sha," ",.head.ref'); + echo "::set-output name=sha::$(echo $sha_and_branch | cut -d " " -f 1)"; + echo "::set-output name=branch::$(echo $sha_and_branch | cut -d " " -f 2)" + # Checkout the comment's branch + - name: Fetch Branch + run: git fetch + - name: Checkout Branch + run: git checkout ${{ steps.get-branch-and-sha.outputs.branch }} + # Setup testing environment + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + - uses: pnpm/action-setup@v2 + - run: pnpm install + - run: npx playwright install --with-deps chromium + - run: pnpm test-ct + # Update the snapshots based on the current UI + - name: Update snapshots + run: pnpm test-ct --update-snapshots + # Commit the changes to the pull request branch + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: "[CI] Update Snapshots" \ No newline at end of file From 885878f67d13df4e325ba63e98e40911088e8013 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:43:45 -0500 Subject: [PATCH 07/11] fix: update --- .github/workflows/ci.yml | 26 +++++----- .github/workflows/update-snapshots.yml | 68 ++++++++++---------------- 2 files changed, 38 insertions(+), 56 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b57a27ece..40680fa0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,19 +49,19 @@ jobs: # with: # token: ${{ secrets.CODECOV_TOKEN }} # fail_ci_if_error: true - VisualTest: - name: Visual Regression - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: 20 - - uses: pnpm/action-setup@v2 - - run: pnpm install - - run: npx playwright install --with-deps chromium - - run: pnpm test-ct + # VisualTest: + # name: Visual Regression + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - name: Use Node.js + # uses: actions/setup-node@v3 + # with: + # node-version: 20 + # - uses: pnpm/action-setup@v2 + # - run: pnpm install + # - run: npx playwright install --with-deps chromium + # - run: pnpm test-ct # Lint: # runs-on: ubuntu-latest diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml index 137f6b894..f6e757fc4 100644 --- a/.github/workflows/update-snapshots.yml +++ b/.github/workflows/update-snapshots.yml @@ -1,69 +1,51 @@ -# This workflow's goal is forcing an update of the reference snapshots used -# by Playwright tests. It runs whenever you post a new pull request comment -# that strictly matches the "/update-snapshots". -# From a high-level perspective, it works like this: -# 1. Because of a GitHub Action limitation, this workflow is triggered on every -# comment posted on a issue or pull request. We manually interrupt it unless -# the comment content strictly matches "/update-snapshots" and we're in a -# pull request. -# 2. Use the GitHub API to grab the information about the branch name and SHA of -# the latest commit of the current pull request. -# 3. Update the Playwright reference snapshots based on the UI of this branch. -# 4. Commit the newly generated Playwright reference snapshots into this branch. name: Update Snapshots - + on: - # It looks like you can't target PRs-only comments: - # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment - # So we must run this workflow every time a new comment is added to issues - # and pull requests + # Run any time any issue/PR has a new comment issue_comment: types: [created] jobs: - updatesnapshots: - # Run this job only on comments of pull requests that strictly match - # the "/update-snapshots" string + slash_command: + name: slash command + # This job will only run if the comment was on a pull requests and matches the slash command if: ${{ github.event.issue.pull_request && github.event.comment.body == '/update-snapshots'}} + # Common with standard build timeout-minutes: 60 runs-on: ubuntu-latest steps: - # Checkout and do a deep fetch to load all commit IDs + # Checkout with personal TOKEN + # and hop on the PR branch - uses: actions/checkout@v3 with: - fetch-depth: 0 # Load all commits - token: ${{ secrets.GITHUB_TOKEN }} - # Get the SHA and branch name of the comment's pull request - # We must use the GitHub API to retrieve these information because they're - # not accessibile within workflows triggered by "issue_comment" - - name: Get SHA and branch name - id: get-branch-and-sha + fetch-depth: 0 + token: ${{ secrets.GH_ACTION_TOKEN }} + - name: Get Branch + id: getbranch run: | - sha_and_branch=$(\ + branch=$(\ curl \ -H 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} \ - | jq -r '.head.sha," ",.head.ref'); - echo "::set-output name=sha::$(echo $sha_and_branch | cut -d " " -f 1)"; - echo "::set-output name=branch::$(echo $sha_and_branch | cut -d " " -f 2)" - # Checkout the comment's branch + | jq -r '.head.ref') + echo "::set-output name=branch::$branch" - name: Fetch Branch run: git fetch - name: Checkout Branch - run: git checkout ${{ steps.get-branch-and-sha.outputs.branch }} - # Setup testing environment + run: git checkout ${{ steps.getbranch.outputs.branch }} + # Continue with standard build - name: Use Node.js uses: actions/setup-node@v3 with: node-version: 20 - - uses: pnpm/action-setup@v2 - - run: pnpm install - - run: npx playwright install --with-deps chromium - - run: pnpm test-ct - # Update the snapshots based on the current UI + - name: Install dependencies + run: pnpm install + - name: Install Playwright + run: npx playwright install --with-deps + # Update the snapshots - name: Update snapshots - run: pnpm test-ct --update-snapshots - # Commit the changes to the pull request branch + run: npx playwright test --update-snapshots --reporter=list + # Commit the changes - uses: stefanzweifel/git-auto-commit-action@v4 with: - commit_message: "[CI] Update Snapshots" \ No newline at end of file + commit_message: Update Snapshots \ No newline at end of file From 193836c18f7a347133c650b0221dc92ff5132506 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:46:55 -0500 Subject: [PATCH 08/11] fix: ci --- .github/workflows/update-snapshots.yml | 54 ++++++++++---------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml index f6e757fc4..4325b7924 100644 --- a/.github/workflows/update-snapshots.yml +++ b/.github/workflows/update-snapshots.yml @@ -1,51 +1,39 @@ name: Update Snapshots on: - # Run any time any issue/PR has a new comment issue_comment: types: [created] jobs: slash_command: - name: slash command - # This job will only run if the comment was on a pull requests and matches the slash command - if: ${{ github.event.issue.pull_request && github.event.comment.body == '/update-snapshots'}} - # Common with standard build - timeout-minutes: 60 + name: Slash Command + if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/update-snapshots')}} runs-on: ubuntu-latest steps: - # Checkout with personal TOKEN - # and hop on the PR branch - - uses: actions/checkout@v3 + - name: Checkout code + uses: actions/checkout@v3 with: - fetch-depth: 0 - token: ${{ secrets.GH_ACTION_TOKEN }} - - name: Get Branch - id: getbranch - run: | - branch=$(\ - curl \ - -H 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} \ - | jq -r '.head.ref') - echo "::set-output name=branch::$branch" - - name: Fetch Branch - run: git fetch - - name: Checkout Branch - run: git checkout ${{ steps.getbranch.outputs.branch }} - # Continue with standard build + ref: ${{ github.event.pull_request.head.sha }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 20 + node-version: '14' + - name: Install dependencies - run: pnpm install + run: npm install + - name: Install Playwright - run: npx playwright install --with-deps - # Update the snapshots + run: npx playwright install + - name: Update snapshots run: npx playwright test --update-snapshots --reporter=list - # Commit the changes - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Update Snapshots \ No newline at end of file + + - name: Commit and push updated snapshots + run: | + git config --global user.name 'Your Name' + git config --global user.email 'you@example.com' + git add . + git commit -m "Update snapshots" + git push From 4dded4b0600f063e2038e751af7076b3ec8a2b23 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:49:11 -0500 Subject: [PATCH 09/11] fix: ci --- .github/workflows/update-snapshots.yml | 32 ++++---------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml index 4325b7924..8ff97db15 100644 --- a/.github/workflows/update-snapshots.yml +++ b/.github/workflows/update-snapshots.yml @@ -5,35 +5,13 @@ on: types: [created] jobs: - slash_command: - name: Slash Command - if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/update-snapshots')}} + update_snapshots: + if: github.event.comment.body == '/update-snapshots' && github.event.issue.pull_request runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha }} - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: '14' - - - name: Install dependencies - run: npm install - - - name: Install Playwright - run: npx playwright install + uses: actions/checkout@v2 + # Add your steps here to update snapshots - name: Update snapshots - run: npx playwright test --update-snapshots --reporter=list - - - name: Commit and push updated snapshots - run: | - git config --global user.name 'Your Name' - git config --global user.email 'you@example.com' - git add . - git commit -m "Update snapshots" - git push + run: echo "Updating snapshots..." From 5c99f88195c379cb6bb4ab3c1063f15d59e5e9a7 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:50:58 -0500 Subject: [PATCH 10/11] fix: ci --- .github/workflows/update-snapshots.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml index 8ff97db15..969b36344 100644 --- a/.github/workflows/update-snapshots.yml +++ b/.github/workflows/update-snapshots.yml @@ -1,17 +1,14 @@ -name: Update Snapshots - +name: Slash Command Dispatch on: issue_comment: types: [created] - jobs: - update_snapshots: - if: github.event.comment.body == '/update-snapshots' && github.event.issue.pull_request + slashCommandDispatch: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v2 - - # Add your steps here to update snapshots - - name: Update snapshots - run: echo "Updating snapshots..." + - name: Slash Command Dispatch + uses: peter-evans/slash-command-dispatch@v4 + with: + token: ${{ secrets.PAT }} + commands: | + echo "hello world" \ No newline at end of file From 0baca2721df6e7aa1391bcf9822859d878722ca0 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 22 Feb 2024 18:52:54 -0500 Subject: [PATCH 11/11] fix: ci --- .github/workflows/update-snapshots.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml index 969b36344..f0f0fe877 100644 --- a/.github/workflows/update-snapshots.yml +++ b/.github/workflows/update-snapshots.yml @@ -11,4 +11,5 @@ jobs: with: token: ${{ secrets.PAT }} commands: | - echo "hello world" \ No newline at end of file + deploy + - run: echo "hello there" \ No newline at end of file