From e897d7360de7e9d8b2825cf44a99058abd7f217e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 29 Nov 2023 16:39:27 +0000 Subject: [PATCH] Migrate user-view.spec.ts from Cypress to Playwright (#11944) * Migrate user-view.spec.ts from Cypress to Playwright Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add bot support & update screenshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add screenshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Use JSHandle Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove stale snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update element-web-test.ts --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- cypress/e2e/user-view/user-view.spec.ts | 55 ----- playwright/.gitignore | 4 +- playwright/e2e/user-view/user-view.spec.ts | 35 +++ playwright/element-web-test.ts | 10 + playwright/global.d.ts | 16 +- playwright/pages/bot.ts | 220 ++++++++++++++++++ ...nder-the-user-view-as-expected-1-linux.png | Bin 0 -> 17497 bytes .../user-view.spec.ts/user-info-linux.png | Bin 0 -> 12530 bytes 8 files changed, 282 insertions(+), 58 deletions(-) delete mode 100644 cypress/e2e/user-view/user-view.spec.ts create mode 100644 playwright/e2e/user-view/user-view.spec.ts create mode 100644 playwright/pages/bot.ts create mode 100644 playwright/snapshots/user-view/user-view.spec.ts/UserView-should-render-the-user-view-as-expected-1-linux.png create mode 100644 playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png diff --git a/cypress/e2e/user-view/user-view.spec.ts b/cypress/e2e/user-view/user-view.spec.ts deleted file mode 100644 index 12097ec786..0000000000 --- a/cypress/e2e/user-view/user-view.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2022 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/// - -import { HomeserverInstance } from "../../plugins/utils/homeserver"; -import { MatrixClient } from "../../global"; - -describe("UserView", () => { - let homeserver: HomeserverInstance; - - beforeEach(() => { - cy.startHomeserver("default").then((data) => { - homeserver = data; - - cy.initTestUser(homeserver, "Violet"); - cy.getBot(homeserver, { displayName: "Usman" }).as("bot"); - }); - }); - - afterEach(() => { - cy.stopHomeserver(homeserver); - }); - - it("should render the user view as expected", () => { - cy.get("@bot").then((bot) => { - cy.visit(`/#/user/${bot.getUserId()}`); - }); - - cy.get(".mx_RightPanel .mx_UserInfo_profile h2").within(() => { - cy.findByText("Usman").should("exist"); - }); - - cy.findByText("1 session").should("be.visible"); - - cy.get(".mx_RightPanel").percySnapshotElement("User View", { - // Hide the MXID field as it'll vary on each test - percyCSS: ".mx_UserInfo_profile_mxid { visibility: hidden !important; }", - widths: [260, 500], - }); - }); -}); diff --git a/playwright/.gitignore b/playwright/.gitignore index 2719122491..1d4efea520 100644 --- a/playwright/.gitignore +++ b/playwright/.gitignore @@ -2,5 +2,5 @@ /html-report/ /synapselogs/ # Only commit snapshots from Linux -/snapshots/*/*.png -!/snapshots/*/*-linux.png +/snapshots/**/*.png +!/snapshots/**/*-linux.png diff --git a/playwright/e2e/user-view/user-view.spec.ts b/playwright/e2e/user-view/user-view.spec.ts new file mode 100644 index 0000000000..a2191b8583 --- /dev/null +++ b/playwright/e2e/user-view/user-view.spec.ts @@ -0,0 +1,35 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { test, expect } from "../../element-web-test"; + +test.describe("UserView", () => { + test.use({ + displayName: "Violet", + botCreateOpts: { displayName: "Usman" }, + }); + + test("should render the user view as expected", async ({ page, homeserver, user, bot }) => { + await page.goto(`/#/user/${bot.credentials.userId}`); + + const rightPanel = page.getByRole("complementary"); + await expect(rightPanel.getByRole("heading", { name: bot.credentials.displayName, exact: true })).toBeVisible(); + await expect(rightPanel.getByText("1 session")).toBeVisible(); + await expect(rightPanel).toHaveScreenshot("user-info.png", { + mask: [page.locator(".mx_BaseAvatar, .mx_UserInfo_profile_mxid")], + }); + }); +}); diff --git a/playwright/element-web-test.ts b/playwright/element-web-test.ts index 8777b84b41..dcc4367b97 100644 --- a/playwright/element-web-test.ts +++ b/playwright/element-web-test.ts @@ -28,6 +28,7 @@ import { ElementAppPage } from "./pages/ElementAppPage"; import { OAuthServer } from "./plugins/oauth_server"; import { Crypto } from "./pages/crypto"; import { Toasts } from "./pages/toasts"; +import { Bot, CreateBotOpts } from "./pages/bot"; const CONFIG_JSON: Partial = { // This is deliberately quite a minimal config.json, so that we can test that the default settings @@ -67,6 +68,8 @@ export const test = base.extend< room?: { roomId: string }; toasts: Toasts; uut?: Locator; // Unit Under Test, useful place to refer a prepared locator + botCreateOpts: CreateBotOpts; + bot: Bot; } >({ cryptoBackend: ["legacy", { option: true }], @@ -173,6 +176,13 @@ export const test = base.extend< toasts: async ({ page }, use) => { await use(new Toasts(page)); }, + + botCreateOpts: {}, + bot: async ({ page, homeserver, botCreateOpts }, use) => { + const bot = new Bot(page, homeserver, botCreateOpts); + await bot.start(); + await use(bot); + }, }); test.use({}); diff --git a/playwright/global.d.ts b/playwright/global.d.ts index 8b4a280153..87fdebcc5f 100644 --- a/playwright/global.d.ts +++ b/playwright/global.d.ts @@ -14,7 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { type MatrixClient } from "matrix-js-sdk/src/matrix"; +import { + ICreateClientOpts, + type MatrixClient, + MatrixScheduler, + MemoryCryptoStore, + MemoryStore, +} from "matrix-js-sdk/src/matrix"; import { type SettingLevel } from "../src/settings/SettingLevel"; @@ -26,5 +32,13 @@ declare global { mxSettingsStore: { setValue(settingName: string, roomId: string | null, level: SettingLevel, value: any): Promise; }; + // Partial type for the matrix-js-sdk module, exported by browser-matrix + matrixcs: { + MatrixClient: typeof MatrixClient; + MatrixScheduler: typeof MatrixScheduler; + MemoryStore: typeof MemoryStore; + MemoryCryptoStore: typeof MemoryCryptoStore; + createClient(opts: ICreateClientOpts | string); + }; } } diff --git a/playwright/pages/bot.ts b/playwright/pages/bot.ts new file mode 100644 index 0000000000..298c744e80 --- /dev/null +++ b/playwright/pages/bot.ts @@ -0,0 +1,220 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { JSHandle, Page } from "@playwright/test"; +import { uniqueId } from "lodash"; + +import type { MatrixClient, ISendEventResponse } from "matrix-js-sdk/src/matrix"; +import type { AddSecretStorageKeyOpts } from "matrix-js-sdk/src/secret-storage"; +import type { Credentials, HomeserverInstance } from "../plugins/homeserver"; + +export interface CreateBotOpts { + /** + * A prefix to use for the userid. If unspecified, "bot_" will be used. + */ + userIdPrefix?: string; + /** + * Whether the bot should automatically accept all invites. + */ + autoAcceptInvites?: boolean; + /** + * The display name to give to that bot user + */ + displayName?: string; + /** + * Whether to start the syncing client. + */ + startClient?: boolean; + /** + * Whether to generate cross-signing keys + */ + bootstrapCrossSigning?: boolean; + /** + * Whether to use the rust crypto impl. Defaults to false (for now!) + */ + rustCrypto?: boolean; + /** + * Whether to bootstrap the secret storage + */ + bootstrapSecretStorage?: boolean; +} + +const defaultCreateBotOptions = { + userIdPrefix: "bot_", + autoAcceptInvites: true, + startClient: true, + bootstrapCrossSigning: true, +} satisfies CreateBotOpts; + +export class Bot { + private client: JSHandle; + public credentials?: Credentials; + + constructor(private page: Page, private homeserver: HomeserverInstance, private readonly opts: CreateBotOpts) { + this.opts = Object.assign({}, defaultCreateBotOptions, opts); + } + + public async start(): Promise { + this.credentials = await this.getCredentials(); + this.client = await this.setupBotClient(); + } + + private async getCredentials(): Promise { + const username = uniqueId(this.opts.userIdPrefix); + const password = uniqueId("password_"); + console.log(`getBot: Create bot user ${username} with opts ${JSON.stringify(this.opts)}`); + return await this.homeserver.registerUser(username, password, this.opts.displayName); + } + + private async setupBotClient(): Promise> { + return this.page.evaluateHandle( + async ({ homeserver, credentials, opts }) => { + const keys = {}; + + const getCrossSigningKey = (type: string) => { + return keys[type]; + }; + + const saveCrossSigningKeys = (k: Record) => { + Object.assign(keys, k); + }; + + // Store the cached secret storage key and return it when `getSecretStorageKey` is called + let cachedKey: { keyId: string; key: Uint8Array }; + const cacheSecretStorageKey = (keyId: string, keyInfo: AddSecretStorageKeyOpts, key: Uint8Array) => { + cachedKey = { + keyId, + key, + }; + }; + + const getSecretStorageKey = () => + Promise.resolve<[string, Uint8Array]>([cachedKey.keyId, cachedKey.key]); + + const cryptoCallbacks = { + getCrossSigningKey, + saveCrossSigningKeys, + cacheSecretStorageKey, + getSecretStorageKey, + }; + + const cli = new window.matrixcs.MatrixClient({ + baseUrl: homeserver.baseUrl, + userId: credentials.userId, + deviceId: credentials.deviceId, + accessToken: credentials.accessToken, + store: new window.matrixcs.MemoryStore(), + scheduler: new window.matrixcs.MatrixScheduler(), + cryptoStore: new window.matrixcs.MemoryCryptoStore(), + cryptoCallbacks, + }); + + if (opts.autoAcceptInvites) { + cli.on((window as any).matrixcs.RoomMemberEvent.Membership, (event, member) => { + if (member.membership === "invite" && member.userId === cli.getUserId()) { + cli.joinRoom(member.roomId); + } + }); + } + + if (!opts.startClient) { + return cli; + } + + if (opts.rustCrypto) { + await cli.initRustCrypto({ useIndexedDB: false }); + } else { + await cli.initCrypto(); + } + cli.setGlobalErrorOnUnknownDevices(false); + await cli.startClient(); + + if (opts.bootstrapCrossSigning) { + await cli.getCrypto()!.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: async (func) => { + await func({ + type: "m.login.password", + identifier: { + type: "m.id.user", + user: credentials.userId, + }, + password: credentials.password, + }); + }, + }); + } + + if (opts.bootstrapSecretStorage) { + const passphrase = "new passphrase"; + const recoveryKey = await cli.getCrypto().createRecoveryKeyFromPassphrase(passphrase); + Object.assign(cli, { __playwright_recovery_key: recoveryKey }); + + await cli.getCrypto()!.bootstrapSecretStorage({ + setupNewSecretStorage: true, + setupNewKeyBackup: true, + createSecretStorageKey: () => Promise.resolve(recoveryKey), + }); + } + + return cli; + }, + { + homeserver: this.homeserver.config, + credentials: this.credentials, + opts: this.opts, + }, + ); + } + + /** + * Make this bot join a room by name + * @param roomName Name of the room to join + */ + public async joinRoomByName(roomName: string): Promise { + await this.client.evaluate( + (client, { roomName }) => { + const room = client.getRooms().find((r) => r.getDefaultRoomName(client.getUserId()) === roomName); + if (room) { + return client.joinRoom(room.roomId); + } + throw new Error(`Bot room join failed. Cannot find room '${roomName}'`); + }, + { + roomName, + }, + ); + } + + /** + * Send a message as a bot into a room + * @param roomId ID of the room to join + * @param message the message body to send + */ + public async sendStringMessage(roomId: string, message: string): Promise { + return this.client.evaluate( + (client, { roomId, message }) => { + return client.sendMessage(roomId, { + msgtype: "m.text", + body: message, + }); + }, + { + roomId, + message, + }, + ); + } +} diff --git a/playwright/snapshots/user-view/user-view.spec.ts/UserView-should-render-the-user-view-as-expected-1-linux.png b/playwright/snapshots/user-view/user-view.spec.ts/UserView-should-render-the-user-view-as-expected-1-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..75b64546d63f0fee745a35e411b2e7fa4fe522de GIT binary patch literal 17497 zcmc({cUV(jw=Np0hyp57q*^EgTc`=}^^Gk?69g@GRuS3R|-pz-0>k1nDX+HD1X5}Isy?vG_7IOR1q(-!!5{yxzWrGQ%wwY$FxksHGv?9reBFOxZp?K9W$id(hjRGB0}8seXJQNjt+0eJfv(>3 zBKhm#f3sjy$`1m4pQ0n5-#?zGBe$jyq!84AzeW$gt{k0n`!nw*J@2O6oK;T|F#J}W`)A$F#g7)|H|jwvMg0HKU57(O^=caTdrU&%6huzARjD>O@d(We*O zYnknSaRa=~Ovj<~h2*UuNa0_L#+O^%S_TZS+xmd(<|`_I_VE(nfPJA}LJY>O?N_awGV>i~5!0uS9CCX@5u zbf>5q7~Q@WZ$4kb38ewS(qE9@qb+ar`!v8Jj^zv#!gnH@%EpI>Q+9upj5I#hSk`1E ztaJ*+AWA=ea{Ts>$uwGLAj!gvq!NU-;8SGta|?C@JIMR?`;JoST8F0rVor(W_Q-nW z$jg_nCs?CL<5SX1%8&YID0B$QH!$${6nJT;ki4Zw9W6SLdp18RU)1V z7@D4r_U^$Fr{}s4nd1kS<9inefj$YM+w}A2fmwpFr*<1Hh+Oh`?AB$AlyR)pvZG;z zbq+ZIJ?8J|^;9!R9JlZ=4KLMS7XxLbL~&iU^s=s~@YpcgFHa|;HH36pC~4fraY;qa zkd$hN$)ZKeWHw#XFCR|s5c&66Ksw!0tJZ2}K7%~c6_%XQwNq?TY}saVLmHa>C}WG9 zUv_pCciGCO5PwAv+`@sfhPEz|c61^!$D(Zax%slkrZu82X2MPKE{tfDIPQqliAtKH z(BW@wOWJX%pnisUaqB``+UHA0jF7;iw3TVK&O-Ig0q^D3^!*Ahbo0g+zc;H~1XLt>6H5uE@I4ki9^Jywn>Qb2f z?QaUN$(m2^K>F7p1C8g(zusJ-cqjRZ{e#G-J6^i(Ay3*DC(r3im1|T}t-)tOd4FZ^ ztcthKkDXf;s?qkVY~J-LcI``4>x0)f_Qg`YQs!)q`gn!yW>vzn5TXY-zSSJH{e3U= za~mu51ZHXKw#T3R4_{eCeh3Ujcxu zy|6Qt-J8`RP%XYk86e8$DF5cEy>(Xn#|Qe7M=i@#6z|gbhs2(Kx>oO=jJ|!d#@*fR zY3Tu(o>qXZsPe1&o19Dpmga{cXrXN{rC>9C;dcl@(kFa)_LWOo!Hxmvfq2yoJ- z_$2KqBZ(*cqBpzOzQE)1BxyTGVkX}~uZ`g=X7Wslj8%DQ^?mK=_`E&_7eoTp#E8%& z{fd!d_wntTefk!~9M-q}KdE$93NK>1>hyjawl`otvYj4%Ek2Y7eqK zs3`LrytA`y_#(|Ij5V&+qasIHVcgk!e4F4pe?7gsxtuVPbsJ+=6S&LH3QBJP z@~;>BVjZd~@Q#$GcD*n35g+(Cqx4fs5TldkEZql)^amkohE;;k6pxx1WwTkFSN%YS zxzJ&2c1Uwy)l4?j*thyEyRBgF+52DPc)Qr%2h;G>d@ zzk$#ivtS{kyk5_9NOocxzQW(zZM_n9t-ip-Xek4u=$;tY>*Yd5Wufn~bE_n3MVR@j zTpNwy$0@PqS(~UfXY94&SL9BHJC~luT9LruKh&tQ)vwhXMg~B!uXp#2ZiS#9 z^dqBEKAlZGP}($NIC9rz?VB{?rkW_eR<(pzS^Kg$Ee_XJ-82f71+H~|(^g|>W_6&D z$1v+0-#n--(!Y7VzW*0@GotJdIyY=8y1?(~hfWkl$XKg@I@N^jWyqNORaEAv#_U~c z;Bb15Y=wG{sD95YH<%uV(2(pmvLkmAeK$bjPIK&+f2}@YKD+mdyUy+33=Rp3n?^?R zGMp>-wRnt~o6T-bkgiXB!0_9K#*oCb0>@Pzx_IZS22{;vx)kz`hg9V6v#m(xpKco6 zJT+6KYI|CHy`EuuXUF7b)@OykY9?Indhv%id1c*?iFCQ+)ye-CTILKG{<>o8O zm)ZvIP))%5n*Ldw`IZanZ`J4fc-t$gr9nA(Dd&+sd9xW?WBUJ8oca17WJ-`$o1QW! z%Bq+u1TE<&L)Ndd54}#l(JT<9!4_}x<7b1SI0`8$K^ zCdVg6VI48a>K{}S@B8T}3}|A?9)|3{NHZZTrff61sOCYN1$*~4^m_fJD;2IF!w@U} zPg8QtlO2kdODjTSnx%tXpS6Ref0_{tZfc|;KkDtu&{Y6a*Pz%t~ z@Z0a@BswzAe-mpp3HoPSjS0d3>{dD%f`!fOLd+kjSHd58n*`TM%>4h}5v$1vDNheA zY;{5te}%&^RcLXW4IA(*A?djfYH!STn)3u2$53y}>hd}D7NlPnWCAznfY~4y!d6$= z4f(PyT$DS@4gf^@ZxwK z1yiWjD(BX=lv(9&NAh`J8(MRqCxKN1>M@(ckmAy6<+EShX83tArsmUx4PBI%sR3!5 z(N{s&L9G&ps45eDuFo)vxA^^mE;fdNRdl(=?17uJ()%Gz>`FwBx}&+}7GLD{YP){V z9u%T*EWO>zVW$+ z_D4JRm8&sDcMn8Awt#~kEUK&b9SToo&;24==>-f5Ifxx8Sk)T*xe44tH8Hcf_Evc| zM$moucs606D!AWeunsk7tix~!`5`+teU(kSo8eA2Ru?@gQSdi+pmckzH!*EP zi<>NvE6o$t+GTEiWJsRNq7FAadpw7z0Tdw9@;^r$Qab|A-8ym;$< zZli<-t(fZ!1x|493(iI}-W4+V1PoE#( z5qjXREG4z7_a1A4&w(RrL!q4?y2Ih0Gv5 zDRy(w>m(s)*#n+S>S_{7%2X5ES#>1$p=`~pmx0r7#ra~MOe=n$NxRW-_4JEAuJ?$Izp$)sFLEctrhP8{z@VQopTU& z>FLs_;6-u9d`#%ZyS|Y!DtN94+?JRVGs}#)7J?R;ndOopW71U{UX!)X389+k$U!o> zGaLs1mNOyYT5p4o?!M&eSW@+0E~Pa&40cA=L6)fR50sPm0Ou_48akL(jHhp z=bpaCPl&r-C3@<21u3xR9(D0QzK$0wJ}}@;)l|1`Q=v&nxj7Pk-5n6(D!sI!(jAxf z*KAqwwPyRLPi2*ybzJ+9X7d0ZI;f-(3+`5JDkmXF^6l>D@MHzQAaAyZcebO}5sf#N zZ#)j25)|xBGl=5$cI~nES()Z+JG&=1i16)xm z>aUhAp;((1A;Iz!BV6)AyM;F8W)C)7G2#&glE(Mn)$XE=ZhP6YrV6^X!h411Yx5N! z0#wRcay6UYc1*O$=#G~?f6`+t-4fsD?eR~32)nn(`w>eKuA!t!zOWaZAxz;ef0S}>3-I4;%pk?w>F8Xh zf`XYhUYRh{KH1E9Ek4?~84(wfI44MT`j92Zi}v`ew^Qp+&TDevyH@DXp1*j1X#@w& zZG}GlQ#_mWlOaHg`)7YbP{j56`wHPo>K-3BikYcCgykQexG(0Vb>2FPbPbcgl9fhf zVGz@Dhg*72A*AtP&=o&ap~jDwN#^A`eIn#nb^o&Xu=L<94G;GR%pJcof5BIlh|C(E zZ=)Y61$o=9S(6JF$8v6yHs1-`;h4H)%iU-zsi~;I>DHpSer!o!ax- z>H-Vxt5s!an5h&yoEGz3oUJ2Rr&H24w7@ixi{H&mgAimFioAlh=Rd($pQC8K9MJ67 zDn9VPikea(>Ttyek3???gI)mm3>0>~{{P=XM6r>B2)0-=j8PEc_q$#tiZOhV%>CAX zbS>&@g@6XKQQY*Nwmg(w#@!gr(I5ufl}eF2Cfx00@X+l#-Qpd0-=D-a*l@b9O_5#X# z6i#Ke_s4uQeRgG^hVQQWnNmL1Y$8kn6>H1*yTee3pkgUV_JdYZA9o5 zrsWF$@_{-0d7)J}jbO#e&&XK2`C{D1*jRjqTph6|e!t4sv$`>3Cwc{su!1nf=Ri9S7>6;a&-x(~ais)VqSnhHT@k2|?)+@cEM$Ceeud2N7*Yzqd6oRfV8F?;jsM~NTR}*@a)~ig*Tcgj(j7KX!Lhk& zp6OyoSD_xmU*9Qw<2v2x569IGzD$uODwUZvS%{}EBr>cyTDCaEdk!}BbYP70>)X?+=88+w`UyVRO= zX8fw*>|j>AVeja*X4>Y9FUz&HwbE<5kE5W4RTZd>>&oXf8&8Om^+=2xAwSiLy%4Z| z;DI9|tJ^q@M3HgkJ=}MF&-B3NR!FUj;rIFa^JX;`FYGnz<5^uRAeWoDet>~DI{ zAZ>ICU?_$-faqVeT^s3g2^2>_sV1ZsPWKhCg_-{4y>B$}rZySb-aCQ)apwV$rt_^r zHjnUvz?0yB^HaKp!y%bzcUg|u#`Blu0VgkG7W_^_8;^XnzI?7(oH&keu!#*wKF@7D z@k@Num6mU8;SYsl>+sV@+fm8t&!1yE<~!YR3C1_ggpp`Pi3zvVUkYAL11wApnni>2 zdf~KdQBY)61^4pVahrX23hHQqAYA1&-AheLirFb|(tu|iZdAf>75NLNCk+?I$ajBX zsd{unHt%X|Tkd4wIn>hsuqaU=1DRL`Sy}fzzpl%^w>n%>f^4veRS?$=JUc7VVv&04 z)U@MR3rz9T?h%wx=Ma0Tpcy(0{m6C5X2_ZMQA!tW`ug1~Y@8V9wdaqMCV)2mF zWIL$K@MLKZlulT$2dG^(o)4Tp>2&)1BI+IH-_ZB;54Xw0vLR zXt=JTf;Ktx8Ygn0I|U4X({*AHm*H0u)kuVJX*bjVO^= z$A%RG>;ya|Tj8(P}czL-y z*F5Yyhi1&0wdD?LMDPOm6vIGRcd-JIXq?IS=#j3y(P>dISxh|MEDNv@p1*yp6Vg9_ z&eF}*8f@5~Kmy{tWjQXenApo7xzyyOE|RO$aS^QIZ?P?O3Xt{Qs#56_lwWPkEx$DJ zw}0-3x7-2tMdyB6n*vydHv~;v1k4A!bQOMIp(}ywbg_tc7aMq1V&NZSuDc&k_~9T=?G!e>ir+;h^)eR)VD2^x@~8pEc$j!7^K7fP2QjJfs# zcF(3{aoS(Ze#pjWK|xz}t@26t&lVxVS3GZ15#AzDX|S*XiNzYs&I3vYNFjV)?oP=k z96L3MSa9BE1We_x3W0H-Yl{cxgNGn}>ImQ{if_Oq^1(nM(`di|HRyj$~OLAclv+5@4 zzp0SZ@!V%GRH;C2|0IgRf(apb;bOaP5va4)eWqKJaCOGQf|lR)f`csEI6X@NvebNp5)}i0gLh449-Od{TGos%1KUE5+P3)W>sQ13|x_#g;5;YjU z9O^?vwPTKooA|i7RVMw8(^oacKW{My>#3^xmO)ebz>w5h zEx?B9y;+b3Uwm1=c8dU7r~**St?-uYy|ocAJSD%7UqRtKs5VhPMI5S`DyoZigm#+S z;EHi9l2gAoUC1ceiVRDPG~g@}jsoaW!PvHvt}b`!y+7F<=4cBkDJd6es5%%NnI*5d zd#xTJ;!&p=(4=r8eU0k(n@aX?AIkm^x!NlP+*iM5Gw>V3dNUfXm+7bMt&Q3NE@9iJ z0fw(G^H>`x!`Tl#xR>jnkEQ_<5TYIoHu}qSTW)F`5JM~(i18il!s6{&(`7vMM#amB^Q%a8sN?S`t2v?%afINs zL~M8JSlhGRe54k>h=$?x05Wy~SXrT`W`u~j3etisK-9Ge7*=g}R{qBbXGFK8(;CUQS z8M{CIY8}&5^T!8mnSvhUtVoOr%}Sv+QwbPy4=T05HvG1l+V3N1B?LjSQDtgrcUb(KMyfb7B~J`{6m2Hz zIdS*UPd-{U;GI`(V)vk!m(Ju}LID@A`tj5K)^>eEovFcovKS!Qgjgl*+R%8kZnHL) zP^QbvZd@+SkjQYA66H9rN~ZBb0mC4+Dk`4@YFT`P2h1H#$zQ*)2y0+Ot5!XMmoZVW#wj$ND!$xjP+%Z zJbu?x9?eu@0>-^ZXVgd#+oavAlz0X5gyFz%8V@9tXxSiG_>GbJalfK}WScE@>4)26 z!ENDn0(T8fj2<#$ht_0J?#Dgvl2om0%(bFr&zKvI`wNfO3qv8bZ_;nj)4QE~ev&XD z9yB5hYxdn+=7sg_?^LZK6OPvQ=K-I%GQy|CoBj~UHQJhV`qp;M>g zAUUk|*oHvZ2uSRHqrbDuBOUNt@j~pV=W7O2p+|R~I6%bc&PDA^j<$zk} z5))g-8HohL#$!^&@#`o?r`y;1=yn+Sn6DOFN4YtSSL+kR-x{HNZmA@8c6Z`G;*wPg zQ1;_`&dx$Kau^_J0x3Zk^z`YH9U)$x`atR&a~XwC^&5OWE@zss_92tr1n%Tc9KK7W z-IxGdI0Y~CmkAs`H1D5=;qMg zZnT4?oL;U>JTHhp@M0g$)~H1%&wU)Miu$Dv7xCP{nl+u3mhH>tt46l#mdSvWQ)d@O z*S)fy!1NLr^@Qbh70N8j*pO;!XgLAd(+Ob(EI6GO-?x`OzsIoj>z30peBW{?FyKT_ zX2(UW%HKw6)uXIqq+CmTBHzz4Shb1xiG_!!iUkc27UZaUaa5~i=)EU$8$qTEJR%f=g&;IB;+X8>pSW?}3$|lE* z!|FYs@ezVM;bN}Kdkc+f1d>{^U48cOf3zp`0Nq*{s#3W*c^PDowsqxx`I3Z*Mzv$# zy#FPp;B{$R=8^XAQ;rer_}&)(Nf1A>b&=pr!BsNEBrx z_!vY2$`;MZQQ|&(mj9-RXa!{leSW^bl~UE2Uf9-jY{2U-oz_R8Q-esUX857 zRzFFRuh~2@F}?))h`<49sygh>o+AHyAt!-1&8cyVhFTdCMy+IuUCedHN>73 zTNcJ4P1fj&eGE64W`(<0b0-Won3D~WaJrvVxvF*yQq+92YC)43a2#$_KB~JS5{|OZ zi3@Wp*~Wkr1^V0m5#cw9rk-d`#oP7m(8q88CHB2s-J}tzXj6aqytITQi?ZlZ^YKQl za7poQ|i6SN95$Sis&p#rjaKa$#tHIhZ!I& z$FC4{+sp2Gw?y-_K|5o7%rbY6f>3VB4XCqqV%%CgUTWj{fJUyc;hqAIog4bz?smw= zj~%D~e1#Sf5>VY_P>EBTHDEo@rfb96|7@jT+=4R>?L595onoxtUp!&;*r}74Mj6sx zA;Wy(m{iA?`N~AzPf2!(Z~Botv8;c<;LFyR>`l`=>e$5V#9wc~g_----m9&A|FZZD zE*p?JaYE#klYo8Fo28FWjL*5G&0NN(v5=+cz&h_Ix9zX5sl?yt$L{015~2vrFpgW!)tEvwHm07f_Eg?IV0(RYJ5d0z zP5I@7WdrN^Kww4CYYdM{m6&J~T?+hmI1*LR*MWKs{ z&zy=hjycM5Exb%Dnk?l8%vQQxkym>IJ+Q49>xG-Bz!TcT_arp-lPbN@EV+zCmiQVA z(^1(z*W6!AVara%^SS8zJ=0jj)9~ej+%Q((b5OB@)jkXa`hfj<qLtAN-=9_>h~MXQk~h3Rh@j>~4#Pz~kX*d0MyDxWG^uYulY6_M^2} zhUqE6G~=be=5uu$xlXqU8?ec06hJg|R`-nlgVIdHQpr!-AC&D zMm8M;L7j7XIb4%=tU8V!1h5zHLr0tjSpK!oN~4|k;reD(7K1@>FE4sDm%B+A4{$te zwgW+FsH}K9U8}H1-v2{LbC&$U+!c!V%}PTu$Gh}6w6Uk4CCUgG!2Cc>9~I7?13kxB zv>q@w2zN0qUhW6H5*weFLkHn;H(Cb&FLHu$?|dU|t=xWE)-PlUgf?J^IBn*fScOxF zYrxmQ-#*~T%hJ|Ab<$=1BB~uHixd)(L$Nzd{Jz>TLQR`pKu(n6H~^p5;`_C(B%PE4 z)U3#QXLobAwdwR$h9yF?WI@_-Q*n4r{DS!gG#KR0Gp*oqqlU^S^8l8^F0zrETxqBv zrqV`p3$d`Qi3RwtxZ^~zpqZxurW~|V4ba`b+++LJmFEpoM0$4rx$7_;1w(th6Lx0u z%;16}Xuaj;;ksz3{{d!fo^vUFS_z0biCeGr2#LsDqgnz~?B-QUT!jjz+HthVomITV zZ1EFfh*fc%9yO&2K>h@)p8m9rMZ2?{$+28^mv^WB zfh#_dZxolVRgC-AW~53K{yDTNXv~YhRQ^bp0TdyXD>wmG2z38l1(-u0r)APO94P4P z$C2p1PWY0W9TK1GgcrI1(Jh0PXY(>dhhZ&^>QXvwR{+uzO8G8ow92Go@_PGO0)N=n zveAMEJ~bsStqV`!k5&Z$YnqSmX?z-zENvtzYp5?#~uyPKZMB9sQjoyOiz142)@wS0mu=p2MoIe8R*0E=l5!==DeW$bXHIrI98I+&}M(B zkcr7J_fm|465vYJ@pQ6{zeQxWUFP&|h|RGeqX=n!mmK^;hn-c7CTaMQpxoado5lis zF~-u?+xpTaz6ye))!@awRU~n^?#B)*Z~py>X>6EGrVh;S>Gx6Qq3ozI_ocf4?*r7S zY^OlB&{{(CzVk%A{dxA43I8XS76HCBf!@nQ(J5z(ufTe_d9O|3)pM)5Q9d+WVAlP3 z+@|h37 zkpC43R0t$U3%{V4|2F!1&FrD;zSj8Gu%+G*rEl?{%~D~;HItbx`o`{cxeQI z95ny3zLg(rbtygT%B1fT{8h>7KZm_B7!!0?TFzeV-!P8dP4w9rj`-JBZqFR+Iem;G?^Lcjsjst9@%XfsiC`5 z?$&jeAIB;{;k$EmmbNblwBO4agCG%%8}fw8O1INp2_kgC1w7!)j#(MjWh(K8y*DwY zQ500Vh~bZ=U;P(fN_@;%ANY2%^g8lPgUC^@70vqzXUN%5P)~~FvNIg=$d`Q=T~y4L zQ?@!yRSVcD0D?M<)S2Pgyi3zO!dbX^2#2~> zyMf^skrURaK!fA9Ha=prz5B`eS&>+s$A`rWYJ9%GzN4P$6$j_6QEjn2OgbC8?xVg5 zm^_&5%TkU5Sjq*3u=si&J=RDHJ>GhqugP4(7(0zxHg*t@6y9a_&AH4+&NXw}k3!vhibd|QWwB$+$F2QXxMW%h zUow18g3vh3$!Bt4o=cswg}*`cJK`)>IFn{S{h?geHNu!-JYr)oWJ}AXqgmg}PrY2c&VD+*DRy`- zT2?n}IpT(o>)4wkY|~j>?wy?P(GMBSXWy4ED~V+*NPSaZ=1}XKAhqr+*syh{d$LBA zzNyTQATk<cfwg{{E9KI z;Mc09lSP`vO6lS?1C{E=y+^zj;~*j)^#wxEESrE7Xb~o3r-!RS^hnS8+zPJhDf3iV z_&`XuskC=#s`sX%8#7@x-M>}x!eKCJ`-A^R@dCuf#@hJ7;$V8UwEXxQa_-t0x1IhL z)I0q*US|L;!{zd3+)I7i(a~$TgFQO3I$qfH11P*qSeiiL_?_6vLW#gr@CEF~9+2mA z=PxzWtJ-(Ky>-2XF9gojc;y4+W^C$vG1+!WG~H16IJe?2ZFeY)!1eeH{nSBQMmrtc54(Z0nre54CJ+T4j_hMb3{M#wzRn%$7+Ca>t=SX*=KR#S&d7l`5Bw`FGQ^C~d(A9+()KsVQ~9BprpUPQsb0|RBO zVyC^WtwNKMd@5VCh2mp}9{@6%bD-71xcCk>tVw@iV%*N+7sUv0v$=0cr{OAba|Xi> z9%Y?9dUgKObt3kukRkrFekDz~{yd<;-cHpJwr^b>>7!VI{D1@EH(IDDKmq~Yqb7VI%wFJ*V5I!eBjM** zA&5oRH|i-Sl!(chzd5gUyk;YTy^h7^lo55crE{kN-hi2;mI>0roUB5&J z;8yl}4xZPY?Z2@dA0Mu0Z7b@m;KXgt>^$w9l+Zm(H? zx&6~?6G)$Nc@vQRW_BY{P72)fMiX~1!cS`|bh>W#T3a0soQp;_JYDDR{6W}AKTA%D zZBpCI8?G@_B`sdHE5w0gF^P>b)EVP{LH6@^@#{fOX{F?l0j z`3$>*bTY+=FX2#*-%a*OrHYEqYolR>><8!Y*HN)af_DPW2|vjxPD(YngYT- zww;CnNOZ#f8yferTxBE z%k64Y>})gqNzP9{_m`R>Iz(PB4jhRb@j1T-BrZ`@c-Xbg_9B`OZ4ztz8V0onhK_k2 ze2pu4p;cae<{wfmccOfAk^!VF#KVh+$R2(R$sjFT5;G}L%P6C?$+RdZrYal-t+uyA zCTtK!G%}h+ymZDRlfU9CPEO(DcB||WkOyFf|n}`r8DcIJ}4<-f_pifayV=@p{cdUfuU&LYmj(?K)cLjm| zUyyS?sl|Q;_}>oD)opY}Yk3IO*GQ%pJQhL8ULR literal 0 HcmV?d00001 diff --git a/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png b/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..f1360f87036859d13a6f9639d1ae9e1d90bfaeb8 GIT binary patch literal 12530 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 eVU>)