From e0771c1f3ff41b3b470ee19c64a3dabba046a448 Mon Sep 17 00:00:00 2001 From: mildred Date: Fri, 8 Mar 2024 23:38:18 +0100 Subject: [PATCH] Fix login flaky tests --- src/constants/env_constants.ts | 3 ++- src/page-objects/AD_AddMembershipPage.ts | 24 +++++++---------- src/page-objects/AD_ImportDataPage.ts | 22 +++++++-------- src/page-objects/AD_ViewImportedDataPage.ts | 25 +++++++----------- src/page-objects/COMM_LoginPage.ts | 25 +++++++++++------- src/steps.ts | 24 ++++++++++------- tests/00-COMM-loginFunctionality.spec.ts | 3 +-- ...ulnerability-alienUser-with-user5Data.xlsx | Bin 12947 -> 0 bytes 8 files changed, 63 insertions(+), 63 deletions(-) delete mode 100644 vc_excel/financial-vulnerability-alienUser-with-user5Data.xlsx diff --git a/src/constants/env_constants.ts b/src/constants/env_constants.ts index 9904f31..c294edb 100644 --- a/src/constants/env_constants.ts +++ b/src/constants/env_constants.ts @@ -1,9 +1,10 @@ /*Login*/ +export const ENCRYPTION_KEY = "1234" export const ADMIN_EMAIL = "idhub_admin@pangea.org" export const ADMIN_K = "1234" export const KO_ADMIN_K = "876" export const URL_IDHUB = "https://idhub1-autotest.demo.pangea.org" -//export const URL_IDHUB = "https://idhub-nightly.demo.pangea.org" +//export const URL_IDHUB = "https://idhub1-nightly.demo.pangea.org" export const USER1_EMAIL = "user1@example.org" export const USER2_EMAIL = "user2@example.org" diff --git a/src/page-objects/AD_AddMembershipPage.ts b/src/page-objects/AD_AddMembershipPage.ts index 3f30a9a..151456e 100644 --- a/src/page-objects/AD_AddMembershipPage.ts +++ b/src/page-objects/AD_AddMembershipPage.ts @@ -131,22 +131,18 @@ export class AddMembershipPage { async alertUserCreationMessageIsValid(): Promise { - await this.page.waitForSelector('.alert.alert-success.alert-dismissible'); - const element = await this.page.$('.alert.alert-success.alert-dismissible'); + try { + await this.page.locator('.alert.alert-success.alert-dismissible').waitFor({ state: 'visible', timeout: 5000 }); + // If the success message is found and visible, retrieve its text content + const message = await this.page.locator('.alert.alert-success.alert-dismissible').textContent(); - if (element !== null) { - const isVisible = await element.isVisible(); - if (isVisible) { - const text = await element.innerText(); - console.log(text); - if (text === ALERT_USER_CREATED_SUCESSFULLY_MESSAGE) { - return true; - } - } + // Compare the retrieved text with the expected error message + return message?.trim() === ALERT_USER_CREATED_SUCESSFULLY_MESSAGE; + + } catch (error) { + console.error('Failed to check success message:', error); + throw error; } - - return false; } - } diff --git a/src/page-objects/AD_ImportDataPage.ts b/src/page-objects/AD_ImportDataPage.ts index 4341032..5a788d6 100644 --- a/src/page-objects/AD_ImportDataPage.ts +++ b/src/page-objects/AD_ImportDataPage.ts @@ -102,20 +102,18 @@ export class ImportDataPage { } async alertFileImportedUnsuccessfully(expectedMessage: string): Promise { + try { - const element = this.page.locator('.alert.alert-danger.alert-dismissible'); - if (element) { - const isVisible = await element.isVisible(); - if (isVisible) { - const text = await element.innerText(); - console.log(text); - expect(text).toBe(expectedMessage); - return true; - } - } - return false; + await this.page.locator('.alert.alert-danger.alert-dismissible').waitFor({ state: 'visible', timeout: 5000 }); + + // If the success message is found and visible, retrieve its text content + const message = await this.page.locator('.alert.alert-danger.alert-dismissible').textContent(); + + // Compare the retrieved text with the expected error message + return message?.trim() === expectedMessage; + } catch (error) { - console.error("Failed to check for unsuccessful file import alert:", error); + console.error('Failed to check for unsuccessful file import alert:', error); throw error; } } diff --git a/src/page-objects/AD_ViewImportedDataPage.ts b/src/page-objects/AD_ViewImportedDataPage.ts index 27fd43f..50a8737 100644 --- a/src/page-objects/AD_ViewImportedDataPage.ts +++ b/src/page-objects/AD_ViewImportedDataPage.ts @@ -107,7 +107,7 @@ export class ViewImportedDataPage { throw error; } } - + async isFileSuccessfullyLoaded(fileName: string): Promise { try { const row = this.page.locator(`tr:has-text('${fileName}')`); @@ -134,24 +134,17 @@ export class ViewImportedDataPage { async alertFileImportedSuccessfully(): Promise { try { - await this.page.waitForSelector('.alert.alert-success.alert-dismissible'); - const element = await this.page.$('.alert.alert-success.alert-dismissible'); + await this.page.locator('.alert.alert-success.alert-dismissible').waitFor({ state: 'visible', timeout: 5000 }); + + // If the success message is found and visible, retrieve its text content + const message = await this.page.locator('.alert.alert-success.alert-dismissible').textContent(); + + // Compare the retrieved text with the expected error message + return message?.trim() === ALERT_FILE_IMPORTED_SUCCESSFULLY; - if (element !== null) { - const isVisible = await element.isVisible(); - if (isVisible) { - const text = await element.innerText(); - console.log(text); - expect(text).toBe(ALERT_FILE_IMPORTED_SUCCESSFULLY); - return true; - } - } - - return false; } catch (error) { - console.error("Failed to check for successful file import alert:", error); + console.error('Failed to check for successful file import alert:', error); throw error; } } - } \ No newline at end of file diff --git a/src/page-objects/COMM_LoginPage.ts b/src/page-objects/COMM_LoginPage.ts index d386929..c61774e 100644 --- a/src/page-objects/COMM_LoginPage.ts +++ b/src/page-objects/COMM_LoginPage.ts @@ -1,4 +1,4 @@ -import { type Page, type Locator } from '@playwright/test'; +import { type Page, type Locator, expect } from '@playwright/test'; import { ERROR_INCORRECT_EMAIL_PASSWORD } from '../constants/constants'; export class LogInPage { @@ -41,18 +41,25 @@ export class LogInPage { } } - async errorMessageIsValid(): Promise { + async errorMessageIsValid() { + try { - const isVisible = await this.page.locator('.well.well-small.text-error').isVisible(); - if (!isVisible) { - return false; + + try { + await this.page.locator('.well.well-small.text-error').waitFor({ state: 'visible', timeout: 5000 }); + } catch (error) { + console.error('Error message not found:', error); + return false; // Return false if the error message is not found } + // If the error message is found and visible, retrieve its text content const errorText = await this.page.locator('.well.well-small.text-error').textContent(); - return errorText === ERROR_INCORRECT_EMAIL_PASSWORD; + + // Compare the retrieved text with the expected error message + return errorText?.trim() === ERROR_INCORRECT_EMAIL_PASSWORD; } catch (error) { console.error('Failed to check error message:', error); - throw error; + throw error; } - } -} + } +} \ No newline at end of file diff --git a/src/steps.ts b/src/steps.ts index 8a9006c..210bf34 100644 --- a/src/steps.ts +++ b/src/steps.ts @@ -6,7 +6,7 @@ import { CREDENTIAL_TYPE_DATASTORE_UNDEFINED, CREDENTIAL_TYPES_DATA_STORE } from import { FinancialVulnerabilityCredential_fields } from './interfaces/credential_interfaces' import { CREDENTIAL_TYPES_FIELDS_LIST, CREDENTIAL_TYPES_REQUIRED_FIELDS_LIST } from './constants/credential_fields' import { LogInPage } from './page-objects/COMM_LoginPage' -import { ADMIN_EMAIL, ADMIN_K, USER_K } from './constants/env_constants' +import { ADMIN_EMAIL, ADMIN_K, ENCRYPTION_KEY, USER_K } from './constants/env_constants' import { LeftMenuAdminPage } from './page-objects/AD_LeftMenuAdminPage' import { LeftMenuUserPage } from './page-objects/US_LeftMenuUserPage' import { TemplatesPage } from './page-objects/AD_TemplatesPage' @@ -16,34 +16,40 @@ import { UserPersonalInformationPage } from './page-objects/AD_UserPersonalInfor import { ImportTemplatePage } from './page-objects/AD_ImportTemplatePage' import { ViewImportedDataPage } from './page-objects/AD_ViewImportedDataPage' import { User } from './interfaces/User' +import { EncryptionKeyPage } from './page-objects/AD_EncryptionKeyPage' +import { DataProtectionPage } from './page-objects/COMM_DataProtectionPage' export async function loginAsAdmin(page: Page, url: string) { try { const loginPage = new LogInPage(page); + const encryptionKeyPage = new EncryptionKeyPage(page); + const dataProtectionPage = new DataProtectionPage(page); + await loginPage.visit(url); await loginPage.login(ADMIN_EMAIL, ADMIN_K); let currentTitle = await page.title(); + console.log("current title: ", currentTitle); if (currentTitle === 'Encryption Key – IdHub') { //code to set Encription Key - page.getByPlaceholder('Key for encrypt the secrets').click(); - await page.getByPlaceholder('Key for encrypt the secrets').fill('1234'); - await page.getByRole('button', { name: 'Save' }).click(); + await encryptionKeyPage.enterKey(ENCRYPTION_KEY); + await encryptionKeyPage.clickSaveButton(); + await expect(page).toHaveTitle('Data protection – IdHub'); currentTitle = await page.title(); } if (currentTitle === 'Data protection – IdHub') { // Code to accept terms and conditions - await page.click('#id_accept_privacy'); - await page.click('#id_accept_legal'); - await page.click('#id_accept_cookies'); - await page.click('a[type="button"]'); + await dataProtectionPage.checkAcceptPrivacyCheckBox(); + await dataProtectionPage.checkAcceptCookiesCheckBox(); + await dataProtectionPage.checkAcceptLegalCheckBox(); + await dataProtectionPage.clickConfirmButton(); } await expect(page).toHaveTitle('Dashboard – IdHub'); - + } catch (error) { console.error(`Failed to login as admin: `); throw error; diff --git a/tests/00-COMM-loginFunctionality.spec.ts b/tests/00-COMM-loginFunctionality.spec.ts index 7e080fc..8d5d101 100644 --- a/tests/00-COMM-loginFunctionality.spec.ts +++ b/tests/00-COMM-loginFunctionality.spec.ts @@ -6,13 +6,12 @@ import { ADMIN_EMAIL, KO_ADMIN_K, KO_USER_K, URL_IDHUB, URL_PASS_RESET, USER1_EM test.describe('Admin login functionality', () => { test('Successful login as admin', async ({ page }) => { await loginAsAdmin(page, URL_IDHUB); - await expect.soft(page).toHaveTitle('Dashboard – IdHub'); }) test('Unsuccessful login as admin', async ({ page }) => { const loginPage = new LogInPage(page) await loginPage.visit(URL_IDHUB); await loginPage.login(ADMIN_EMAIL, KO_ADMIN_K) - expect.soft(loginPage.errorMessageIsValid()).toBeTruthy(); + expect(loginPage.errorMessageIsValid()).toBeTruthy(); }) test('Navigate to Forgot password page from login page', async ({ page }) => { const loginPage = new LogInPage(page) diff --git a/vc_excel/financial-vulnerability-alienUser-with-user5Data.xlsx b/vc_excel/financial-vulnerability-alienUser-with-user5Data.xlsx deleted file mode 100644 index 88909c1e27fa6df30ca6f4490111a59502fc95eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12947 zcmeHt1zTHbw|3A%(c zmgxo}3*U-8zTcsjSgGcRVg2skg+e3G z+p!Q&VeuH#@AbrkdP}SnB%)jsmgx2^^XS}2B)PqZ!Qf$otiMKJU4qCq&7?JzC5+pii*Tqz2i){Q0YLCUP?<1&be!uH%4{f7b9;H z*;(yy$IpgkOI~KaN8?K$F5zG8P%!8za<9n9rc~quK=|R)(YZi5yQca&F6C_Vy%`tf z`kbfX(?$Q1v5R_s0lX>0q&xC!N#E=vx-TghiJTbU!1`*0`A)9W9H#dAN0`|3?l;X46K`s{`Rd)*0kiDew@mbQ1R>XVm;k`b3j{#! zZ_bOV(`F0~a^7{2Z^D6`SJ%PV%8`-b=lQ=*`+wL||MJz#;-n=Z-XaH|`o9p5v~n-g zkW5)I3hzrBJ%PPVdD3G~iOK)^bSom@lv7XOkzV1N>7H_JQl00Phy1|>B}o|8Kiy=y zS*1(zsj>0Zn|KG2sAI7%KN72wqta{UD4N}zg=^mMvaQ@e2J#nQEN4-bUVok@EpraF zL^I;F$!2(j+n9o9EG3s|Q zi-L!jthp*V1!SF--a`kWLk}c|5CoJOPg(n?mXit!BIH4-5E$JtTF)~bJiR^gv6Zi8pS5E)$L8+Z`1-c1Yf55}~W6sbCh1F`zS(h2sSNkhJ>R zaP5xok3hxaa{t_`2zPa=ZACBB}d6nB_gt4Edg!Wlrcx$}F zSc)$(2(`2f5E>NIWn(E9SZrfS}8O;n&4v>65@EDR+++v7W$^ECp0%NX=A#D zTYpFku@Ye$e_5uy4*N(^LR=TIZpN^4a1LMBA-YH0#8>~(3Ihtm{f`zU>>l0`I&<8q zd;w#8|4MqR;8JEm5b1Y7bphJ%q<1vacQ7_mbaF7aF?IYICOwrjZI*$^k2Zd50uw%@ zeB_jsiCi)WkfHbuG8xZbdh3J}NR)Zg7a9)0Ji@igXd$7%6)FKI9w&V1m?~VO{5%IUk`P*tB3>~q9{;*AGEb1xoYG!GIen)ce{2oBTPFP@ z!gxR1y1n%=3Jo458zH6i83OVI0VRsoUC2?~KBIP>@b~xWIkhVUw#w&jWdTetn>WLK z7AUO7Qh9H^P4ZqNZiyf07A>Q-^v}x-W5jYZ2(YQM$%xHV?)n#ffh&N>m1Av9qfVu$ zPTLNkmzU*)Q$v@KprhlH%MDqY+Bj{h;&lZEqbbj%YE}xAQ#-9XFHka96xqKF*V)U8rW0vYet4cQZj+k- zPUThqDgKAg_@Qh>t(Q?p6;cRL))0%SWca`_+W{!@Ivww`Y4+R?dqSq>sjR0B7(tHY z=J))N2s*^mL+xj)tw zJm5B|=2S|j!y2xg?V=AaPD*|D91^0cStLXFG(B3kpi+3v4yAkzb3zlh{k-;S-HP1b zkTEKcdJ*nMy$#-jgn#Ab@L~r%yNreI@_6zFLLuX=qf6{FW`Hu;e+jhDCSH-MpgWjY z008dqoHKIJcl}8>Mi*->YfXE%|l;6444j>qbwjPN$Q1VL@*XzZX z^LeC{(NW&7aJeKTvwSaH=R}-v8LegA1%nj|b#&!i&+t(bO`gXVwF`|?%BiD?yz(;86tGDDSwUCd8{V6cJpG*cgYYEBS*oed*Zo^uf_Z`>0nb2` zAbL*SYx-2+Y*Ae%xq1TMbT*lSyqUh`N+TBEWmU3jUxCK}wSlI_?pO@bjeMtA{T zTpZ8Tgu_`+eaL8GP=_cYvn4JJ+-|JIdsZO;#s(=IuAh&_sl@qLRv}hC}Ln8PoKOiUEdS&Xljx8DY-N%glJ7yEv`1udf*D0su|~M|$~LtWaI$|DagS9Swd$!A zJ6GQHWsrQH&J#R%a(V8mZf6QRDCi_s^C+?<kbI|0)_HClc2MVa`^V zb3d&5s8wIpMV5*XVrUK7-7Z-hyM7N=3lGNl6kjg$MrhHIaup$Xy^8h&Wx_`=>`^$DdQS2 zxkxK3^YcykEBID(;URonDz!QH+i9B{Zy_R2v=z%ckFuSAb0y`;&ayVesdG#k3nz% z0Ky-8fRnqG@o&MuMP>bC8V9m>h0_bfRt9S&u{24x1zW@y1x@88o&$m(TfX9D*B=Lx zd8SHU9-fnx}UCHmXd&rKQ+H_Y>lbrHkg$&#}504OK&|pXSp-a z7qehMnJC2cti}&L#XO%ge7VYuZKfw)0z=J}SSg=th7we6Bga!!br6L-PEfEf(=TQ# zTB7!k7^1Qd7F^FFi7vL&goDd`iGCA3`@Z$kVbE1=n$=k8w48o^^L}euL@a0?KAWu* za!&?TN_n59#MPic_R%htR(()G*H4>lIPTjH^$KSyX*yC;$HfsH)SHX$yan8A2}#ua z0aDgo$7!{hciUeb9Ms_ucHf1jyX+}~bsh9LFO{qebS0p9uk(zxe-PfxWf93pWML@4 ziu6kwUELBG@DVbp*FgHFgrmxrE;_=Gf`!{;q`--j3_vUs(vw+2Wlx^0%l)Pn#%cVZ zf`{WrF6$btNL2te<6)L%>3HaKG5e5$KrAmG6U>ieN-4j(UA}W?C1)1d-sZc`PO+9r zcU0pKuh@s^3FC((64N)ql}Z)c*>$jXddppu=*wX1Xfv+O(JJpo7&s{grXFJ6l2qbb zJjFceW+QAqB34W+BFwINS+R?lX<5wbq=SQrW}DwJf?PF^W!c5z-x=TN0T+-*iSwoI z*ey8ne(XnY4Msqa-8ogqvAoW^WoSptDfdlE5e43W9k{oX3h-M=&k8Pu0=)I{ZX0q0rARrpYti2 z>IvTr>#;8!hDLL{-tCln;n@;@ze@)NY~#9ijP0&R$p1L&a{H!d-5&hFZE?`N z;H&#%bCQs(XEKMe2F?!D0 zil2*tGnRaiUh6Jgy3&`G^1Gt1DO&*?;U@bo&84H(y<>h5Ohu{!5?ah&J_}MSn@@1m zW}|*rtHrig@)njP_93_9CLPd<@pe=DpKBSxwN_{_fLtg0DZ z5J~4n5RsfNEXzvPv=6A_{bwxbDph&)8gvH|6c0#$j|D%AAV)J}V<*QyO6cF7y#1d7 zdL~ZVvKt5=d>ZidD(~Sq|3NQ-Yc&V=s&aVW+qWvQXGV9y=F{^7XJj=rm&-wcsN_Yd zs7IX|;#>?*Uw-Sh-1KW0u7gAu{E+d{D}p7WFKlta-P` zOHkDO=TiCy%Dd!)(&_*V005Nf{HLD$V=4b!#Qen7Xi$z5bV_@TPqJ={@`{Zvw6a94 zd?`Xg*{33k*|*MK%DTq4r0qk$OuC$W`2w0i-@+if=`k~~oo|T71KK*U1Ws@}K}gO~ zIov}r##Wcm_C(Bxu2fQ`80WPE`1=z~+w~D73BDA1->4Y5G=Hn|)zP?~GkP%-gSr45 z)j0`Clk?IwGLu5T#Ajl4a`n`2VMe(W2>PEZ^oy=F?A+u$8FT6pgx>ek9kOdyG5fjD zRcs}u;#baP?@4XFubDdEzZ1+t)1u9rWBVpS5mh@(Q#g24!A0KwS#I!Z+S@f?10SOS zC9#rHmm!QS%WGjqqDDE0(jnilFE6jpH8^WBMb9PWz+*7dHGepx<@Njiuwc{8ve~?9 z{bn&+Zk4%i5eR+vSB}dCrXS=nc2t+zY=8=`;t!H)57HQy3fZ?>%CW)D0&A0t{jlN~ zj;F_`;E3O>@YL&nfTP>NB?rU}u#QO-Q@U=yN|%T=dqjvHXha{c3@o7;aPV)vo!(QE zS;7Xxaq8f?<(2V3~Kc|6S%gmj;OuZ73p%gv@iO7)sMW0LE% z^5l(_9iPoj&nk8BZbYdg6U;Irdvh;D8@52nB~F#4CQfo5rh`%}TSgvx-y?$3Bx^-3 zf7cW31fTjNo@ing)4#EPOmv=obCR@+)pKw_+6{)m!rwPCm`E>=HB@G)NuuVRICG-! z$7ni;fpDUYGD;XRO;pG-YcdqSx^m&G*lf~fa|G*Ul}6%-iF$!8$p$49^sFL+{`-fT za(!KA$N+Uz$f*fr$gwqXGNC9VbUa=EjYky7SII;iofu7Is3xu&5uMu+)Li0Ukk;Ns z>rS6aP65UIjpk4~Ggu-00_&ig#^95|aV4gr`9__rD$GOq&k2y;via84=vb&ojQk@SQiXa(3Tb4}n z+bAc?&5q|0Aab+x=Qm)3^y{&r&(+=z;x5mzSPthdLD=eB>K#$c;LX+OZnJ;_tAGUu zvxR3mIdxH|omjvzYT?BL_dD2$Fkq3s0Z%!E_5b|)gT6~j*3wMFMR27W`hLpkCe$1u z!2HqICyE8S$#RLvReBf`cG`&1ES2bw#}diNRc-KRW6D7D08bl_lV|p)SO3E#qZk33 z0MPWP!od9$#SqBxIu_WcfXNf68CfOx7DuZ~uzqc_Ph{U7Vs0%Bm4-tVRPFEflTr(pts^vDA&T>Gp91*j^!2RQ*SE-go7=Ex7e~ z-H*xNuv&w$x*tRWSTlkWu|&LJ>;pa=O>$kjDC&LjcJLw8I}#pb?J&kJi`pan%{zXb z$8?1(UwDDKg!7ystHiOtuw2iO>om{q=#+5>L13`E_7)ti1tNBLh$?*W>Gq{ zQnalaj?R`c>wsj9KIi6DnFgjpQve6NfF-SDtVwx;iut%12LH_w(|4IBoi9yy;7^I< zDQVwbn%72*tAr2+yvyk~=ZB(}OlCWAV|wR5oz>l#6i(ZnK^vV%k$c+KEfzJoL4;#V z_e+|6pU`0}n1iPX3cremseMCn4EASs#37?+csVgW^Hf>muIaC#kas@v`stkdr1akYOxnU@t! zz{l%xl~=H9wbEytzU$*B;l~O-&x_<OvgsX}B;Q@B6EU zJy~7v>x0vEp7RcNC<3%iuTetfPTrJDIZT+PGX&$fwtNj`p4W#EN!qN|qMa7MOD9)mtC zI%6EvRyNV;(iGkh$Kg3FpnBbw|@%3 z9)t!)_k7Eft6gk|2@;BymymJ`;WoU-T{yNRX-a%F*-&F3=M-HUSTbIob)VX6>y{M3 zBM;sxWg&yQ?hqIE0*;We!Q^5VCdbpr$#4og6%*Rh)4no;0Etk2HPaHEJnA zvHhYRpWpS0LfJ@5-{3c9^ok3+)S)hnw%ju4q#{hgF&^OYCPmZHQ&37y>eHIdm0kma zI4q$^vC=jzHTd3^U#a+tiFg(xsR{w% zk_rhdDV2DAK({VD~UqukpNnuK1K zW7tL^<~oR6s4w1|Zz0iF%+p@{@%CTX_UI7e=YYy3JUGMxo9`5=h>S3Y;=P2$#gx^+ zGqLJtU3L7-d_z(vILU|yC4IVR>QUm(>DJxpJ(f48QbryHnvN3IVQbM4KfM~`qY)I5 z!a9Pe8^6u2CvmILU#5kW_v&RM5W^JHBzQH!u_%lym6~9!c5!mkwM^A`KE}PusXH*$ zaNsm6dI_c%LDy`q(5LrwuW*Jwsww;hR%*o zw$?xEIFO3qzcWzKMxGwi`BVBLbSm~3u-k0NUNoU3PP0^NN+wY;;GEt=rK4Go8jAc9 zt@*muQET+jd~K%`X*Exts{$U)EeRM&2BS!82KQLK9t$PHYggoM%a7p}M^G`eoc1_~ zO{*<8l@|)o-C%%h&Yy{pu{epxdM|~u&4P69M9(Un7g!dFRU62!*mqKlt1%d;)|lD%C6mStMD`i$3N$}wI1{1Lg58&@nDA# z_b=o~$?8y-Ok&OeLVtNuCCSP+qLK>Y#azmiGc&hEcV0#WFX zBRx*qcKK&h1mcDeeifH-%s@=_s?q=~uHIDUvtdp(S!iVJ@w{yP$}unNY}P}>_eh#Ib|*a_*taaJ*jkj6iZ0E3 znXg4O*~r^lr8dzkZ|mDH0~@QD`8b!)pR>93Ch{7uh`tRUfs1^PDUq|m3~}&6{FB6( zVjb&-U54mBG+s{JDb%$uu*?>MEmno6!h;1*LEuJJXQPhF8+;R>5=IaQHI8i;BRz=K zES7#HDONEM+$t!-iA8ZXJhT)*X}U;rrjah@^s_YuxOi!SJqE=h zfuM_Ot2e8Pba8r!=qb)wATlKLqFjkdoONg_(|EuVKYh3Fo1_TZJxlS&!+Ceo*?6t@ zyfonr?g(UcWGSLu-i`(%hzS}QRB3Z*LfOiQ&=!?w7n^#??Rv1D_|Ho*G^PVM=IfP( z-Hm%I8qU3_KR7pF8<)RBH7?L6FKGxSv4f&X=~rV2BEq+Ep2WCFZKM^g|OSGn}xT z6dQ-W?+EaR(eV#LS4gGtUC;Fgiq(HK$+O=TsMfg__p$o46|t8AeG&467xC6jwsM`b z;{7Z+sHt(-C+45IL6|v!#W^gGb63^DG>sJXr&-lvYj^S1kch1w# z-mjx|EZytkE5Mz0G03qAU*^zMzntKs&M3+}CYZqXDX}Kz0s_)ImdC4FZVapPEfBnt zc-J`6n?Xp991FW|ETDif=shWE>y`@SN1{tPa3RI88nTqvq;(i*nKIi>1w5NrUMFl!c=!vd%t@*^&=^#4ylJs8pZu8 zS^UYi>=1ERCI$aj#$wp@MHv<9EPDx&rXk{Kwh$iV9jhrw`va?E=yKzONGty!makk+ zlQk4X8nww@Nzlo(YIJ_of$1u$!{I?5m*`4bFsT|7wYkHBc;YVtch`1ZO4y^Zj_-l_m1yKi<4BBzp6GM||_7Ip7V-KzFS<5c4OXBS>!jcfdc#GQXny+VuMqMH>G%lwVtbzXJTd4*Sy_0C)gR+Ww^~`xW5t zx%XcI#3=p(@JA;8tM%Vgp1)cnQUAsIKMByU=D!B*KLH}?e*5}=3*mB-P$1d^0EnQ! N6p-iaF#LS>{{U;2Lc;(6