diff --git a/tests/govtool-frontend/playwright/.env.example b/tests/govtool-frontend/playwright/.env.example index c5f2ff3d1..7dbc8561a 100644 --- a/tests/govtool-frontend/playwright/.env.example +++ b/tests/govtool-frontend/playwright/.env.example @@ -1,13 +1,13 @@ -FRONTEND_URL=http://localhost:8080 -API_URL=http://localhost:8080/api +FRONTEND_URL=http://localhost:3000 +API_URL=http://localhost:3000/api DOCS_URL=https://docs.sanchogov.tools -# 1 for testnet, 0 for mainnet -NETWORK_ID=1, +# 0 for testnet, 1 for mainnet +NETWORK_ID=0 # Create mock wallets if true -ONE_TIME_WALLET_SETUP=false, +ONE_TIME_WALLET_SETUP=false # Faucet FAUCET_API_URL=https://faucet.sanchonet.world.dev.cardano.org @@ -15,4 +15,10 @@ FAUCET_API_KEY= # Kuber KUBER_API_URL=https://sanchonet.kuber.cardanoapi.io -KUBER_API_KEY= \ No newline at end of file +KUBER_API_KEY= + +# Transaction timeout +TX_TIMEOUT=120000 # milliseconds + +# Metadata Bucket +METADATA_BUCKET_URL=https://metadata.cardanoapi.io/data \ No newline at end of file diff --git a/tests/govtool-frontend/playwright/.gitignore b/tests/govtool-frontend/playwright/.gitignore new file mode 100644 index 000000000..0a969ff71 --- /dev/null +++ b/tests/govtool-frontend/playwright/.gitignore @@ -0,0 +1,16 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +.env +tests-out/ +.auth/ +.download/ +lib/_mock/ +allure-results/ +allure-report/ +.secrets +.vars +.lock-pool/ +.logs/ diff --git a/tests/govtool-frontend/playwright/.prettierignore b/tests/govtool-frontend/playwright/.prettierignore new file mode 100644 index 000000000..944d08317 --- /dev/null +++ b/tests/govtool-frontend/playwright/.prettierignore @@ -0,0 +1,6 @@ +# Ignore artifacts: +.github +node_modules +playwright-report +test-results +playwright.config.ts diff --git a/tests/govtool-frontend/playwright/lib/constants/environments.ts b/tests/govtool-frontend/playwright/lib/constants/environments.ts new file mode 100644 index 000000000..ccdfd8c14 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/constants/environments.ts @@ -0,0 +1,27 @@ +const environments = { + frontendUrl: process.env.HOST_URL || "http://localhost:8080", + apiUrl: `${process.env.HOST_URL}/api` || "http://localhost:8080/api", + docsUrl: process.env.DOCS_URL || "https://docs.sanchogov.tools", + networkId: parseInt(process.env.NETWORK_ID) || 0, + oneTimeWalletSetup: process.env.ONE_TIME_WALLET_SETUP === "true" || false, + faucet: { + apiUrl: + process.env.FAUCET_API_URL || + "https://faucet.sanchonet.world.dev.cardano.org", + apiKey: process.env.FAUCET_API_KEY || "", + }, + kuber: { + apiUrl: + process.env.KUBER_API_URL || "https://sanchonet.kuber.cardanoapi.io", + apiKey: process.env.KUBER_API_KEY || "", + }, + txTimeOut: parseInt(process.env.TX_TIMEOUT) || 240000, + metadataBucketUrl: + `${process.env.CARDANOAPI_METADATA_URL}/data` || + "https://metadata.cardanoapi.io/data", + lockInterceptorUrl: + `${process.env.CARDANOAPI_METADATA_URL}/data` || + "https://metadata.cardanoapi.io/lock", +}; + +export default environments; diff --git a/tests/govtool-frontend/playwright/lib/constants/staticWallets.ts b/tests/govtool-frontend/playwright/lib/constants/staticWallets.ts new file mode 100644 index 000000000..21a0289e6 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/constants/staticWallets.ts @@ -0,0 +1,122 @@ +import { StaticWallet } from "@types"; + +export const faucetWallet: StaticWallet = { + payment: { + pkh: "b5187cdefbc5b49ddc17b423c079f0717721a03882a3b265bd4c12e0", + private: "11abec096ef0ea7edbeeee01a1a3f0e9f24a7225c2ee99687fb328146fe85ba6", + public: "b6a42d4ccc4d26adaec67e8578bf31f13b1b7e640527356248f2ec547f9de6e4", + }, + stake: { + pkh: "80f326af300273d19d5a541d45baa42ebc04265816735b026b5f34a4", + private: "283fd7625ef596f04f21b50ee14a9f4b49f8b1a6f17773cd2e1e69841a111bc1", + public: "86b08ee3d86cb72d026197a5a710e248d66f28fcff21b4467b75f876b4e6d050", + }, + dRepId: "drep1zg6zq3ku422ppvfm835rnvzf9ckxtzmy3ayjwylck6s4q9zr5ve", + address: + "addr_test1qz63slx7l0zmf8wuz76z8sre7pchwgdq8zp28vn9h4xp9cyq7vn27vqzw0ge6kj5r4zm4fpwhszzvkqkwddsy66lxjjqxnc9zk", +}; + +export const dRep01Wallet: StaticWallet = { + payment: { + public: "891ed5096ee248bc7f31a3094ea90f34485483eb1050c7ee368e64d04b90a009", + private: "2f1053f22707b9881ea6112024027a660bd5508e22081cf5e4e95cc663802dd9", + pkh: "5775ad2fb14ca1b45381a40e40f0c06081edaf2261e02bbcebcf8dc3", + }, + stake: { + private: "39db531b1ba6d659f0e09ed609e86a080ba2a5629dc5fad3b29890bdba64a014", + public: "45a35ffab6c467531ee528fbdbe1a629de806c7af19dcb5aacb70e4286fd6b9a", + pkh: "46a95c1337b27332131f3c1b9d8e7689edd2f593e7e69bf5dcf0c278", + }, + address: + "addr_test1qpthttf0k9x2rdznsxjqus8scpsgrmd0yfs7q2aua08cms6x49wpxdajwvepx8eurwwcua5fahf0tyl8u6dlth8scfuqk8r352", + dRepId: "drep1g654cyehkfenyycl8sdemrnk38ka9avnulnfhawu7rp8skl824l", +}; + +// export const dRep02Wallet: StaticWallet = { +// payment: { +// private: "71120ea01dc0c367da113a7ee7b3744a46f793edb4f30a06b46d800324b2c999", +// public: "66724455eaacb6dea6686ba09bc159d5deef3d82ebf9c6a60d61748b59e32627", +// pkh: "363547ffb44d337f8055515e75e8af516e557b3270bfa4d9198e7195", +// }, +// stake: { +// private: "4dfc89a9d680b237146dde69282c709e93ba91ac0b028e980bc40ec573c77f0f", +// public: "009c10056aff887d66135886d1fb9f046190bdf1d90a3f9cff954386f7cf37fb", +// pkh: "4d52d1d178157ab4c5ab6f8cb109ff91f750b367830463ef8344007e", +// }, +// address: +// "addr_test1qqmr23llk3xnxluq24g4ua0g4agku4tmxfctlfxerx88r92d2tgaz7q4026vt2m03jcsnlu37agtxeurq337lq6yqplqftpnqu", +// dRepId: "drep1f4fdr5tcz4atf3dtd7xtzz0lj8m4pvm8svzx8murgsq8u6dkmf4", +// }; + +export const adaHolder01Wallet: StaticWallet = { + payment: { + private: "63be29a8c8a73571ab410062f4555998c45a61f96a9bf1c5308c4f3eb7e4453f", + public: "dd1e7ca0deb26499a1336fbe2a5169ba3043a27763bb9e600625a95728be6167", + pkh: "daa1dd48181133eb21a376da773a1d31f72281008d790ecac885ff97", + }, + stake: { + private: "13e9e60b51768367c0d4c07a9f02b90d6511a9d7f7215b465fd87488171c687f", + public: "2819c4d6a988746ac7f5be3edc93c86d4cd0e3fae9c23a6ceeb23e6d0b207ad0", + pkh: "56ffa2a26e57c5b14c7c8d58455ebc24ce628f1c456be7e2e7448c8f", + }, + address: + "addr_test1qrd2rh2grqgn86ep5dmd5ae6r5clwg5pqzxhjrk2ezzll96kl732ymjhckc5clydtpz4a0pyee3g78z9d0n79e6y3j8smc7gzu", + dRepId: "drep12ml69gnw2lzmznru34vy2h4uyn8x9rcug4470ch8gjxg74htere", +}; + +export const adaHolder02Wallet: StaticWallet = { + payment: { + private: "6794cee96fd24aa68ae6e7df8548c15c6faf0373fc53c9714517b7c09b2ba6c0", + public: "58df2c02b5a2af09b51d5357f675b4f13ee019db57686adaab7536f6a3b8c29f", + pkh: "aec3d01a7fa061d027e945aedcfcb8e32eab0390063d19ef8ab89a88", + }, + stake: { + private: "cb4fa9d68add76c15b2b16b4bb7aeab043d95f42f4adec3d9d50396e6d1760e4", + public: "45dec9c5c130c23b1b9fedda680bdf1658e918087bd0f51c5548c471ca7d2991", + pkh: "49da6cb42b23f1c1f25f85e91dd325414b154f036bbf43a69dea27ee", + }, + address: + "addr_test1qzhv85q607sxr5p8a9z6ah8uhr3ja2crjqrr6x0032uf4zzfmfktg2er78qlyhu9aywaxf2pfv257qmthap6d802ylhqvz8qsf", + dRepId: "drep1f8dxedpty0curujlsh53m5e9g9932ncrdwl58f5aagn7u9psjya", +}; + +// export const soleVoterWallet: StaticWallet = { +// payment: { +// private: "98d35ef14dedc4520ed0153bc41e4db884deb0390f659ee1e28bb52da6045d4e", +// public: "5206735d1a1a1ac4ac625c973581ed97daec145d2e47a5c9bb14754527929f78", +// pkh: "0b5aa57cfd8010b00c649bf281520514de4efd952eba9c31eb7db187", +// }, +// stake: { +// private: "95ae1de1c2984c18207b8f57c450f1fbc54c2f0f1b878d3b24df11157277e1d1", +// public: "3bca5cb3599020808f69df269eb42b0e66ecf7455fb969e90c46ae0ac55e6572", +// pkh: "97265a1e13717c04a85e7d6dc156ba38340645b1e812a935823092f9", +// }, +// address: +// "addr_test1qq944ftulkqppvqvvjdl9q2jq52dunhaj5ht48p3ad7mrpuhyedpuym30sz2shnadhq4dw3cxsrytv0gz25ntq3sjtusesfzgd", +// dRepId: "drep1jun958snw97qf2z704kuz4468q6qv3d3aqf2jdvzxzf0jtlmwlf", +// }; + +// Does not takes part in transaction +export const user01Wallet: StaticWallet = { + payment: { + private: "a84d81412e41b55f9a484ce2cb5849660c7a8874df7ea11cb48120ec8efd2911", + public: "18cc2696ff588c19789f908df838fc58dd58986ebf6191b7b63d310c997f968b", + pkh: "56a6427fa7f8599d1e49271eb8123d0e02bc06bd44fc2d988e25455a", + }, + stake: { + private: "c3d9fde8d81c1533ab9a1e6fdbaddd792dfba8f58656c95e83f5e967087f4605", + public: "dd50a02daa77061ecd2d43d6fa1049e3db0192b94112ffc4f4e48975362987ff", + pkh: "cac600470e3b3027bf3ad3c363c35c690a54308bd001120194c1ba0b", + }, + address: + "addr_test1qpt2vsnl5lu9n8g7fyn3awqj858q90qxh4z0ctvc3cj52kk2ccqywr3mxqnm7wkncd3uxhrfpf2rpz7sqyfqr9xphg9s77zxlg", + dRepId: "drep1etrqq3cw8vcz00e660pk8s6udy99gvyt6qq3yqv5cxaqkuupyzg", +}; + +export const adaHolderWallets = [adaHolder01Wallet, adaHolder02Wallet]; + +export const userWallets = [user01Wallet]; + +export const dRepWallets = [dRep01Wallet]; + +// export const soleVoterWallets = [soleVoterWallet]; diff --git a/tests/govtool-frontend/playwright/lib/datafactory/createAuth.ts b/tests/govtool-frontend/playwright/lib/datafactory/createAuth.ts new file mode 100644 index 000000000..2525ed5dc --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/datafactory/createAuth.ts @@ -0,0 +1,44 @@ +// Saves storage state to a file in the .auth directory + +import { importWallet } from "@fixtures/importWallet"; +import { ShelleyWallet } from "@helpers/crypto"; +import LoginPage from "@pages/loginPage"; +import { Page } from "@playwright/test"; + +const tempDRepAuth = ".auth/tempDRepAuth.json"; +const tempUserAuth = ".auth/tempUserAuth.json"; +const tempAdaHolderAuth = ".auth/tempAdaHolderAuth.json"; + +export async function createTempDRepAuth(page: Page, wallet: ShelleyWallet) { + await importWallet(page, wallet.json()); + + const loginPage = new LoginPage(page); + await loginPage.login(); + await loginPage.isLoggedIn(); + + await page.context().storageState({ path: tempDRepAuth }); + return tempDRepAuth; +} + +export async function createTempAdaHolderAuth( + page: Page, + wallet: ShelleyWallet +) { + await importWallet(page, wallet.json()); + + const loginPage = new LoginPage(page); + await loginPage.login(); + await loginPage.isLoggedIn(); + + await page.context().storageState({ path: tempAdaHolderAuth }); + return tempAdaHolderAuth; +} + +export async function createTempUserAuth(page: Page) { + const loginPage = new LoginPage(page); + await loginPage.login(); + await loginPage.isLoggedIn(); + + await page.context().storageState({ path: tempUserAuth }); + return tempUserAuth; +} diff --git a/tests/govtool-frontend/playwright/lib/fixtures/createWallet.ts b/tests/govtool-frontend/playwright/lib/fixtures/createWallet.ts new file mode 100644 index 000000000..0ab7f1c04 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/fixtures/createWallet.ts @@ -0,0 +1,34 @@ +import { + CardanoTestWallet, + CardanoTestWalletConfig, +} from "@cardanoapi/cardano-test-wallet/types"; +import { ShelleyWallet } from "@helpers/crypto"; +import { Page } from "@playwright/test"; + +export default async function createWallet( + page: Page, + config?: CardanoTestWalletConfig +) { + const wallet = (await ShelleyWallet.generate()).json(); + + const initScriptArgs: { + wallet: CardanoTestWallet; + config: CardanoTestWalletConfig; + } = { + wallet, + config: config, + }; + + await page.addInitScript(({ wallet, config }) => { + window["cardanoTestWallet"] = { + ...window["cardanoTestWallet"], + wallet: wallet, + }; + if (config) { + window["cardanoTestWallet"]["config"] = { + ...window["cardanoTestWallet"]["config"], + ...config, + }; + } + }, initScriptArgs); +} diff --git a/tests/govtool-frontend/playwright/lib/fixtures/importWallet.ts b/tests/govtool-frontend/playwright/lib/fixtures/importWallet.ts new file mode 100644 index 000000000..d825f8cf9 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/fixtures/importWallet.ts @@ -0,0 +1,14 @@ +import { CardanoTestWallet } from "@cardanoapi/cardano-test-wallet/types"; +import { Page } from "@playwright/test"; +import { StaticWallet } from "@types"; + +export async function importWallet( + page: Page, + wallet: StaticWallet | CardanoTestWallet +) { + await page.addInitScript((wallet) => { + // @ts-ignore + window.cardanoTestWallet.wallet = wallet; + //@ts-ignore + }, wallet); +} diff --git a/tests/govtool-frontend/playwright/lib/fixtures/loadExtension.ts b/tests/govtool-frontend/playwright/lib/fixtures/loadExtension.ts new file mode 100644 index 000000000..44cea5367 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/fixtures/loadExtension.ts @@ -0,0 +1,28 @@ +import { CardanoTestWalletConfig } from "@cardanoapi/cardano-test-wallet/types"; +import environments from "@constants/environments"; +import { Page } from "@playwright/test"; + +import path = require("path"); + +export default async function loadDemosExtension( + page: Page, + enableStakeSigning = false +) { + const demosBundleScriptPath = path.resolve( + __dirname, + "../../node_modules/@cardanoapi/cardano-test-wallet/script.js" + ); + let walletConfig: CardanoTestWalletConfig = { + enableStakeSigning, + kuberApiUrl: environments.kuber.apiUrl, + kuberApiKey: environments.kuber.apiKey, + }; + await page.addInitScript((walletConfig) => { + window["cardanoTestWallet"] = { + walletName: "demos", + config: walletConfig, + }; + }, walletConfig); + + await page.addInitScript({ path: demosBundleScriptPath }); +} diff --git a/tests/govtool-frontend/playwright/lib/fixtures/walletExtension.ts b/tests/govtool-frontend/playwright/lib/fixtures/walletExtension.ts new file mode 100644 index 000000000..5f1fa6e7c --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/fixtures/walletExtension.ts @@ -0,0 +1,25 @@ +import { test as base } from "@playwright/test"; +import { StaticWallet } from "@types"; +import { importWallet } from "./importWallet"; +import loadDemosExtension from "./loadExtension"; + +type WalletExtensionTestOptions = { + wallet?: StaticWallet; + enableStakeSigning: boolean; +}; + +export const test = base.extend({ + wallet: [null, { option: true }], + + enableStakeSigning: [true, { option: true }], + + page: async ({ page, wallet, enableStakeSigning }, use) => { + await loadDemosExtension(page, enableStakeSigning); + + if (wallet) { + await importWallet(page, wallet); + } + + await use(page); + }, +}); diff --git a/tests/govtool-frontend/playwright/lib/helpers/cborEncodeDecode.ts b/tests/govtool-frontend/playwright/lib/helpers/cborEncodeDecode.ts new file mode 100644 index 000000000..e28c95183 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/cborEncodeDecode.ts @@ -0,0 +1,7 @@ +import { Decoder, Encoder } from "cbor-x"; + +export const cborxEncoder = new Encoder({ + mapsAsObjects: false, + useRecords: false, +}); +export const cborxDecoder = new Decoder({ mapsAsObjects: false }); diff --git a/tests/govtool-frontend/playwright/lib/helpers/computeTxHash.ts b/tests/govtool-frontend/playwright/lib/helpers/computeTxHash.ts new file mode 100644 index 000000000..4baf52026 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/computeTxHash.ts @@ -0,0 +1,8 @@ +import { blake2bHex } from "blakejs"; +import { cborxDecoder, cborxEncoder } from "./cborEncodeDecode"; + +export default function computeTxHash(tx: string) { + let decodedTx = cborxDecoder.decode(Buffer.from(tx, "hex")); + const txBody = Uint8Array.from(cborxEncoder.encode(decodedTx[0])); + return blake2bHex(txBody, undefined, 32); +} diff --git a/tests/govtool-frontend/playwright/lib/helpers/convertBufferToHex.ts b/tests/govtool-frontend/playwright/lib/helpers/convertBufferToHex.ts new file mode 100644 index 000000000..f7a9e0050 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/convertBufferToHex.ts @@ -0,0 +1,3 @@ +export default function convertBufferToHex(buffer: Uint8Array) { + return Buffer.from(buffer).toString("hex"); +} diff --git a/tests/govtool-frontend/playwright/lib/helpers/crypto.ts b/tests/govtool-frontend/playwright/lib/helpers/crypto.ts new file mode 100644 index 000000000..24fe8fd26 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/crypto.ts @@ -0,0 +1,251 @@ +import { ed25519 as ed } from "@noble/curves/ed25519"; +import { bech32 } from "bech32"; +import * as blake from "blakejs"; + +const KEY_HASH_LENGTH = 28; +const ADDR_LENGTH = KEY_HASH_LENGTH * 2 + 1; + +// Stores ed25519 KeyPair and hash of publicKey +export class Ed25519Key { + private: Uint8Array; + public: Uint8Array; + pkh: Uint8Array; + + private constructor(priv: Uint8Array, pub: Uint8Array, pkh: Uint8Array) { + this.private = priv; + this.public = pub; + this.pkh = pkh; + } + public static async generate() { + const privKey = ed.utils.randomPrivateKey(); // Secure random private key + return await Ed25519Key.fromPrivateKey(privKey); + } + + public static async fromPrivateKey(privKey: Uint8Array) { + const pubKey = ed.getPublicKey(privKey); + const pkh = blake.blake2b(pubKey, undefined, KEY_HASH_LENGTH); + const key = new Ed25519Key(privKey, pubKey, pkh); + return key; + } + public static async fromPrivateKeyHex(privKey) { + return await Ed25519Key.fromPrivateKey( + Uint8Array.from(Buffer.from(privKey, "hex")), + ); + } + + public bech32Pkh(prefix: string = "stake"): string { + return bech32.encode(prefix, bech32.toWords(this.pkh)); + } + public bech32PublicKey(prefix: string = "vk_"): string { + return bech32.encode(prefix, bech32.toWords(this.public)); + } + public bech32PrivateKey(prefix: string = "sk_"): string { + return bech32.encode(prefix, bech32.toWords(this.private)); + } + public async signRaw(message: Uint8Array) { + return ed.sign(message, this.private); + } + public async verify(message, signature) { + return ed.verify(signature, message, this.public); + } + + public json() { + return { + private: Buffer.from(this.private).toString("hex"), + public: Buffer.from(this.public).toString("hex"), + pkh: Buffer.from(this.pkh).toString("hex"), + }; + } + public static fromJson(json: any): Ed25519Key { + if (!json || typeof json !== "object") { + throw new Error( + "Invalid JSON format for Ed25519Key: Input must be a non-null object.", + ); + } + + if (!json.private || !json.public || !json.pkh) { + throw new Error( + "Invalid JSON format for Ed25519Key: Missing required fields (private, public, or pkh).", + ); + } + + return new Ed25519Key( + Uint8Array.from(Buffer.from(json.private, "hex")), + Uint8Array.from(Buffer.from(json.public, "hex")), + Uint8Array.from(Buffer.from(json.pkh, "hex")), + ); + } +} + +// Shelley Wallet has 2 ed25519 key pair +// - one for payment purpose +// - one for staking/governance purpose +export class ShelleyWallet { + paymentKey: Ed25519Key; + stakeKey: Ed25519Key; + + public constructor(payment, stake) { + this.paymentKey = payment; + this.stakeKey = stake; + } + + public static async generate() { + const wallet = new ShelleyWallet( + await Ed25519Key.generate(), + await Ed25519Key.generate(), + ); + return wallet; + } + + addressBech32(networkId: number): string { + const prefix = networkId == 0 ? "addr_test" : "addr"; + return bech32.encode( + prefix, + bech32.toWords(Buffer.from(this.addressRawBytes(networkId))), + 200, + ); + } + + addressRawBytes(networkId) { + const concatenatedArray1 = new Uint8Array(ADDR_LENGTH); + concatenatedArray1[0] = networkId; + concatenatedArray1.set(this.paymentKey.pkh, 1); + concatenatedArray1.set(this.stakeKey.pkh, KEY_HASH_LENGTH + 1); + return concatenatedArray1; + } + rewardAddressRawBytes(network: number) { + const rewardAccountPrefix = 0xe0; + const header = network | rewardAccountPrefix; + const result = new Uint8Array(KEY_HASH_LENGTH + 1); + result[0] = header; + result.set(this.stakeKey.pkh, 1); + return result; + } + + rewardAddressBech32(networkId: number): string { + const prefix = networkId == 0 ? "stake" : "stake_test"; + return bech32.encode( + prefix, + bech32.toWords(Buffer.from(this.rewardAddressRawBytes(networkId))), + 200, + ); + } + public json() { + return { + payment: this.paymentKey.json(), + stake: this.stakeKey.json(), + }; + } + + public static fromJson(obj: { + payment: object; + stake: object; + }): ShelleyWallet { + if (!obj || typeof obj !== "object") { + throw new Error("ShelleyWallet.fromJson: The input must be an object."); + } + + const paymentKey = obj.payment; + const stakeKey = obj.stake; + + if (!paymentKey || typeof paymentKey !== "object") { + throw new Error( + "ShelleyWallet.fromJson : Invalid payment key: It must be an object.", + ); + } + + if (!stakeKey || typeof stakeKey !== "object") { + throw new Error( + "ShelleyWallet.fromJson : Invalid stake key: It must be an object.", + ); + } + return new ShelleyWallet( + Ed25519Key.fromJson(paymentKey), + Ed25519Key.fromJson(stakeKey), + ); + } + + public static dummy(): ShelleyWallet { + return ShelleyWallet.fromJson({ + payment: { + pkh: "595ac9bbf256bae584f56a4b671baa4b14a18c8098b8e571834bc12c", + private: + "5a1380cd79ecaee48d66c14f7d92ddfc866490a3b59d44520e60f16309c8a17d", + public: + "8d2f4d49118eb1156048b66dd6372cdb1f82da0f8e208d9f8ea4b388c79c09ad", + }, + stake: { + pkh: "6706efab75778c2f08b9a5321ead8bfc982a5c08b51a0b2a713cac52", + private: + "24e8c012c7bef2f5823baef1c06dac253da860a43f0d1f43fc3c8349a4f719a1", + public: + "f7a1eaea2691ee80b6c0d6f27482145d7037055829b1b26224a5d8f0c2243f16", + }, + }); + } +} + +export interface Address { + toBech32(): string; + toRawBytes(): Uint8Array; + toRawBytesHex(): string; +} +export class ShelleyWalletAddress implements Address { + paymentKeyHash: Uint8Array; + stakeKeyHash: Uint8Array; + network: number; + + private constructor( + network: number | "mainnet" | "testnet", + pkh: Uint8Array, + skh: Uint8Array, + ) { + this.network = + network == "mainnet" ? 1 : network == "testnet" ? 0 : network; + this.paymentKeyHash = pkh; + this.stakeKeyHash = skh; + } + public static fromRawBytes(bytea: Uint8Array | string | Buffer) { + let bytebuffer: Buffer; + if (bytea.length == ADDR_LENGTH * 2 && typeof bytea == "string") { + bytebuffer = Buffer.from(bytea, "hex"); + } else { + if (bytea.length !== ADDR_LENGTH) { + throw Error( + "ShelleyAddress.fromRawBytes: Invalid byte array length. expected: " + + ADDR_LENGTH + + " got: " + + bytea.length, + ); + } + bytebuffer = Buffer.from(bytea); + } + + let paymentKeyHash = bytebuffer.subarray(1, 29); + let stakeKeyHash = bytebuffer.subarray(29, ADDR_LENGTH); + + return new ShelleyWalletAddress( + bytebuffer.at(0), + paymentKeyHash, + stakeKeyHash, + ); + } + toBech32(): string { + const prefix = this.network == 0 ? "addr_test" : "addr"; + return bech32.encode( + prefix, + bech32.toWords(Buffer.from(this.toRawBytes())), + 200, + ); + } + toRawBytes(): Uint8Array { + const rawBytes = new Uint8Array(ADDR_LENGTH); + rawBytes[0] = this.network; + rawBytes.set(this.paymentKeyHash, 1); + rawBytes.set(this.stakeKeyHash, KEY_HASH_LENGTH + 1); + return rawBytes; + } + toRawBytesHex(): string { + return Buffer.from(this.toRawBytes()).toString("hex"); + } +} diff --git a/tests/govtool-frontend/playwright/lib/helpers/extractDRepsFromStakePubkey.ts b/tests/govtool-frontend/playwright/lib/helpers/extractDRepsFromStakePubkey.ts new file mode 100644 index 000000000..f0983717e --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/extractDRepsFromStakePubkey.ts @@ -0,0 +1,10 @@ +import { bech32 } from "bech32"; +import { blake2bHex } from "blakejs"; + +export default function extractDRepsFromStakePubKey(stakePubKey: string) { + const dRepKeyBytes = Buffer.from(stakePubKey, "hex"); + const dRepId = blake2bHex(dRepKeyBytes, undefined, 28); + const words = bech32.toWords(Buffer.from(dRepId, "hex")); + const dRepIdBech32 = bech32.encode("drep", words); + return { dRepId, dRepIdBech32 }; +} diff --git a/tests/govtool-frontend/playwright/lib/helpers/generateShellyWallets.ts b/tests/govtool-frontend/playwright/lib/helpers/generateShellyWallets.ts new file mode 100644 index 000000000..127cb577f --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/generateShellyWallets.ts @@ -0,0 +1,15 @@ +import { ShelleyWallet } from "./crypto"; + +export default async function generateShellyWallets( + numWallets: number = 100, +): Promise { + const wallets: ShelleyWallet[] = []; + + for (let i = 0; i < numWallets; i++) { + const wallet = await ShelleyWallet.generate(); + + wallets.push(wallet); + } + + return wallets; +} diff --git a/tests/govtool-frontend/playwright/lib/helpers/mobile.ts b/tests/govtool-frontend/playwright/lib/helpers/mobile.ts new file mode 100644 index 000000000..b0c0329ca --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/mobile.ts @@ -0,0 +1,16 @@ +import { Page } from "@playwright/test"; + +export function isMobile(page: Page) { + const { width } = page.viewportSize(); + if (width <= 414) return true; + + return false; +} + +export async function openDrawer(page: Page) { + await page.getByRole("img", { name: "drawer-icon" }).click(); //BUG testId +} + +export async function openDrawerLoggedIn(page: Page) { + await page.getByTestId("open-drawer-button").click(); +} diff --git a/tests/govtool-frontend/playwright/lib/helpers/page.ts b/tests/govtool-frontend/playwright/lib/helpers/page.ts new file mode 100644 index 000000000..7a20d6d80 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/page.ts @@ -0,0 +1,25 @@ +import { importWallet } from "@fixtures/importWallet"; +import loadDemosExtension from "@fixtures/loadExtension"; +import { Browser, Page } from "@playwright/test"; +import { ShelleyWallet } from "./crypto"; + +interface BrowserConfig { + storageState: string; + wallet: ShelleyWallet; + enableStakeSigning?: boolean; +} + +export async function createNewPageWithWallet( + browser: Browser, + { storageState, wallet, enableStakeSigning }: BrowserConfig +): Promise { + const context = await browser.newContext({ + storageState: storageState, + }); + const newPage = await context.newPage(); + + await loadDemosExtension(newPage, enableStakeSigning); + await importWallet(newPage, wallet.json()); + + return newPage; +} diff --git a/tests/govtool-frontend/playwright/lib/helpers/removeAllSpaces.ts b/tests/govtool-frontend/playwright/lib/helpers/removeAllSpaces.ts new file mode 100644 index 000000000..e7c3f95eb --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/removeAllSpaces.ts @@ -0,0 +1,3 @@ +export default function removeAllSpaces(inputStr: string) { + return inputStr.replace(/\s/g, ""); +} diff --git a/tests/govtool-frontend/playwright/lib/helpers/setupWallets.ts b/tests/govtool-frontend/playwright/lib/helpers/setupWallets.ts new file mode 100644 index 000000000..b12684b3b --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/setupWallets.ts @@ -0,0 +1,23 @@ +import { faucetWallet } from "@constants/staticWallets"; +import { ShelleyWallet } from "@helpers/crypto"; +import kuberService from "@services/kuberService"; +import { pollTransaction } from "./transaction"; + +/* +Registers stake & fund wallets +*/ +export default async function setupWallets(wallets: ShelleyWallet[]) { + if (wallets.length === 0) { + throw new Error("No wallets to load balance"); + } + + const signingKey = faucetWallet.payment.private; + const { txId, address } = await kuberService.initializeWallets( + faucetWallet.address, + signingKey, + wallets + ); + await pollTransaction(txId, address); + + console.debug(`[Setup Wallet] Successfully setup ${wallets.length} wallets`); +} diff --git a/tests/govtool-frontend/playwright/lib/helpers/transaction.ts b/tests/govtool-frontend/playwright/lib/helpers/transaction.ts new file mode 100644 index 000000000..d85dc2efa --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/transaction.ts @@ -0,0 +1,71 @@ +import environments from "@constants/environments"; +import { Page, expect } from "@playwright/test"; +import kuberService from "@services/kuberService"; +import { LockInterceptor, LockInterceptorInfo } from "lib/lockInterceptor"; +import { Logger } from "../../../cypress/lib/logger/logger"; + +/** + * Polls the transaction status until it's resolved or times out. + * address is used to release lock of that address + */ +export async function pollTransaction( + txHash: string, + lockInfo?: LockInterceptorInfo +) { + try { + Logger.info(`Waiting for tx completion: ${txHash}`); + await expect + .poll( + async () => { + const response = await kuberService.getTransactionDetails(txHash); + const data = await response.json(); + return data.length; + }, + { + timeout: environments.txTimeOut, + } + ) + .toBeGreaterThan(0); + + Logger.success("Tx completed"); + + if (!lockInfo) return; + + await LockInterceptor.releaseLockForAddress( + lockInfo.address, + lockInfo.lockId, + `Task completed for:${lockInfo.lockId}` + ); + } catch (err) { + if (lockInfo) { + const errorMessage = { lockInfo, error: JSON.stringify(err) }; + + await LockInterceptor.releaseLockForAddress( + lockInfo.address, + lockInfo.lockId, + `Task failure: \n${JSON.stringify(errorMessage)}` + ); + } + + throw err; + } +} + +export async function waitForTxConfirmation(page: Page) { + let transactionHash: string | undefined; + const transactionStatusPromise = page.waitForRequest((request) => { + return request.url().includes("/transaction/status/"); + }); + + const url = (await transactionStatusPromise).url(); + const regex = /\/transaction\/status\/([^\/]+)$/; + const match = url.match(regex); + if (match) { + transactionHash = match[1]; + } + + if (transactionHash) { + await pollTransaction(transactionHash); + await page.reload(); + } +} diff --git a/tests/govtool-frontend/playwright/lib/lockInterceptor.ts b/tests/govtool-frontend/playwright/lib/lockInterceptor.ts new file mode 100644 index 000000000..a3b13cda7 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/lockInterceptor.ts @@ -0,0 +1,184 @@ +import { TxSubmitResponse } from "@services/kuberService"; +import * as fs from "fs"; +import * as lockfile from "lockfile"; +import { Logger } from "../../cypress/lib/logger/logger"; + +import path = require("path"); + +export interface LockInterceptorInfo { + lockId: string; + address: string; +} + +abstract class BaseLock { + abstract acquireLock(key: string, id?: string): Promise; + + abstract releaseLock(key: string, id?: string): Promise; + + abstract checkLock(key: string): Promise; +} + +export class LockInterceptor { + private static async acquireLock( + address: string, + lockId: string + ): Promise { + const lockFilePath = path.resolve(__dirname, `../.lock-pool/${address}`); + + try { + await log( + `Initiator: ${address} \n---------------------> acquiring lock for:${lockId}` + ); + await new Promise((resolve, reject) => { + lockfile.lock(lockFilePath, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + await log( + `Initiator: ${address} \n---------------------> acquired lock for:${lockId}` + ); + } catch (err) { + throw err; + } + } + + private static async releaseLock( + address: string, + lockId: string + ): Promise { + const lockFilePath = path.resolve(__dirname, `../.lock-pool/${address}`); + + try { + await log( + `Initiator: ${address} \n---------------------> releasing lock for:${lockId}` + ); + await new Promise((resolve, reject) => { + lockfile.unlock(lockFilePath, async (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + await log( + `Initiator: ${address} \n---------------------> released lock for:${lockId}\n` + ); + } catch (err) { + throw err; + } + } + + private static async waitForReleaseLock( + address: string, + lockId: string + ): Promise { + const pollInterval = 4000; // 4 secs + + try { + await log( + `Initiator: ${address} \n ---------------------> waiting lock for:${lockId}` + ); + return new Promise((resolve, reject) => { + const pollFn = () => { + try { + const isAddressLocked = checkAddressLock(address); + if (!isAddressLocked) { + resolve(); + } else { + setTimeout(pollFn, pollInterval); + } + } catch (err) { + reject(err); + } + }; + + pollFn(); + }); + } catch (err) { + throw err; + } + } + + static async intercept( + address: string, + callbackFn: () => Promise, + lockId: string, + provider: "local" | "server" = "local" + ): Promise { + while (true) { + const isAddressLocked = checkAddressLock(address); + if (isAddressLocked) { + await LockInterceptor.waitForReleaseLock(address, lockId); + } + + try { + await LockInterceptor.acquireLock(address, lockId); + break; + } catch (err) { + if (err.code === "EEXIST") { + await new Promise((resolve) => setTimeout(resolve, 1000)); // timeout for retry + continue; + } else { + throw err; + } + } + } + try { + const res = await callbackFn(); + return { ...res, lockInfo: { lockId, address } }; + } catch (err) { + const errorMessage = { lock_id: lockId, error: JSON.stringify(err) }; + await log(`Task failure: \n${JSON.stringify(errorMessage)}`); + await LockInterceptor.releaseLock(address, lockId); + throw err; + } + } + + static async releaseLockForAddress( + address: string, + lockId: string, + message?: string + ) { + try { + message && (await log(message)); + + await this.releaseLock(address, lockId); + } catch { + Logger.fail("Failed to write lock logs"); + } + } +} + +function checkAddressLock(address: string): boolean { + const lockFilePath = path.resolve(__dirname, `../.lock-pool/${address}`); + return lockfile.checkSync(lockFilePath); +} + +function log(message: string): Promise { + const options: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + timeZone: "Asia/Kathmandu", + }; + const logFilePath = path.resolve(__dirname, "../.logs/lock_logs.txt"); + const logMessage = `[${new Date().toLocaleString("en-US", options)}] ${message}\n`; + return new Promise((resolve, reject) => { + fs.appendFile(logFilePath, logMessage, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} diff --git a/tests/govtool-frontend/playwright/lib/pages/dRepRegistrationPage.ts b/tests/govtool-frontend/playwright/lib/pages/dRepRegistrationPage.ts new file mode 100644 index 000000000..4925124d7 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/pages/dRepRegistrationPage.ts @@ -0,0 +1,52 @@ +import { Page } from "@playwright/test"; +import { IDRepInfo } from "@types"; +import environments from "lib/constants/environments"; + +export default class DRepRegistrationPage { + readonly registerBtn = this.page.getByTestId("register-button"); + readonly skipBtn = this.page.getByTestId("skip-button"); + readonly confirmBtn = this.page.getByTestId("confirm-modal-button"); + readonly registrationSuccessModal = this.page.getByTestId( + "governance-action-submitted-modal" + ); + readonly continueBtn = this.page.getByTestId("retire-button"); // BUG testId -> continue-button + readonly addLinkBtn = this.page.getByRole("button", { name: "+ Add link" }); // BUG: testId -> add-link-button + + // input fields + readonly nameInput = this.page.getByPlaceholder("ex. JohnDRep"); // BUG testId + readonly emailInput = this.page.getByPlaceholder("john.smith@email.com"); // BUG testId + readonly bioInput = this.page.getByPlaceholder("Enter your Bio"); // BUG testId + readonly linkInput = this.page.getByPlaceholder("https://website.com/"); // BUG: testId + + constructor(private readonly page: Page) {} + + async goto() { + await this.page.goto(`${environments.frontendUrl}/register_drep`); + await this.continueBtn.click(); // BUG: testId -> continue-register-button + } + + async register(dRepInfo: IDRepInfo) { + await this.nameInput.fill(dRepInfo.name); + + if (dRepInfo.email != null) { + await this.emailInput.fill(dRepInfo.email); + } + if (dRepInfo.bio != null) { + await this.bioInput.fill(dRepInfo.bio); + } + if (dRepInfo.extraContentLinks != null) { + for (let i = 0; i < dRepInfo.extraContentLinks.length; i++) { + await this.linkInput.nth(i).fill(dRepInfo.extraContentLinks[i]); + } + } + + await this.continueBtn.click(); // BUG: testId -> submit-button + await this.page.getByRole("checkbox").click(); + await this.continueBtn.click(); // BUG: testId -> submit-button + + await this.page + .getByPlaceholder("URL") + .fill(`${environments.metadataBucketUrl}/Test_dRep`); + await this.continueBtn.click(); + } +} diff --git a/tests/govtool-frontend/playwright/lib/pages/delegationPage.ts b/tests/govtool-frontend/playwright/lib/pages/delegationPage.ts new file mode 100644 index 000000000..dc4d6b1ec --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/pages/delegationPage.ts @@ -0,0 +1,51 @@ +import { Page } from "@playwright/test"; +import environments from "lib/constants/environments"; + +export default class DelegationPage { + readonly otherOptionsBtn = this.page.getByText("Other options"); + readonly nextStepBtn = this.page.getByTestId("next-step-button"); + readonly dRepInput = this.page.getByRole("textbox"); + readonly searchInput = this.page.getByTestId("search-input"); + + readonly delegationOptionsDropdown = this.page.getByRole("button", { + name: "Automated Voting Options arrow", + }); // BUG: testId -> delegation-options-dropdown + + readonly delegateToDRepCard = this.page.getByTestId("delegate-to-drep-card"); + readonly signalNoConfidenceCard = this.page + .getByRole("region") + .locator("div") + .filter({ hasText: "Signal No Confidence on Every" }) + .nth(2); // BUG: testId -> signal-no-confidence-card + readonly abstainDelegationCard = this.page.getByText( + "Abstain from Every VoteSelect this to vote ABSTAIN to every vote.Voting Power₳" + );// BUG: testId -> abstain-delegation-card + + readonly delegationErrorModal = this.page.getByTestId( + "delegation-transaction-error-modal" + ); + + readonly delegateBtns = this.page.locator( + '[data-testid$="-delegate-button"]' + ); + + constructor(private readonly page: Page) {} + + async goto() { + await this.page.goto( + `${environments.frontendUrl}/connected/dRep_directory` + ); + } + + async delegateToDRep(dRepId: string) { + await this.searchInput.fill(dRepId); + await this.page.getByTestId(`${dRepId}-delegate-button`).click(); + } + + async resetDRepForm() { + if (await this.delegationErrorModal.isVisible()) { + await this.page.getByTestId("confirm-modal-button").click(); + } + await this.dRepInput.clear(); + } +} diff --git a/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts new file mode 100644 index 000000000..89877b9c4 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts @@ -0,0 +1,53 @@ +import environments from "@constants/environments"; +import { Page } from "@playwright/test"; + +export default class GovernanceActionDetailsPage { + readonly voteBtn = this.page.getByTestId("vote-button"); + readonly changeVoteBtn = this.page.getByTestId("change-vote"); + readonly yesVoteRadio = this.page.getByTestId("yes-radio"); + readonly noVoteRadio = this.page.getByTestId("no-radio"); + readonly abstainRadio = this.page.getByTestId("abstain-radio"); + readonly governanceActionType = this.page.getByText( + "Governance Action Type:" + ); + readonly submittedDate = this.page.getByTestId("submission-date"); + readonly expiryDate = this.page.getByTestId("expiry-date"); + readonly externalModalBtn = this.page.getByTestId("external-modal-button"); + readonly governanceActionId = this.page.getByText("Governance Action ID:"); + + readonly contextBtn = this.page.getByRole("button", { + name: "Provide context about your", + }); // BUG testId + readonly viewOtherDetailsLink = this.page.getByTestId( + "view-other-details-button" + ); + readonly continueModalBtn = this.page.getByTestId("continue-modal-button"); + + readonly voteSuccessModal = this.page.getByTestId("alert-success"); + readonly externalLinkModal = this.page.getByTestId("external-link-modal"); + + readonly contextInput = this.page.getByPlaceholder("Provide context"); // BUG testId + readonly cancelModalBtn = this.page.getByTestId("cancel-modal-button"); + + constructor(private readonly page: Page) {} + + get currentPage(): Page { + return this.page; + } + + async goto(proposalId: string) { + await this.page.goto( + `${environments.frontendUrl}/governance_actions/${proposalId}` + ); + } + + async vote() { + await this.yesVoteRadio.click(); + await this.voteBtn.click(); + } + + async reVote() { + await this.noVoteRadio.click(); + await this.changeVoteBtn.click(); + } +} diff --git a/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts b/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts new file mode 100644 index 000000000..a74a9c8d1 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts @@ -0,0 +1,159 @@ +import removeAllSpaces from "@helpers/removeAllSpaces"; +import { Locator, Page, expect } from "@playwright/test"; +import { IProposal } from "@types"; +import environments from "lib/constants/environments"; +import GovernanceActionDetailsPage from "./governanceActionDetailsPage"; + +enum FilterOption { + ProtocolParameterChange = "ParameterChange", + InfoAction = "InfoAction", + TreasuryWithdrawal = "TreasuryWithdrawals", + HardFork = "HardForkInitiation", + NoConfidence = "NoConfidence", + NewCommittee = "NewCommittee", + UpdatetotheConstitution = "NewConstitution", +} + +export default class GovernanceActionsPage { + readonly filterBtn = this.page.getByTestId("filters-button"); + readonly sortBtn = this.page.getByTestId("sort-button"); + readonly votedTab = this.page.getByTestId("voted-tab"); + + constructor(private readonly page: Page) {} + + async goto() { + await this.page.goto(`${environments.frontendUrl}/governance_actions`); + } + + async viewProposal( + proposal: IProposal + ): Promise { + const proposalId = `govaction-${proposal.txHash}#${proposal.index}-view-detail`; + await this.page.getByTestId(proposalId).click(); + + return new GovernanceActionDetailsPage(this.page); + } + + async viewFirstProposal(): Promise { + await this.page + .locator('[data-testid^="govaction-"][data-testid$="-view-detail"]') + .first() + .click(); + return new GovernanceActionDetailsPage(this.page); + } + + async viewFirstVotedProposal(): Promise { + await this.page + .locator('[data-testid^="govaction-"][data-testid$="-change-your-vote"]') + .first() + .click(); + return new GovernanceActionDetailsPage(this.page); + } + + async viewVotedProposal( + proposal: IProposal + ): Promise { + const proposalId = `govaction-${proposal.txHash}#${proposal.index}-change-your-vote`; + await this.page.getByTestId(proposalId).click(); + + return new GovernanceActionDetailsPage(this.page); + } + + async filterProposalByNames(names: string[]) { + for (const name of names) { + const sanitizedProposalName = removeAllSpaces(name); + await this.page.getByTestId(`${sanitizedProposalName}-checkbox`).click(); + } + } + + async unFilterProposalByNames(names: string[]) { + for (const name of names) { + const sanitizedProposalName = removeAllSpaces(name); + await this.page.getByTestId(`${sanitizedProposalName}-checkbox`).click(); + } + } + + async validateFilters(filters: string[]) { + const proposalCards = await this.page + .locator('[data-test-id$="-card"]') + .all(); + + for (const proposalCard of proposalCards) { + const hasFilter = await this._validateFiltersInProposalCard( + proposalCard, + filters + ); + expect( + hasFilter, + "A proposal card does not contain any of the filters" + ).toBe(true); + } + } + + async sortProposal(option: string) { + await this.page.getByTestId(`${option}-radio`).check(); + } + + async validateSort( + sortOption: string, + validationFn: (p1: IProposal, p2: IProposal) => boolean, + filterKeys = Object.keys(FilterOption) + ) { + const responses = await Promise.all( + filterKeys.map((filterKey) => + this.page.waitForResponse((response) => + response + .url() + .includes(`&type[]=${FilterOption[filterKey]}&sort=${sortOption}`) + ) + ) + ); + const proposalData = await Promise.all( + responses.map(async (response) => { + return await response.json(); + }) + ); + expect(proposalData.length, "No proposals to sort").toBeGreaterThan(0); + + // API validation + proposalData.forEach(async (proposal) => { + if (proposal.elements.length <= 1) return; + + const proposals = proposal.elements as IProposal[]; + for (let i = 0; i <= proposals.length - 2; i++) { + const isValid = validationFn(proposals[i], proposals[i + 1]); + expect(isValid, "API Sorting validation failed").toBe(true); + } + }); + + // Frontend validation + const proposalCards = await Promise.all( + filterKeys.map((key) => + this.page.getByTestId(`govaction-${key}-card`).allInnerTexts() + ) + ); + + for (let dIdx = 0; dIdx <= proposalData.length - 1; dIdx++) { + const proposals = proposalData[dIdx].elements as IProposal[]; + for (let i = 0; i <= proposals.length - 1; i++) { + expect( + proposalCards[dIdx][i].includes(proposals[i].txHash), + "Frontend validation failed" + ).toBe(true); + } + } + } + + async _validateFiltersInProposalCard( + proposalCard: Locator, + filters: string[] + ): Promise { + for (const filter of filters) { + try { + await expect(proposalCard.getByText(filter)).toBeVisible(); + return true; + } catch (e) {} + return false; + } + } +} diff --git a/tests/govtool-frontend/playwright/lib/pages/loginPage.ts b/tests/govtool-frontend/playwright/lib/pages/loginPage.ts new file mode 100644 index 000000000..964b14ff8 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/pages/loginPage.ts @@ -0,0 +1,76 @@ +import { + CIP30Instance, + Cip95Instance, +} from "@cardanoapi/cardano-test-wallet/types"; +import { isMobile, openDrawer, openDrawerLoggedIn } from "@helpers/mobile"; +import { Page, expect } from "@playwright/test"; + +export default class LoginPage { + readonly connectWalletBtn = this.page.getByTestId("connect-wallet-button"); + readonly demosWalletBtn = this.page.getByTestId("demos-wallet-button"); + readonly acceptSanchoNetInfoBtn = this.page + .getByTestId("confirm-modal-button") + .nth(0); + readonly disconnectWalletBtn = this.page.getByTestId("disconnect-button"); + readonly dRepIdDisplay = this.page.getByTestId("dRep-id-display"); + + constructor(private readonly page: Page) {} + + async goto() { + await this.page.goto("/"); + } + + async login() { + await this.goto(); + + if (isMobile(this.page)) { + await openDrawer(this.page); + await this.page + .getByRole("button", { name: "Connect your wallet" }) // BUG testId should be same as connect-wallet-button + .click(); + } else { + await this.connectWalletBtn.click(); + } + await this.demosWalletBtn.click({ force: true }); + await this.acceptSanchoNetInfoBtn.click({ force: true }); + + const { stakeKeys, rewardAddresses } = await this.page.evaluate( + async () => { + const walletInstance: CIP30Instance | Cip95Instance = + await window["cardano"]["demos"].enable(); + + let stakeKeys = []; + let rewardAddresses = []; + if ("cip95" in walletInstance) { + stakeKeys = await walletInstance.cip95.getRegisteredPubStakeKeys(); + rewardAddresses = await walletInstance.getRewardAddresses(); + } + + return { stakeKeys, rewardAddresses }; + } + ); + + // Handle multiple stake keys + if (stakeKeys.length > 1) { + await this.page + .getByTestId(`${rewardAddresses[0]}-radio`) + .getByText("Voting power:") + .click({ force: true }); + await this.page.getByTestId("select-button").click(); + } + } + + async logout() { + if (isMobile(this.page)) { + await openDrawerLoggedIn(this.page); + } + await this.disconnectWalletBtn.click(); + } + + async isLoggedIn() { + if (isMobile(this.page)) { + await openDrawerLoggedIn(this.page); + } + await expect(this.disconnectWalletBtn).toBeVisible(); + } +} diff --git a/tests/govtool-frontend/playwright/lib/services/faucetService.ts b/tests/govtool-frontend/playwright/lib/services/faucetService.ts new file mode 100644 index 000000000..a2f9971e3 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/services/faucetService.ts @@ -0,0 +1,34 @@ +import environments from "lib/constants/environments"; +import fetch = require("node-fetch"); + +interface IFaucetResponse { + amount: { + lovelace: number; + }; + txid: string; + txin: string; +} + +export const loadAmountFromFaucet = async ( + walletAddress: string +): Promise => { + try { + const res = await fetchClient( + `/send-money?type=default&action=funds&address=${walletAddress}&poolid=undefined&api_key=${environments.faucet.apiKey}` + ); + const responseBody = await res.json(); + // console.debug(`faucet response: ${JSON.stringify(responseBody)}`); + + if (responseBody.error) { + throw new Error("Error in loadAmountFaucet:" + responseBody.error.tag); + } + return responseBody; + } catch (error) { + // console.error("Error in loadAmountFromFaucet:", error); + throw error; + } +}; + +const fetchClient = (url: string) => { + return fetch(environments.faucet.apiUrl + url); +}; diff --git a/tests/govtool-frontend/playwright/lib/services/kuberService.ts b/tests/govtool-frontend/playwright/lib/services/kuberService.ts new file mode 100644 index 000000000..65190100e --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/services/kuberService.ts @@ -0,0 +1,423 @@ +import { faucetWallet } from "@constants/staticWallets"; +import { ShelleyWallet } from "@helpers/crypto"; +import { KuberValue } from "@types"; +import * as blake from "blakejs"; +import environments from "lib/constants/environments"; +import { LockInterceptor, LockInterceptorInfo } from "lib/lockInterceptor"; +import fetch, { BodyInit, RequestInit } from "node-fetch"; +import { cborxDecoder, cborxEncoder } from "../helpers/cborEncodeDecode"; +import convertBufferToHex from "../helpers/convertBufferToHex"; +import { Logger } from "./../../../cypress/lib/logger/logger"; + +type CertificateType = "registerstake" | "registerdrep" | "deregisterdrep"; + +export type TxSubmitResponse = { + cbor: string; + txId: string; + lockInfo?: LockInterceptorInfo; +}; + +type KuberBalanceResponse = { + txin: string; + value: KuberValue; + address?: string; +}; + +const config = { + apiUrl: environments.kuber.apiUrl, + apiKey: environments.kuber.apiKey, +}; + +class Kuber { + walletAddr: string; + signingKey: string; + version: string; + + constructor(walletAddr: string, signingKey: string, version = "v1") { + this.walletAddr = walletAddr; + this.signingKey = signingKey; + this.version = version; + } + + static generateCert(type: CertificateType, key: string) { + if (type === "registerstake" || type === "deregisterdrep") { + return { + type: type, + key: key, + }; + } else if (type === "registerdrep") { + return { + type: "registerdrep", + key: key, + anchor: { + url: "https://bit.ly/3zCH2HL", + dataHash: + "1111111111111111111111111111111111111111111111111111111111111111", + }, + }; + } + } + signTx(tx: any) { + return { + ...tx, + selections: [ + ...(tx.selections || []), + { + type: "PaymentSigningKeyShelley_ed25519", + description: "Payment Signing Key", + cborHex: "5820" + this.signingKey, + }, + this.walletAddr, + ], + changeAddress: this.walletAddr, + }; + } + + async signAndSubmitTx(tx: any) { + const signedTx = this.signTx(tx); + const signedTxBody = Uint8Array.from(cborxEncoder.encode(tx)); + const lockId = Buffer.from( + blake.blake2b(signedTxBody, undefined, 32) + ).toString("hex"); + const submitTxCallback = async () => { + return this.submitTx(signedTx, lockId); + }; + return LockInterceptor.intercept(this.walletAddr, submitTxCallback, lockId); + } + + async submitTx(signedTx: any, lockId?: string) { + Logger.info( + `Submitting tx: ${JSON.stringify({ lock_id: lockId, tx: signedTx })}` + ); + + const res = (await callKuber( + `/api/${this.version}/tx?submit=true`, + "POST", + JSON.stringify(signedTx) + )) as any; + let decodedTx = cborxDecoder.decode(Buffer.from(res.cborHex, "hex")); + const submittedTxBody = Uint8Array.from(cborxEncoder.encode(decodedTx[0])); + const submittedTxHash = Buffer.from( + blake.blake2b(submittedTxBody, undefined, 32) + ).toString("hex"); + + Logger.success(`Tx submitted: ${submittedTxHash}`); + return { + cbor: res.cborHex, + txId: submittedTxHash, + }; + } +} + +const kuberService = { + initializeWallets: ( + senderAddress: string, + signingKey: string, + wallets: ShelleyWallet[] + ) => { + const kuber = new Kuber(senderAddress, signingKey); + const outputs = []; + const stakes = []; + const certificates = []; + for (let i = 0; i < wallets.length; i++) { + const wallet = wallets[i]; + const address = wallet.addressBech32(environments.networkId); + outputs.push({ + address: address, + value: 0, + }); + stakes.push({ + type: "PaymentSigningKeyShelley_ed25519", + description: "Payment Signing Key", + cborHex: "5820" + convertBufferToHex(wallet.stakeKey.private), + }); + certificates.push( + Kuber.generateCert( + "registerstake", + convertBufferToHex(wallet.stakeKey.pkh) + ) + ); + } + return kuber.signAndSubmitTx({ + selections: [...stakes], + outputs, + certificates, + }); + }, + + submitTransaction(tx: any) { + return fetch(config.apiUrl + "/api/v1/tx/submit", { + method: "POST", + headers: { + "Content-Type": "application/json", + "api-key": config.apiKey, + }, + + body: JSON.stringify({ + tx: { + description: "", + type: "Tx ConwayEra", + cborHex: tx, + }, + }), + redirect: "follow", + }); + }, + transferADA: (receiverAddressList: string[], ADA = 20) => { + const kuber = new Kuber(faucetWallet.address, faucetWallet.payment.private); + const req = { + outputs: receiverAddressList.map((addr) => { + return { + address: addr, + value: `${ADA}A`, + }; + }), + }; + return kuber.signAndSubmitTx(req); + }, + + dRepRegistration: (stakeSigningKey: string, pkh: string) => { + const kuber = new Kuber(faucetWallet.address, faucetWallet.payment.private); + const req = { + certificates: [Kuber.generateCert("registerdrep", pkh)], + selections: [ + { + type: "PaymentSigningKeyShelley_ed25519", + description: "Stake Signing Key", + cborHex: `5820${stakeSigningKey}`, + }, + ], + }; + return kuber.signAndSubmitTx(req); + }, + + dRepDeRegistration: ( + addr: string, + signingKey: string, + stakePrivateKey: string, + pkh: string + ) => { + const kuber = new Kuber(addr, signingKey); + const selections = [ + { + type: "PaymentSigningKeyShelley_ed25519", + description: "Payment Signing Key", + cborHex: "5820" + stakePrivateKey, + }, + ]; + const req = { + selections, + inputs: addr, + certificates: [Kuber.generateCert("deregisterdrep", pkh)], + }; + return kuber.signAndSubmitTx(req); + }, + + stakeDelegation: ( + addr: string, + signingKey: string, + stakePrivateKey: string, + pkh: string, + dRep: string | "abstain" | "noconfidence" + ) => { + const kuber = new Kuber(addr, signingKey); + const selections = [ + { + type: "PaymentSigningKeyShelley_ed25519", + description: "Payment Signing Key", + cborHex: "5820" + stakePrivateKey, + }, + ]; + const req = { + selections, + certificates: [ + { + type: "delegate", + key: pkh, + drep: dRep, + }, + ], + }; + return kuber.signAndSubmitTx(req); + }, + + getBalance: async (addr: string) => { + const utxos: any[] = await callKuber(`/api/v3/utxo?address=${addr}`); + const balanceInLovelace = utxos.reduce( + (acc, utxo) => acc + utxo.value.lovelace, + 0 + ); + return balanceInLovelace / 1000000; + }, + + registerStake: ( + stakePrivateKey: string, + pkh: string, + signingKey: string, + addr: string + ) => { + const kuber = new Kuber(addr, signingKey); + const selections = [ + { + type: "PaymentSigningKeyShelley_ed25519", + description: "Payment Signing Key", + cborHex: "5820" + stakePrivateKey, + }, + ]; + const req = { + selections, + certificates: [Kuber.generateCert("registerstake", pkh)], + }; + return kuber.signAndSubmitTx(req); + }, + + createGovAction(proposalsCount = 2) { + const kuber = new Kuber(faucetWallet.address, faucetWallet.payment.private); + const infoProposal = { + deposit: 1000000000, + refundAccount: { + network: "Testnet", + credential: { + "key hash": + "db1bc3c3f99ce68977ceaf27ab4dd917123ef9e73f85c304236eab23", + }, + }, + anchor: { + url: "https://bit.ly/3zCH2HL", + dataHash: + "1111111111111111111111111111111111111111111111111111111111111111", + }, + }; + const req = kuber.signTx({ + proposals: Array.from({ length: proposalsCount }, (_, i) => infoProposal), + }); + return callKuber("/api/v1/tx?submit=true", "POST", JSON.stringify(req)); + }, + + getTransactionDetails(txHash: string) { + return fetch(config.apiUrl + "/api/v3/utxo?txin=" + txHash + "%230", { + method: "GET", + headers: { + "Content-Type": "application/json", + "api-key": config.apiKey, + }, + }); + }, + + queryUtxos(address: string): Promise<[KuberBalanceResponse]> { + return callKuber("/api/v3/utxo?address=" + address) as Promise< + [KuberBalanceResponse] + >; + }, + + voteOnProposal( + addr: string, + signingKey: string, + voter: string, // dRepHash + dRepStakePrivKey: string, + proposal: string + ) { + const kuber = new Kuber(addr, signingKey); + const req = { + selections: [ + { + type: "PaymentSigningKeyShelley_ed25519", + description: "Payment Signing Key", + cborHex: "5820" + dRepStakePrivKey, + }, + ], + vote: { + voter, + role: "drep", + proposal, + vote: true, + anchor: { + url: "https://bit.ly/3zCH2HL", + dataHash: + "1111111111111111111111111111111111111111111111111111111111111111", + }, + }, + }; + return kuber.signAndSubmitTx(req); + }, + + abstainDelegations( + stakePrivKeys: string[], + stakePkhs: string[] + ): Promise { + const kuber = new Kuber(faucetWallet.address, faucetWallet.payment.private); + const selections = stakePrivKeys.map((key) => { + return { + type: "PaymentSigningKeyShelley_ed25519", + description: "Payment Signing Key", + cborHex: "5820" + key, + }; + }); + + const certificates = stakePkhs.map((pkh) => { + return { + type: "delegate", + key: pkh, + drep: "abstain", + }; + }); + const req = { + selections, + certificates, + }; + return kuber.signAndSubmitTx(req); + }, +}; +async function callKuber( + path: any, + method: "GET" | "POST" = "GET", + body?: BodyInit, + contentType = "application/json" +) { + const url = config.apiUrl + path; + + const headers: Record = { + "api-key": config.apiKey, + }; + if (contentType) { + headers["content-type"] = contentType; + } + + const options: RequestInit = { + method, + headers, + }; + + if (method === "POST") { + if (body) options.body = body; + } + + return fetch(url, options).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return res.text().then((txt) => { + let err; + let json: any; + try { + json = JSON.parse(txt); + if (json) { + err = Error( + `KuberApi [Status ${res.status}] : ${ + json.message ? json.message : txt + }` + ); + } else { + err = Error(`KuberApi [Status ${res.status}] : ${txt}`); + } + } catch (e) { + err = Error(`KuberApi [Status ${res.status}] : ${txt}`); + } + err.status = res.status; + throw err; + }); + } + }); +} + +export default kuberService; diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts new file mode 100644 index 000000000..7cfb36fc8 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/types.ts @@ -0,0 +1,52 @@ +import { CardanoTestWallet } from "@cardanoapi/cardano-test-wallet/types"; + +export type StaticWallet = CardanoTestWallet & { + dRepId: string; + address: string; +}; + +export type KuberValue = { + [policyId: string]: Record | BigInt | number; +}; + +export interface IProposal { + id: string; + txHash: string; + index: number; + type: string; + details: any; + expiryDate: string; + expiryEpochNo: number; + createdDate: string; + createdEpochNo: number; + url: string; + metadataHash: string; + title: string | null; + about: string | null; + motivation: string | null; + rationale: string | null; + metadata: any; + references: any; + yesVotes: number; + noVotes: number; +} + +export type IVote = { + drepId: string; + metadataHash: string; + url: string; + proposalId: string; + vote: string; // You might want to consider using a more specific type, like 'VoteType' enum +}; + +export type IVotedProposal = { + proposal: IProposal; + vote: IVote; +}; + +export type IDRepInfo = { + name: string; + email?: string; + bio?: string; + extraContentLinks?: string[]; +}; diff --git a/tests/govtool-frontend/playwright/package-lock.json b/tests/govtool-frontend/playwright/package-lock.json new file mode 100644 index 000000000..f229e2e72 --- /dev/null +++ b/tests/govtool-frontend/playwright/package-lock.json @@ -0,0 +1,5158 @@ +{ + "name": "intersect-govtool", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "intersect-govtool", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@cardanoapi/cardano-test-wallet": "^1.0.2", + "@faker-js/faker": "^8.4.1", + "@noble/curves": "^1.3.0", + "@noble/ed25519": "^2.0.0", + "@types/seedrandom": "^3.0.8", + "bech32": "^2.0.0", + "blakejs": "^1.2.1", + "buffer": "^6.0.3", + "cbor-x": "^1.5.8", + "dotenv": "^16.4.4", + "fast-check": "^3.17.2", + "lockfile": "^1.0.4", + "node-fetch": "v2", + "path": "^0.12.7", + "seedrandom": "^3.0.5" + }, + "devDependencies": { + "@playwright/test": "^1.41.2", + "@types/node": "^20.11.17", + "@types/node-fetch": "^2.6.11", + "allure-commandline": "^2.27.0", + "allure-playwright": "^2.15.0", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.57.0", + "prettier": "3.2.5", + "ts-loader": "^9.5.1", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "typescript": "^5.4.5", + "webpack": "^5.90.3", + "webpack-cli": "^5.1.4" + } + }, + "../../../../cardano-test-wallet": { + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "^1.3.0", + "@noble/ed25519": "^2.0.0", + "@types/seedrandom": "^3.0.8", + "bech32": "^2.0.0", + "blakejs": "^1.2.1", + "buffer": "^6.0.3", + "cbor-x": "^1.5.8", + "dotenv": "^16.4.4", + "node-fetch": "v2", + "path": "^0.12.7", + "seedrandom": "^3.0.5", + "typescript": "^5.4.2" + }, + "devDependencies": { + "@types/node": "^20.11.17", + "@types/node-fetch": "^2.6.11", + "copy-webpack-plugin": "^12.0.2", + "prettier": "3.2.5", + "ts-loader": "^9.5.1", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "webpack": "^5.90.3", + "webpack-cli": "^5.1.4" + } + }, + "../../../../cardano-test-wallet/dist": { + "name": "@cardanoapi/cardano-test-wallet", + "version": "1.0.0", + "extraneous": true, + "license": "MIT" + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@cardanoapi/cardano-test-wallet": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@cardanoapi/cardano-test-wallet/-/cardano-test-wallet-1.0.2.tgz", + "integrity": "sha512-ABOOSoB0DUggcIwl6nj1v4/IhzFKPPV6krGLAv9RRmX5trkiOBLqGuSNrOlFU+7XELJzagDDwYS1ykhyuVD/HA==" + }, + "node_modules/@cbor-extract/cbor-extract-linux-x64": { + "version": "2.2.0", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@faker-js/faker": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", + "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.23", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@noble/curves": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/ed25519": { + "version": "2.0.0", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@noble/hashes": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@playwright/test": { + "version": "1.41.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.41.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.11.17", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/@types/node": { + "version": "20.11.19", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/seedrandom": { + "version": "3.0.8", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.11.3", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/allure-commandline": { + "version": "2.27.0", + "dev": true, + "license": "Apache-2.0", + "bin": { + "allure": "bin/allure" + } + }, + "node_modules/allure-js-commons": { + "version": "2.15.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "md5": "^2.3.0", + "properties": "^1.2.1", + "strip-ansi": "^5.2.0" + } + }, + "node_modules/allure-playwright": { + "version": "2.15.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "allure-js-commons": "2.15.0" + } + }, + "node_modules/ansi-regex": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bech32": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/blakejs": { + "version": "1.2.1", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001591", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/cbor-extract": { + "version": "2.2.0", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.1.1" + }, + "bin": { + "download-cbor-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", + "@cbor-extract/cbor-extract-linux-x64": "2.2.0", + "@cbor-extract/cbor-extract-win32-x64": "2.2.0" + } + }, + "node_modules/cbor-x": { + "version": "1.5.8", + "license": "MIT", + "optionalDependencies": { + "cbor-extract": "^2.2.0" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.4", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.685", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.15.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.11.1", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-check": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.17.2.tgz", + "integrity": "sha512-+3DPTxtxABLgmmVpYxrash3DHoq0cMa1jjLYNp3qqokKKhqVEaS4lbnaDKqWU5Dd6C2pEudPPBAEEQ9nUou9OQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/form-data": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "14.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/@types/node": { + "version": "20.11.19", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lockfile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", + "dependencies": { + "signal-exit": "^3.0.2" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path": { + "version": "0.12.7", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/playwright": { + "version": "1.41.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.41.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.41.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/properties": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.6.0", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/slash": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.28.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "dev": true, + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "license": "MIT" + }, + "node_modules/ts-loader": { + "version": "9.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/enhanced-resolve": { + "version": "5.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util": { + "version": "0.10.4", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "license": "BSD-2-Clause" + }, + "node_modules/webpack": { + "version": "5.90.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, + "@cardanoapi/cardano-test-wallet": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@cardanoapi/cardano-test-wallet/-/cardano-test-wallet-1.0.2.tgz", + "integrity": "sha512-ABOOSoB0DUggcIwl6nj1v4/IhzFKPPV6krGLAv9RRmX5trkiOBLqGuSNrOlFU+7XELJzagDDwYS1ykhyuVD/HA==" + }, + "@cbor-extract/cbor-extract-linux-x64": { + "version": "2.2.0", + "optional": true + }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "dev": true + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "@faker-js/faker": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", + "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==" + }, + "@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.4", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.5", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.23", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@noble/curves": { + "version": "1.3.0", + "requires": { + "@noble/hashes": "1.3.3" + } + }, + "@noble/ed25519": { + "version": "2.0.0" + }, + "@noble/hashes": { + "version": "1.3.3" + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@playwright/test": { + "version": "1.41.2", + "dev": true, + "requires": { + "playwright": "1.41.2" + } + }, + "@sindresorhus/merge-streams": { + "version": "2.3.0", + "dev": true + }, + "@types/eslint": { + "version": "8.56.4", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.7", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.5", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "dev": true + }, + "@types/node": { + "version": "20.11.17", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/node-fetch": { + "version": "2.6.11", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "20.11.19", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + } + } + }, + "@types/seedrandom": { + "version": "3.0.8" + }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.11.6", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.6", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.6", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.6", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.6", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/configtest": { + "version": "2.1.1", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "2.0.2", + "dev": true, + "requires": {} + }, + "@webpack-cli/serve": { + "version": "2.0.5", + "dev": true, + "requires": {} + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "dev": true + }, + "acorn": { + "version": "8.11.3", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.9.0", + "dev": true, + "requires": {} + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "8.12.0", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "dev": true, + "requires": { + "ajv": "^8.0.0" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "allure-commandline": { + "version": "2.27.0", + "dev": true + }, + "allure-js-commons": { + "version": "2.15.0", + "dev": true, + "requires": { + "md5": "^2.3.0", + "properties": "^1.2.1", + "strip-ansi": "^5.2.0" + } + }, + "allure-playwright": { + "version": "2.15.0", + "dev": true, + "requires": { + "allure-js-commons": "2.15.0" + } + }, + "ansi-regex": { + "version": "4.1.1", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1" + }, + "bech32": { + "version": "2.0.0" + }, + "blakejs": { + "version": "1.2.1" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.23.0", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + } + }, + "buffer": { + "version": "6.0.3", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-from": { + "version": "1.1.2", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001591", + "dev": true + }, + "cbor-extract": { + "version": "2.2.0", + "optional": true, + "requires": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", + "@cbor-extract/cbor-extract-linux-x64": "2.2.0", + "@cbor-extract/cbor-extract-win32-x64": "2.2.0", + "node-gyp-build-optional-packages": "5.1.1" + } + }, + "cbor-x": { + "version": "1.5.8", + "requires": { + "cbor-extract": "^2.2.0" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "charenc": { + "version": "0.0.2", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.3", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "colorette": { + "version": "2.0.20", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "10.0.1", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "copy-webpack-plugin": { + "version": "12.0.2", + "dev": true, + "requires": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "dependencies": { + "schema-utils": { + "version": "4.2.0", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + } + } + }, + "cross-spawn": { + "version": "7.0.3", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypt": { + "version": "0.0.2", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "dev": true + }, + "detect-libc": { + "version": "2.0.2", + "optional": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dotenv": { + "version": "16.4.4" + }, + "electron-to-chromium": { + "version": "1.4.685", + "dev": true + }, + "enhanced-resolve": { + "version": "5.15.1", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "envinfo": { + "version": "7.11.1", + "dev": true + }, + "es-module-lexer": { + "version": "1.4.1", + "dev": true + }, + "escalade": { + "version": "3.1.2", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "dependencies": { + "@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "events": { + "version": "3.3.0", + "dev": true + }, + "fast-check": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.17.2.tgz", + "integrity": "sha512-+3DPTxtxABLgmmVpYxrash3DHoq0cMa1jjLYNp3qqokKKhqVEaS4lbnaDKqWU5Dd6C2pEudPPBAEEQ9nUou9OQ==", + "requires": { + "pure-rand": "^6.1.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "dev": true + }, + "fast-glob": { + "version": "3.3.2", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "dev": true + }, + "fastq": { + "version": "1.17.1", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "dev": true + }, + "flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "form-data": { + "version": "4.0.0", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "function-bind": { + "version": "1.1.2", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "dev": true + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "14.0.1", + "dev": true, + "requires": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "dev": true + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "hasown": { + "version": "2.0.1", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "ieee754": { + "version": "1.2.1" + }, + "ignore": { + "version": "5.3.1", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-local": { + "version": "3.1.0", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3" + }, + "interpret": { + "version": "3.1.1", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "dev": true + }, + "is-core-module": { + "version": "2.13.1", + "dev": true, + "requires": { + "hasown": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "isexe": { + "version": "2.0.0", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "dev": true + }, + "jest-worker": { + "version": "27.5.1", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "@types/node": { + "version": "20.11.19", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "supports-color": { + "version": "8.1.1", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "dev": true + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "kind-of": { + "version": "6.0.3", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "loader-runner": { + "version": "4.3.0", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lockfile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "md5": { + "version": "2.3.0", + "dev": true, + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "merge-stream": { + "version": "2.0.0", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "dev": true + }, + "node-fetch": { + "version": "2.7.0", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-gyp-build-optional-packages": { + "version": "5.1.1", + "optional": true, + "requires": { + "detect-libc": "^2.0.1" + } + }, + "node-releases": { + "version": "2.0.14", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, + "p-limit": { + "version": "2.3.0", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path": { + "version": "0.12.7", + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "path-exists": { + "version": "4.0.0", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "dev": true + }, + "path-type": { + "version": "5.0.0", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "playwright": { + "version": "1.41.2", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.41.2" + } + }, + "playwright-core": { + "version": "1.41.2", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true + }, + "process": { + "version": "0.11.10" + }, + "properties": { + "version": "1.2.1", + "dev": true + }, + "punycode": { + "version": "2.3.1", + "dev": true + }, + "pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==" + }, + "queue-microtask": { + "version": "1.2.3", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "rechoir": { + "version": "0.8.0", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } + }, + "require-from-string": { + "version": "2.0.2", + "dev": true + }, + "resolve": { + "version": "1.22.8", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "dev": true + }, + "schema-utils": { + "version": "3.3.0", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "dev": true, + "requires": {} + }, + "json-schema-traverse": { + "version": "0.4.1", + "dev": true + } + } + }, + "seedrandom": { + "version": "3.0.5" + }, + "semver": { + "version": "7.6.0", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "serialize-javascript": { + "version": "6.0.2", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "shallow-clone": { + "version": "3.0.1", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "slash": { + "version": "5.1.0", + "dev": true + }, + "source-map": { + "version": "0.7.4", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "dev": true + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "dev": true + }, + "terser": { + "version": "5.28.1", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "5.3.10", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3" + }, + "ts-loader": { + "version": "9.5.1", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "dependencies": { + "enhanced-resolve": { + "version": "5.15.0", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + } + } + }, + "tsconfig-paths": { + "version": "4.2.0", + "dev": true, + "requires": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "tsconfig-paths-webpack-plugin": { + "version": "4.1.0", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^4.1.2" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "dev": true + }, + "unicorn-magic": { + "version": "0.1.0", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.13", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util": { + "version": "0.10.4", + "requires": { + "inherits": "2.0.3" + } + }, + "watchpack": { + "version": "2.4.0", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "webidl-conversions": { + "version": "3.0.1" + }, + "webpack": { + "version": "5.90.3", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + } + }, + "webpack-cli": { + "version": "5.1.4", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + } + }, + "webpack-merge": { + "version": "5.10.0", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wildcard": { + "version": "2.0.1", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/tests/govtool-frontend/playwright/package.json b/tests/govtool-frontend/playwright/package.json new file mode 100644 index 000000000..3cb14402e --- /dev/null +++ b/tests/govtool-frontend/playwright/package.json @@ -0,0 +1,45 @@ +{ + "name": "intersect-govtool", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@playwright/test": "^1.41.2", + "@types/node": "^20.11.17", + "@types/node-fetch": "^2.6.11", + "allure-commandline": "^2.27.0", + "allure-playwright": "^2.15.0", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.57.0", + "prettier": "3.2.5", + "ts-loader": "^9.5.1", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "typescript": "^5.4.5", + "webpack": "^5.90.3", + "webpack-cli": "^5.1.4" + }, + "scripts": { + "package-wallet": "tsc ./lib/_mock/cardano-test-wallet/types.ts --outDir dist/@types/cardano-test-wallet --declaration && npx webpack", + "allure:generate": "npx allure generate ./allure-results --clean", + "allure:open": "npx allure open ./allure-report", + "allure:serve": "npx allure serve", + "test": "npx playwright test", + "format": "prettier . --write" + }, + "dependencies": { + "@cardanoapi/cardano-test-wallet": "^1.0.2", + "@faker-js/faker": "^8.4.1", + "@noble/curves": "^1.3.0", + "@noble/ed25519": "^2.0.0", + "@types/seedrandom": "^3.0.8", + "bech32": "^2.0.0", + "blakejs": "^1.2.1", + "buffer": "^6.0.3", + "cbor-x": "^1.5.8", + "dotenv": "^16.4.4", + "fast-check": "^3.17.2", + "lockfile": "^1.0.4", + "node-fetch": "v2", + "path": "^0.12.7", + "seedrandom": "^3.0.5" + } +} diff --git a/tests/govtool-frontend/playwright/playwright.config.ts b/tests/govtool-frontend/playwright/playwright.config.ts index 3439dd15c..a2adcef75 100644 --- a/tests/govtool-frontend/playwright/playwright.config.ts +++ b/tests/govtool-frontend/playwright/playwright.config.ts @@ -1,6 +1,7 @@ import { defineConfig, devices } from "@playwright/test"; -import { config } from "dotenv"; import { testPlanFilter } from "allure-playwright/dist/testplan"; +import { config } from "dotenv"; +import environments from "lib/constants/environments"; config(); @@ -22,56 +23,96 @@ export default defineConfig({ /* Retry on CI only */ retries: 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? parseInt(process.env.WORKERS) : undefined, + workers: process.env.CI ? parseInt(process.env.TEST_WORKERS) : undefined, /*use Allure Playwright's testPlanFilter() to determine the grep parameter*/ grep: testPlanFilter(), /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [["line"], ["allure-playwright"]], + reporter: process.env.CI ? [["line"], ["allure-playwright"]] : [["line"]], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: "", + baseURL: environments.frontendUrl, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", screenshot: "only-on-failure", - // video:'retain-on-failure', + // video: "on", }, /* Configure projects for major browsers */ projects: [ { - name: "chrome", - use: { ...devices["Desktop Chrome"] }, + name: "faucet setup", + testMatch: "**/faucet.setup.ts", + }, + { + name: "auth setup", + testMatch: "**/auth.setup.ts", + }, + { + name: "dRep setup", + testMatch: "**/dRep.setup.ts", + dependencies: ["faucet setup"], + }, + { + name: "wallet bootstrap", + testMatch: "**/wallet.bootstrap.ts", + dependencies: ["faucet setup"], }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // name: "transaction", + // use: { ...devices["Desktop Chrome"] }, + // testMatch: "**/*.tx.spec.ts", + // dependencies: process.env.CI ? ["auth setup", "wallet bootstrap"] : [], // }, + { + name: "loggedin (desktop)", + use: { ...devices["Desktop Chrome"] }, + testMatch: "**/*.loggedin.spec.ts", + dependencies: process.env.CI ? ["auth setup"] : [], + }, + { + name: "loggedin (mobile)", + use: { ...devices["Pixel 5"] }, + testMatch: "**/*.loggedin.spec.ts", + dependencies: process.env.CI ? ["auth setup"] : [], + }, + { + name: "dRep", + use: { ...devices["Desktop Chrome"] }, + testMatch: "**/*.dRep.spec.ts", + dependencies: process.env.CI ? ["auth setup", "dRep setup"] : [], + }, + { + name: "delegation", + use: { ...devices["Desktop Chrome"] }, + testMatch: "**/*.delegation.spec.ts", + dependencies: process.env.CI ? ["auth setup", "dRep setup"] : [], + teardown: "cleanup delegation", + }, + { + name: "independent (desktop)", + use: { ...devices["Desktop Chrome"] }, + testIgnore: [ + "**/*.delegation.spec.ts", + "**/*.loggedin.spec.ts", + "**/*.dRep.spec.ts", + ], + }, + { + name: "independent (mobile)", + use: { ...devices["Pixel 5"] }, + testIgnore: [ + "**/*.tx.spec.ts", + "**/*.loggedin.spec.ts", + "**/*.dRep.spec.ts", + ], + }, + { + name: "cleanup delegation", + testMatch: "delegation.teardown.ts", + }, ], - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, }); diff --git a/tests/govtool-frontend/playwright/tests/1-wallet-connect/walletConnect.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/1-wallet-connect/walletConnect.loggedin.spec.ts new file mode 100644 index 000000000..ab080382a --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/1-wallet-connect/walletConnect.loggedin.spec.ts @@ -0,0 +1,13 @@ +import { user01Wallet } from "@constants/staticWallets"; +import { test } from "@fixtures/walletExtension"; +import LoginPage from "@pages/loginPage"; + +test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); + +test("1B: Should connect wallet with single stake key @smoke @fast", async ({ + page, +}) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.isLoggedIn(); +}); diff --git a/tests/govtool-frontend/playwright/tests/1-wallet-connect/walletConnect.spec.ts b/tests/govtool-frontend/playwright/tests/1-wallet-connect/walletConnect.spec.ts new file mode 100644 index 000000000..ea33fdcb4 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/1-wallet-connect/walletConnect.spec.ts @@ -0,0 +1,52 @@ +import createWallet from "@fixtures/createWallet"; +import { test } from "@fixtures/walletExtension"; +import convertBufferToHex from "@helpers/convertBufferToHex"; +import { ShelleyWallet } from "@helpers/crypto"; +import LoginPage from "@pages/loginPage"; +import { expect } from "@playwright/test"; + +test("1A. Should connect wallet and choose stake-key to use @smoke @fast", async ({ + page, +}) => { + const shellyWallet = await ShelleyWallet.generate(); + const extraPubStakeKey = convertBufferToHex(shellyWallet.stakeKey.public); + const extraRewardAddress = convertBufferToHex( + shellyWallet.rewardAddressRawBytes(0) + ); + + await createWallet(page, { + extraRegisteredPubStakeKeys: [extraPubStakeKey], + extraRewardAddresses: [extraRewardAddress], + }); + + const loginPage = new LoginPage(page); + await loginPage.login(); +}); + +test("1C: Should disconnect Wallet When connected @smoke @fast", async ({ + page, +}) => { + await createWallet(page); + + const loginPage = new LoginPage(page); + await loginPage.login(); + + await loginPage.logout(); +}); + +test("1D. Should check correct network (Testnet/Mainnet) on connection @smoke @fast", async ({ + page, +}) => { + const wrongNetworkId = 1; // mainnet network + await createWallet(page, { networkId: wrongNetworkId }); + + const errors: Array = []; + page.on("pageerror", (error) => { + errors.push(error); + }); + + const loginPage = new LoginPage(page); + await loginPage.login(); + + expect(errors).not.toHaveLength(0); +}); diff --git a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.delegation.spec.ts b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.delegation.spec.ts new file mode 100644 index 000000000..92811984f --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.delegation.spec.ts @@ -0,0 +1,92 @@ +import environments from "@constants/environments"; +import { + adaHolder01Wallet, + adaHolder02Wallet, + dRep01Wallet, +} from "@constants/staticWallets"; +import { createTempDRepAuth } from "@datafactory/createAuth"; +import { test } from "@fixtures/walletExtension"; +import { ShelleyWallet } from "@helpers/crypto"; +import { createNewPageWithWallet } from "@helpers/page"; +import { pollTransaction, waitForTxConfirmation } from "@helpers/transaction"; +import DelegationPage from "@pages/delegationPage"; +import { expect } from "@playwright/test"; +import kuberService from "@services/kuberService"; + +test.describe("Delegate to others", () => { + test.use({ + storageState: ".auth/adaHolder01.json", + wallet: adaHolder01Wallet, + }); + + test("2A. Should show delegated DRep Id on dashboard after delegation @slow @critical", async ({ + page, + }, testInfo) => { + test.setTimeout(testInfo.timeout + 2 * environments.txTimeOut); + + const delegationPage = new DelegationPage(page); + await delegationPage.goto(); + + delegationPage.delegateToDRep(dRep01Wallet.dRepId); + await waitForTxConfirmation(page); + + page.goto("/"); + await expect(page.getByTestId("delegated-dRep-id")).toHaveText( + dRep01Wallet.dRepId + ); + }); +}); + +test.describe("Delegate to myself", () => { + test("2E. Should register as SoleVoter @slow @critical", async ({ + page, + browser, + }, testInfo) => { + test.setTimeout(testInfo.timeout + 2 * environments.txTimeOut); + + const wallet = await ShelleyWallet.generate(); + const txRes = await kuberService.transferADA( + [wallet.addressBech32(environments.networkId)], + 600 + ); + await pollTransaction(txRes.txId, txRes.lockInfo); + const dRepAuth = await createTempDRepAuth(page, wallet); + const dRepPage = await createNewPageWithWallet(browser, { + storageState: dRepAuth, + wallet, + enableStakeSigning: true, + }); + await dRepPage.goto("/"); + await dRepPage.getByTestId("register-as-sole-voter-button").click(); + await dRepPage.getByTestId("retire-button").click(); // BUG: Incorrect test-id , it should be continue-retirement + await expect( + dRepPage.getByTestId("registration-transaction-submitted-modal") + ).toBeVisible(); + dRepPage.getByTestId("confirm-modal-button").click(); + await waitForTxConfirmation(dRepPage); + + await expect(dRepPage.getByText("You are a Sole Voter")).toBeVisible(); + }); +}); + +test.describe("Change Delegation", () => { + test.use({ + storageState: ".auth/adaHolder02.json", + wallet: adaHolder02Wallet, + }); + + // Skipped: Blocked because delegation is not working + test.skip("2F. Should change delegated dRep @slow @critical", async ({ + page, + }) => { + const delegationPage = new DelegationPage(page); + await delegationPage.goto(); + delegationPage.delegateToDRep(dRep01Wallet.dRepId); + await waitForTxConfirmation(page); + + // await delegationPage.goto("/"); + // await adaHolderPage.getByTestId("change-dRep-button").click(); + // await delegationPage.delegateToDRep(dRep02Wallet.dRepId); + // await waitForTxConfirmation(page); + }); +}); diff --git a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.loggedin.spec.ts new file mode 100644 index 000000000..969607c1f --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.loggedin.spec.ts @@ -0,0 +1,49 @@ +import { user01Wallet } from "@constants/staticWallets"; +import { test } from "@fixtures/walletExtension"; +import DelegationPage from "@pages/delegationPage"; +import { expect } from "@playwright/test"; + +test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); + +test("2B. Should access delegation to dRep page @smoke @fast", async ({ + page, +}) => { + await page.goto("/"); + + await page.getByTestId("delegate-button").click(); // BUG incorrect test ID + await expect( + page.getByRole("navigation").getByText("DRep Directory") + ).toBeVisible(); +}); + +// Skipped: No need to insert dRep id to delegate +test.skip("2I. Should check validity of DRep Id @slow", async ({ page }) => { + // const urlToIntercept = "**/utxo?**"; + // const invalidDRepId = generateRandomDRepId(); + // const validDRepId = dRep01Wallet.dRepId; + // // Invalidity checks + // const delegationPage = new DelegationPage(page); + // await delegationPage.goto(); + // await delegationPage.delegateToDRep(invalidDRepId); + // await expect(delegationPage.delegationErrorModal).toBeVisible(); + // await delegationPage.resetDRepForm(); + // // Validity checks + // await delegationPage.dRepInput.fill(validDRepId); + // await delegationPage.delegateBtn.click(); + // const response = await page.waitForResponse(urlToIntercept); + // expect(response.body.length).toEqual(0); +}); + +test("2D. Verify Delegation Behavior in Connected State @smoke @fast", async ({ + page, +}) => { + const delegationPage = new DelegationPage(page); + await delegationPage.goto(); + + // Verifying delegation options + await delegationPage.delegationOptionsDropdown.click(); + await expect(delegationPage.signalNoConfidenceCard).toBeVisible(); + await expect(delegationPage.abstainDelegationCard).toBeVisible(); + + expect(await delegationPage.delegateBtns.count()).toBeGreaterThanOrEqual(2); +}); diff --git a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts new file mode 100644 index 000000000..7104d728d --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts @@ -0,0 +1,14 @@ +import { expect, test } from "@playwright/test"; + +test("2C. Verify DRep Behavior in Disconnected State @smoke @fast", async ({ + page, +}) => { + await page.goto("/"); + + await page.getByTestId("delegate-connect-wallet-button").click(); + await page + .locator('[data-testid$="-connect-to-delegate-button"]') + .first() + .click(); + await expect(page.getByTestId("connect-your-wallet-modal")).toBeVisible(); +}); diff --git a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts new file mode 100644 index 000000000..2e633866d --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts @@ -0,0 +1,138 @@ +import environments from "@constants/environments"; +import { dRep01Wallet } from "@constants/staticWallets"; +import { createTempDRepAuth } from "@datafactory/createAuth"; +import { test } from "@fixtures/walletExtension"; +import convertBufferToHex from "@helpers/convertBufferToHex"; +import { ShelleyWallet } from "@helpers/crypto"; +import { createNewPageWithWallet } from "@helpers/page"; +import { pollTransaction, waitForTxConfirmation } from "@helpers/transaction"; +import DRepRegistrationPage from "@pages/dRepRegistrationPage"; +import GovernanceActionsPage from "@pages/governanceActionsPage"; +import { expect } from "@playwright/test"; +import kuberService from "@services/kuberService"; +import * as crypto from "crypto"; + +test.describe("Logged in DReps", () => { + test.use({ storageState: ".auth/dRep01.json", wallet: dRep01Wallet }); + + test("3A. Should show dRepId on dashboard after connecting registered dRep Wallet", async ({ + page, + }) => { + await page.goto("/"); + await expect(page.getByTestId("dRep-id-display")).toContainText( + dRep01Wallet.dRepId + ); // BUG: testId -> dRep-id-display-dashboard (It is taking sidebar dRep-id) + }); + + test.use({ storageState: ".auth/dRep01.json", wallet: dRep01Wallet }); + + // Skipped: No option to update metadata + test.skip("3H. Should be able to update metadata @slow", async ({ page }) => { + page.getByTestId("change-metadata-button").click(); + page.getByTestId("url-input").fill("https://google.com"); + page.getByTestId("hash-input").fill(crypto.randomBytes(32).toString("hex")); + await expect(page.getByTestId("confirm-modal-button")).toBeVisible(); + }); +}); + +test.describe("Temporary DReps", () => { + test("3G. Should show confirmation message with link to view transaction, when DRep registration txn is submitted @slow ", async ({ + page, + browser, + }, testInfo) => { + test.setTimeout(testInfo.timeout + environments.txTimeOut); + + const wallet = await ShelleyWallet.generate(); + const res = await kuberService.transferADA( + [wallet.addressBech32(environments.networkId)], + 600 + ); + await pollTransaction(res.txId, res.lockInfo); + + const tempDRepAuth = await createTempDRepAuth(page, wallet); + const dRepPage = await createNewPageWithWallet(browser, { + storageState: tempDRepAuth, + wallet, + enableStakeSigning: true, + }); + + const dRepRegistrationPage = new DRepRegistrationPage(dRepPage); + await dRepRegistrationPage.goto(); + await dRepRegistrationPage.register({ name: "Test_dRep" }); + + await expect(dRepRegistrationPage.registrationSuccessModal).toBeVisible(); + await expect( + dRepRegistrationPage.registrationSuccessModal.getByText("this link") + ).toBeVisible(); + }); + + test("3I. Should verify retire as DRep @slow", async ({ + page, + browser, + }, testInfo) => { + test.setTimeout(testInfo.timeout + environments.txTimeOut); + + const wallet = await ShelleyWallet.generate(); + const registrationRes = await kuberService.dRepRegistration( + convertBufferToHex(wallet.stakeKey.private), + convertBufferToHex(wallet.stakeKey.pkh) + ); + await pollTransaction(registrationRes.txId, registrationRes.lockInfo); + + const tempDRepAuth = await createTempDRepAuth(page, wallet); + const dRepPage = await createNewPageWithWallet(browser, { + storageState: tempDRepAuth, + wallet, + enableStakeSigning: true, + }); + + await dRepPage.goto("/"); + await dRepPage.getByTestId("retire-button").click(); + await dRepPage.getByTestId("retire-button").click(); // BUG testId -> continue-retire-button + + await expect( + dRepPage.getByTestId("retirement-transaction-error-modal") + ).toBeVisible(); + }); + + test("3J. Verify DRep behavior in retired state", async ({ + page, + browser, + }, testInfo) => { + test.setTimeout(testInfo.timeout + 3 * environments.txTimeOut); + + const wallet = await ShelleyWallet.generate(); + const registrationRes = await kuberService.dRepRegistration( + convertBufferToHex(wallet.stakeKey.private), + convertBufferToHex(wallet.stakeKey.pkh) + ); + await pollTransaction(registrationRes.txId, registrationRes.lockInfo); + + const res = await kuberService.transferADA([ + wallet.addressBech32(environments.networkId), + ]); + await pollTransaction(res.txId, res.lockInfo); + + const dRepAuth = await createTempDRepAuth(page, wallet); + const dRepPage = await createNewPageWithWallet(browser, { + storageState: dRepAuth, + wallet, + enableStakeSigning: true, + }); + + await dRepPage.goto("/"); + await dRepPage.getByTestId("retire-button").click(); + await dRepPage.getByTestId("retire-button").click(); // BUG: testId -> continue-retire-button + await expect( + dRepPage.getByTestId("retirement-transaction-submitted-modal") + ).toBeVisible(); + dRepPage.getByTestId("confirm-modal-button").click(); + await waitForTxConfirmation(dRepPage); + + const governanceActionsPage = new GovernanceActionsPage(dRepPage); + await governanceActionsPage.goto(); + const govActionDetailsPage = + await governanceActionsPage.viewFirstProposal(); + await expect(govActionDetailsPage.voteBtn).not.toBeVisible(); + }); +}); diff --git a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.loggedin.spec.ts new file mode 100644 index 000000000..a263302a8 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.loggedin.spec.ts @@ -0,0 +1,65 @@ +import { user01Wallet } from "@constants/staticWallets"; +import { faker } from "@faker-js/faker"; +import { test } from "@fixtures/walletExtension"; +import DRepRegistrationPage from "@pages/dRepRegistrationPage"; +import { expect } from "@playwright/test"; + +test.use({ + storageState: ".auth/user01.json", + wallet: user01Wallet, +}); + +test("3B. Should access DRep registration page @fast @smoke", async ({ + page, +}) => { + await page.goto("/"); + + await page.getByTestId("register-button").click(); + await expect(page.getByText("Become a DRep")).toBeVisible(); +}); + +test("3D.Verify DRep registration functionality with Wallet Connected State State @fast @smoke", async ({ + page, +}) => { + const dRepRegistrationPage = new DRepRegistrationPage(page); + await dRepRegistrationPage.goto(); + + await expect(dRepRegistrationPage.nameInput).toBeVisible(); + await expect(dRepRegistrationPage.emailInput).toBeVisible(); + await expect(dRepRegistrationPage.bioInput).toBeVisible(); + await expect(dRepRegistrationPage.linkInput).toBeVisible(); + await expect(dRepRegistrationPage.addLinkBtn).toBeVisible(); + await expect(dRepRegistrationPage.continueBtn).toBeVisible(); +}); + +// Skipped: Because there are no fields for url and hash inputs. +test.skip("3E. Should reject invalid data and accept valid data @smoke @fast", async ({ + page, +}) => { + const dRepRegistrationPage = new DRepRegistrationPage(page); + await dRepRegistrationPage.goto(); + + // Invalidity test + faker.helpers + .multiple(() => faker.internet.displayName(), { count: 100 }) + .forEach(async (dRepName) => { + await dRepRegistrationPage.nameInput.fill(dRepName); + await dRepRegistrationPage.nameInput.clear({ force: true }); + }); + + // Validity test +}); + +test("3F. Should create proper DRep registration request, when registered with data @slow", async ({ + page, +}) => { + const urlToIntercept = "**/utxo?**"; + + const dRepRegistrationPage = new DRepRegistrationPage(page); + await dRepRegistrationPage.goto(); + + await dRepRegistrationPage.register({ name: "Test_dRep" }); + + const response = await page.waitForResponse(urlToIntercept); + expect(response.body.length).toEqual(0); +}); diff --git a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.spec.ts b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.spec.ts new file mode 100644 index 000000000..9daf86969 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.spec.ts @@ -0,0 +1,10 @@ +import { test, expect } from "@playwright/test"; + +test("3C. Should open wallet connection popup, when Register as DRep from wallet unconnected state @smoke @fast", async ({ + page, +}) => { + await page.goto("/"); + + await page.getByTestId("register-connect-wallet-button").click(); + await expect(page.getByTestId("connect-your-wallet-modal")).toBeVisible(); +}); diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.loggedin.spec.ts new file mode 100644 index 000000000..d55c62514 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.loggedin.spec.ts @@ -0,0 +1,124 @@ +import { user01Wallet } from "@constants/staticWallets"; +import { test } from "@fixtures/walletExtension"; +import { isMobile, openDrawerLoggedIn } from "@helpers/mobile"; +import removeAllSpaces from "@helpers/removeAllSpaces"; +import GovernanceActionsPage from "@pages/governanceActionsPage"; +import { expect } from "@playwright/test"; + +const filterOptionNames = [ + "Protocol Parameter Change", + "New Committee", + "Hard Fork", + "No Confidence", + "Info Action", + "Treasury Withdrawal", + "Update to the Constitution", +]; + +enum SortOption { + SoonToExpire = "SoonestToExpire", + NewestFirst = "NewestCreated", + HighestYesVotes = "MostYesVotes", +} + +test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); + +test("4A.1: Should access Governance Actions page with connecting wallet @smoke @fast", async ({ + page, +}) => { + await page.goto("/"); + if (isMobile(page)) { + await openDrawerLoggedIn(page); + } + + await page.getByTestId("governance-actions-link").click(); + await expect(page.getByText(/Governance Actions/i)).toHaveCount(2); +}); + +test("4B.1: Should restrict voting for users who are not registered as DReps (with wallet connected) @fast", async ({ + page, +}) => { + const govActionsPage = new GovernanceActionsPage(page); + await govActionsPage.goto(); + + const govActionDetailsPage = await govActionsPage.viewFirstProposal(); + await expect(govActionDetailsPage.voteBtn).not.toBeVisible(); +}); + +test("4C.1: Should filter Governance Action Type on governance actions page @slow", async ({ + page, +}) => { + test.slow(); + + const govActionsPage = new GovernanceActionsPage(page); + await govActionsPage.goto(); + + await govActionsPage.filterBtn.click(); + + // Single filter + for (const option of filterOptionNames) { + await govActionsPage.filterProposalByNames([option]); + await govActionsPage.validateFilters([option]); + await govActionsPage.unFilterProposalByNames([option]); + } + + // Multiple filters + const multipleFilterOptionNames = [...filterOptionNames]; + while (multipleFilterOptionNames.length > 1) { + await govActionsPage.filterProposalByNames(multipleFilterOptionNames); + await govActionsPage.validateFilters(multipleFilterOptionNames); + await govActionsPage.unFilterProposalByNames(multipleFilterOptionNames); + multipleFilterOptionNames.pop(); + } +}); + +test("4C.2: Should sort Governance Action Type on governance actions page @slow", async ({ + page, +}) => { + test.slow(); + + const govActionsPage = new GovernanceActionsPage(page); + await govActionsPage.goto(); + + await govActionsPage.sortBtn.click(); + + govActionsPage.sortProposal(SortOption.SoonToExpire); + await govActionsPage.validateSort( + SortOption.SoonToExpire, + (p1, p2) => p1.expiryDate <= p2.expiryDate + ); + + govActionsPage.sortProposal(SortOption.NewestFirst); + await govActionsPage.validateSort( + SortOption.NewestFirst, + (p1, p2) => p1.createdDate >= p2.createdDate + ); + + govActionsPage.sortProposal(SortOption.HighestYesVotes); + await govActionsPage.validateSort( + SortOption.HighestYesVotes, + (p1, p2) => p1.yesVotes >= p2.yesVotes + ); +}); + +test("4D: Should filter and sort Governance Action Type on governance actions page @slow", async ({ + page, +}) => { + test.slow(); + + const govActionsPage = new GovernanceActionsPage(page); + await govActionsPage.goto(); + + await govActionsPage.sortBtn.click(); + await govActionsPage.sortProposal(SortOption.SoonToExpire); + + await govActionsPage.filterBtn.click(); + govActionsPage.filterProposalByNames([filterOptionNames[0]]); + + await govActionsPage.validateSort( + SortOption.SoonToExpire, + (p1, p2) => p1.expiryDate <= p2.expiryDate, + [removeAllSpaces(filterOptionNames[0])] + ); + await govActionsPage.validateFilters([filterOptionNames[0]]); +}); diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts new file mode 100644 index 000000000..19b5e5364 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts @@ -0,0 +1,21 @@ +import GovernanceActionsPage from "@pages/governanceActionsPage"; +import { expect, test } from "@playwright/test"; + +test("4A.2: Should access Governance Actions page without connecting wallet @smoke @fast", async ({ + page, +}) => { + await page.goto("/"); + await page.getByTestId("move-to-governance-actions-button").click(); + + await expect(page.getByText(/Governance actions/i)).toHaveCount(2); +}); + +test("4B.2: Should restrict voting for users who are not registered as DReps (without wallet connected) @flaky @fast", async ({ + page, +}) => { + const govActionsPage = new GovernanceActionsPage(page); + await govActionsPage.goto(); + + const govActionDetailsPage = await govActionsPage.viewFirstProposal(); + await expect(govActionDetailsPage.voteBtn).not.toBeVisible(); +}); diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts new file mode 100644 index 000000000..dd13ab19b --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts @@ -0,0 +1,182 @@ +import environments from "@constants/environments"; +import { dRep01Wallet } from "@constants/staticWallets"; +import { createTempDRepAuth } from "@datafactory/createAuth"; +import { test } from "@fixtures/walletExtension"; +import convertBufferToHex from "@helpers/convertBufferToHex"; +import { ShelleyWallet } from "@helpers/crypto"; +import { createNewPageWithWallet } from "@helpers/page"; +import { pollTransaction, waitForTxConfirmation } from "@helpers/transaction"; +import GovernanceActionDetailsPage from "@pages/governanceActionDetailsPage"; +import GovernanceActionsPage from "@pages/governanceActionsPage"; +import { expect } from "@playwright/test"; +import kuberService from "@services/kuberService"; + +test.describe("Proposal checks", () => { + test.use({ storageState: ".auth/dRep01.json", wallet: dRep01Wallet }); + + let govActionDetailsPage: GovernanceActionDetailsPage; + + test.beforeEach(async ({ page }) => { + const govActionsPage = new GovernanceActionsPage(page); + await govActionsPage.goto(); + + govActionDetailsPage = await govActionsPage.viewFirstProposal(); + }); + + test("5A. Should show relevant details about governance action as DRep @slow", async () => { + await expect(govActionDetailsPage.governanceActionType).toBeVisible(); + await expect(govActionDetailsPage.submittedDate).toBeVisible(); + await expect(govActionDetailsPage.expiryDate).toBeVisible(); + + await expect(govActionDetailsPage.externalModalBtn).toBeVisible(); + await expect(govActionDetailsPage.contextBtn).toBeVisible(); + + await expect(govActionDetailsPage.voteBtn).toBeVisible(); + await expect(govActionDetailsPage.yesVoteRadio).toBeVisible(); + await expect(govActionDetailsPage.noVoteRadio).toBeVisible(); + await expect(govActionDetailsPage.abstainRadio).toBeVisible(); + }); + + test("5B. Should view Vote button on governance action item on registered as DRep @slow", async () => { + await expect(govActionDetailsPage.voteBtn).toBeVisible(); + }); + + test("5C. Should show required field in proposal voting on registered as DRep @slow", async () => { + await expect(govActionDetailsPage.voteBtn).toBeVisible(); + await expect(govActionDetailsPage.yesVoteRadio).toBeVisible(); + await expect(govActionDetailsPage.noVoteRadio).toBeVisible(); + await expect(govActionDetailsPage.abstainRadio).toBeVisible(); + + await govActionDetailsPage.contextBtn.click(); + + await expect(govActionDetailsPage.contextInput).toBeVisible(); + await govActionDetailsPage.cancelModalBtn.click(); + + await govActionDetailsPage.yesVoteRadio.click(); + await expect(govActionDetailsPage.voteBtn).toBeEnabled(); + }); + + // Skipped: No url/hash input to validate + test.skip("5D. Should validate proposal voting @slow", async () => { + // const invalidURLs = ["testdotcom", "https://testdotcom", "https://test.c"]; + // invalidURLs.forEach(async (url) => { + // govActionDetailsPage.urlInput.fill(url); + // await expect(govActionDetailsPage.urlInputError).toBeVisible(); + // }); + // const validURLs = ["https://test.com"]; + // validURLs.forEach(async (url) => { + // govActionDetailsPage.urlInput.fill(url); + // await expect(govActionDetailsPage.urlInputError).not.toBeVisible(); + // }); + // const invalidHashes = [ + // randomBytes(20).toString("hex"), + // randomBytes(32).toString(), + // ]; + // invalidHashes.forEach(async (hash) => { + // govActionDetailsPage.hashInput.fill(hash); + // await expect(govActionDetailsPage.hashInputError).toBeVisible(); + // }); + // const validHash = randomBytes(32).toString("hex"); + // govActionDetailsPage.hashInput.fill(validHash); + // await expect(govActionDetailsPage.hashInputError).not.toBeVisible(); + }); + + test("5G. Should show warning to the users to visit the site at their own risk, when external url is opened", async () => { + await govActionDetailsPage.externalModalBtn.click(); + + await expect(govActionDetailsPage.externalLinkModal).toBeVisible(); + await expect( + govActionDetailsPage.currentPage.getByText("Be careful", { exact: false }) + ).toBeVisible(); + }); + + test("5H. Should open a new tab, when external URL is opened", async ({ + page, + }) => { + await govActionDetailsPage.externalModalBtn.click(); + await govActionDetailsPage.continueModalBtn.click(); + + const existingPages = page.context().pages(); + expect(existingPages).toHaveLength(1); + }); +}); + +test.describe("Perform voting", () => { + let govActionDetailsPage: GovernanceActionDetailsPage; + + test.beforeEach(async ({ page, browser }, testInfo) => { + test.setTimeout(testInfo.timeout + 2 * environments.txTimeOut); + + const wallet = await ShelleyWallet.generate(); + const registrationRes = await kuberService.dRepRegistration( + convertBufferToHex(wallet.stakeKey.private), + convertBufferToHex(wallet.stakeKey.pkh) + ); + await pollTransaction(registrationRes.txId, registrationRes.lockInfo); + + const res = await kuberService.transferADA( + [wallet.addressBech32(environments.networkId)], + 40 + ); + await pollTransaction(res.txId, registrationRes.lockInfo); + + const tempDRepAuth = await createTempDRepAuth(page, wallet); + + const dRepPage = await createNewPageWithWallet(browser, { + storageState: tempDRepAuth, + wallet, + enableStakeSigning: true, + }); + + const govActionsPage = new GovernanceActionsPage(dRepPage); + await govActionsPage.goto(); + + govActionDetailsPage = await govActionsPage.viewFirstProposal(); + }); + + test("5E. Should re-vote with new data on a already voted governance action", async ({}, testInfo) => { + test.setTimeout(testInfo.timeout + 2 * environments.txTimeOut); + + govActionDetailsPage.vote(); + await waitForTxConfirmation(govActionDetailsPage.currentPage); + + const governanceActionsPage = new GovernanceActionsPage( + govActionDetailsPage.currentPage + ); + await governanceActionsPage.goto(); + await governanceActionsPage.votedTab.click(); + await expect( + govActionDetailsPage.currentPage.getByTestId("my-vote").getByText("Yes") + ).toBeVisible(); + + govActionDetailsPage = await governanceActionsPage.viewFirstVotedProposal(); + govActionDetailsPage.reVote(); + await waitForTxConfirmation(govActionDetailsPage.currentPage); + + await governanceActionsPage.votedTab.click(); + await expect( + govActionDetailsPage.currentPage.getByTestId("my-vote").getByText("No") + ).toBeVisible(); + }); + + test("5F. Should show notification of casted vote after vote", async ({}) => { + await govActionDetailsPage.vote(); + await expect(govActionDetailsPage.voteSuccessModal).toBeVisible(); + }); + + test("5I. Should view the vote details,when viewing governance action already voted by the DRep", async ({}, testInfo) => { + test.setTimeout(testInfo.timeout + environments.txTimeOut); + + govActionDetailsPage.vote(); + await waitForTxConfirmation(govActionDetailsPage.currentPage); + + const governanceActionsPage = new GovernanceActionsPage( + govActionDetailsPage.currentPage + ); + await governanceActionsPage.goto(); + await governanceActionsPage.votedTab.click(); + await expect( + govActionDetailsPage.currentPage.getByTestId("my-vote").getByText("Yes") + ).toBeVisible(); + }); +}); diff --git a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts new file mode 100644 index 000000000..8f58286fb --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts @@ -0,0 +1,36 @@ +import { user01Wallet } from "@constants/staticWallets"; +import { test } from "@fixtures/walletExtension"; +import DRepRegistrationPage from "@pages/dRepRegistrationPage"; +import DelegationPage from "@pages/delegationPage"; +import { expect } from "@playwright/test"; + +test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); + +// Skipped: No dRepId to validate +test.skip("6B. Provides error for invalid format @fast @smoke", async ({ + page, +}) => { + // invalid dRep delegation + const delegationPage = new DelegationPage(page); + await delegationPage.goto(); + await delegationPage.delegateToDRep("Random values"); + await expect(delegationPage.delegationErrorModal).toBeVisible(); + + // invalid dRep registration + const dRepRegistrationPage = new DRepRegistrationPage(page); + await dRepRegistrationPage.goto(); + + // await dRepRegistrationPage.urlInput.fill("abc"); + // await expect(dRepRegistrationPage.urlInputError).toBeVisible(); + + // await dRepRegistrationPage.hashInput.fill("abc"); + // await expect(dRepRegistrationPage.hashInputError).toBeVisible(); +}); + +test("6D: Proper label and recognition of the testnet network @fast @smoke", async ({ + page, +}) => { + await page.goto("/"); + + await expect(page.getByText("testnet")).toBeVisible(); +}); diff --git a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts new file mode 100644 index 000000000..d26feb683 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts @@ -0,0 +1,44 @@ +import { isMobile, openDrawer } from "@helpers/mobile"; +import { expect, test } from "@playwright/test"; +import environments from "lib/constants/environments"; + +test("6C. Navigation within the dApp @smoke @fast", async ({ + page, + context, +}) => { + await page.goto("/"); + + if (isMobile(page)) { + await openDrawer(page); + } + await page.getByTestId("governance-actions-link").click(); + await expect(page).toHaveURL(/\/governance_actions/); + + if (isMobile(page)) { + await openDrawer(page); + } + const [guidesPage] = await Promise.all([ + context.waitForEvent("page"), + page.getByTestId("guides-link").click(), + ]); + + await expect(guidesPage).toHaveURL( + `${environments.docsUrl}/about/what-is-sanchonet-govtool` + ); + + if (isMobile(page)) { + await openDrawer(page); + } + const [faqsPage] = await Promise.all([ + context.waitForEvent("page"), + page.getByTestId("faqs-link").click(), + ]); + + await expect(faqsPage).toHaveURL(`${environments.docsUrl}/faqs`); + + if (isMobile(page)) { + await openDrawer(page); + } + await page.getByTestId("dashboard-link").click(); + expect(page.url()).toEqual(`${environments.frontendUrl}/`); +}); diff --git a/tests/govtool-frontend/playwright/tests/auth.setup.ts b/tests/govtool-frontend/playwright/tests/auth.setup.ts new file mode 100644 index 000000000..d2206b142 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/auth.setup.ts @@ -0,0 +1,44 @@ +// Saves storage state to a file in the .auth directory + +import { + adaHolder01Wallet, + dRep01Wallet, + user01Wallet, +} from "@constants/staticWallets"; +import { importWallet } from "@fixtures/importWallet"; +import { test as setup } from "@fixtures/walletExtension"; +import LoginPage from "@pages/loginPage"; + +const dRep01AuthFile = ".auth/dRep01.json"; +const adaHolder01AuthFile = ".auth/adaHolder01.json"; +const user01AuthFile = ".auth/user01.json"; + +setup("Create DRep 01 auth", async ({ page, context }) => { + await importWallet(page, dRep01Wallet); + + const loginPage = new LoginPage(page); + await loginPage.login(); + await loginPage.isLoggedIn(); + + await context.storageState({ path: dRep01AuthFile }); +}); + +setup("Create User 01 auth", async ({ page, context }) => { + await importWallet(page, user01Wallet); + + const loginPage = new LoginPage(page); + await loginPage.login(); + await loginPage.isLoggedIn(); + + await context.storageState({ path: user01AuthFile }); +}); + +setup("Create AdaHolder 01 auth", async ({ page, context }) => { + await importWallet(page, adaHolder01Wallet); + + const loginPage = new LoginPage(page); + await loginPage.login(); + await loginPage.isLoggedIn(); + + await context.storageState({ path: adaHolder01AuthFile }); +}); diff --git a/tests/govtool-frontend/playwright/tests/dRep.setup.ts b/tests/govtool-frontend/playwright/tests/dRep.setup.ts new file mode 100644 index 000000000..38c587663 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/dRep.setup.ts @@ -0,0 +1,43 @@ +import environments from "@constants/environments"; +import { dRepWallets } from "@constants/staticWallets"; +import { pollTransaction } from "@helpers/transaction"; +import { expect, test as setup } from "@playwright/test"; +import kuberService from "@services/kuberService"; +import { Logger } from "../../cypress/lib/logger/logger"; +import fetch = require("node-fetch"); + +const dRepInfo = require("../lib/_mock/dRepInfo.json"); + +setup.describe.configure({ timeout: environments.txTimeOut }); + +dRepWallets.forEach((wallet) => { + setup(`Register DRep of wallet: ${wallet.address}`, async () => { + try { + const res = await kuberService.dRepRegistration( + wallet.stake.private, + wallet.stake.pkh + ); + + await pollTransaction(res.txId, res.lockInfo); + } catch (err) { + if (err.status === 400) { + expect(true, "DRep already registered").toBeTruthy(); + } else { + throw err; + } + } + }); +}); + +setup("Setup dRep metadata", async () => { + try { + const res = await fetch(`${environments.metadataBucketUrl}/Test_dRep`, { + method: "PUT", + body: JSON.stringify(dRepInfo), + }); + Logger.success("Uploaded dRep metadata to bucket"); + } catch (err) { + Logger.fail(`Failed to upload dRep metadata: ${err}`); + throw err; + } +}); diff --git a/tests/govtool-frontend/playwright/tests/delegation.teardown.ts b/tests/govtool-frontend/playwright/tests/delegation.teardown.ts new file mode 100644 index 000000000..986ab1a45 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/delegation.teardown.ts @@ -0,0 +1,18 @@ +import environments from "@constants/environments"; +import { adaHolderWallets } from "@constants/staticWallets"; +import { pollTransaction } from "@helpers/transaction"; +import { test as cleanup } from "@playwright/test"; +import kuberService from "@services/kuberService"; + +cleanup.describe.configure({ timeout: environments.txTimeOut }); + +cleanup(`Abstain delegation`, async () => { + const stakePrivKeys = adaHolderWallets.map((wallet) => wallet.stake.private); + const stakePkhs = adaHolderWallets.map((wallet) => wallet.stake.pkh); + + const { txId, lockInfo } = await kuberService.abstainDelegations( + stakePrivKeys, + stakePkhs + ); + await pollTransaction(txId, lockInfo); +}); diff --git a/tests/govtool-frontend/playwright/tests/example.spec.ts b/tests/govtool-frontend/playwright/tests/example.spec.ts deleted file mode 100644 index 190e698e2..000000000 --- a/tests/govtool-frontend/playwright/tests/example.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import test, { expect } from "@playwright/test"; - -test.describe("Example test specs", () => { - test("has title", async ({ page }) => { - await page.goto("https://playwright.dev/"); - - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); - }); - - test("get started link", async ({ page }) => { - await page.goto("https://playwright.dev/"); - - // Click the get started link. - await page.getByRole("link", { name: "Get started" }).click(); - - // Expects page to have a heading with the name of Installation. - await expect( - page.getByRole("heading", { name: "Installation" }) - ).toBeVisible(); - }); -}); diff --git a/tests/govtool-frontend/playwright/tests/faucet.setup.ts b/tests/govtool-frontend/playwright/tests/faucet.setup.ts new file mode 100644 index 000000000..5aeb32639 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/faucet.setup.ts @@ -0,0 +1,16 @@ +import { faucetWallet } from "@constants/staticWallets"; +import { pollTransaction } from "@helpers/transaction"; +import { test as setup } from "@playwright/test"; +import { loadAmountFromFaucet } from "@services/faucetService"; +import kuberService from "@services/kuberService"; +import environments from "lib/constants/environments"; + +setup.describe.configure({ mode: "serial", timeout: environments.txTimeOut }); + +setup("Fund faucet wallet", async () => { + const balance = await kuberService.getBalance(faucetWallet.address); + if (balance > 2000) return; + + const res = await loadAmountFromFaucet(faucetWallet.address); + await pollTransaction(res.txid); +}); diff --git a/tests/govtool-frontend/playwright/tests/wallet.bootstrap.ts b/tests/govtool-frontend/playwright/tests/wallet.bootstrap.ts new file mode 100644 index 000000000..1d7397fda --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/wallet.bootstrap.ts @@ -0,0 +1,68 @@ +import { adaHolderWallets, dRepWallets } from "@constants/staticWallets"; +import { ShelleyWallet } from "@helpers/crypto"; +import extractDRepsFromStakePubKey from "@helpers/extractDRepsFromStakePubkey"; +import generateShellyWallets from "@helpers/generateShellyWallets"; +import setupWallets from "@helpers/setupWallets"; +import { pollTransaction } from "@helpers/transaction"; +import { expect, test as setup } from "@playwright/test"; +import kuberService from "@services/kuberService"; +import { writeFile } from "fs"; +import environments from "lib/constants/environments"; + +setup.describe.configure({ mode: "serial", timeout: environments.txTimeOut }); + +setup("Setup mock wallets", async () => { + setup.skip(!environments.oneTimeWalletSetup); + + const wallets = await generateShellyWallets(6); + await setupWallets(wallets); + saveWallets(wallets); +}); + +setup("Fund static wallets", async () => { + const addresses = [...adaHolderWallets, ...dRepWallets].map((e) => e.address); + const res = await kuberService.transferADA(addresses); + await pollTransaction(res.txId); +}); + +for (const wallet of [...adaHolderWallets, ...dRepWallets]) { + setup(`Register stake of static wallet: ${wallet.address}`, async () => { + try { + const { txId, lockInfo } = await kuberService.registerStake( + wallet.stake.private, + wallet.stake.pkh, + wallet.payment.private, + wallet.address + ); + await pollTransaction(txId, lockInfo); + } catch (err) { + if (err.status === 400) { + expect(true, "Stake already registered").toBeTruthy(); + } else { + throw Error(err); + } + } + }); +} + +function saveWallets(wallets: ShelleyWallet[]) { + const jsonWallets = []; + for (let i = 0; i < wallets.length; i++) { + const stakePublicKey = Buffer.from(wallets[i].stakeKey.public).toString( + "hex" + ); + const { dRepIdBech32 } = extractDRepsFromStakePubKey(stakePublicKey); + + jsonWallets.push({ + ...wallets[i].json(), + address: wallets[i].addressBech32(environments.networkId), + dRepId: dRepIdBech32, + }); + } + const jsonString = JSON.stringify(jsonWallets, null, 2); + writeFile("lib/_mock/wallets.json", jsonString, "utf-8", (err) => { + if (err) { + throw Error("Failed to write wallets into file"); + } + }); +} diff --git a/tests/govtool-frontend/playwright/tsconfig.json b/tests/govtool-frontend/playwright/tsconfig.json new file mode 100644 index 000000000..0209bfb32 --- /dev/null +++ b/tests/govtool-frontend/playwright/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "baseUrl": "./", // This must be specified if "paths" is. + "paths": { + "@mock/*": ["lib/_mock/*"], + "@fixtures/*": ["lib/fixtures/*"], + "@helpers/*": ["lib/helpers/*"], + "@pages/*": ["lib/pages/*"], + "@services/*": ["lib/services/*"], + "@types": ["lib/types.ts"], + "@constants/*": ["lib/constants/*"], + "@datafactory/*": ["lib/datafactory/*"] + } + } +}