From e8070a27e8175293bf478dd7f3f87e97703ec7c8 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Tue, 9 Jul 2024 07:46:04 -0400 Subject: [PATCH] Adds tests for Near.call (#25) * init tests * adds tests for vm custom elements * fix structure for video, update pause method * wip * wip * passing unhappy paths * renames * happy path tests * validates more transaction details --- .../code/near-call/object-params.js | 7 + .../code/near-call/positional-params.js | 13 ++ .../storage-states/wallet-connected.json | 31 +++ .../storage-states/wallet-not-connected.json | 10 + playwright-tests/testUtils.js | 27 +++ playwright-tests/tests/vm/near-call.spec.js | 216 ++++++++++++++++++ 6 files changed, 304 insertions(+) create mode 100644 playwright-tests/code/near-call/object-params.js create mode 100644 playwright-tests/code/near-call/positional-params.js create mode 100644 playwright-tests/storage-states/wallet-connected.json create mode 100644 playwright-tests/storage-states/wallet-not-connected.json create mode 100644 playwright-tests/tests/vm/near-call.spec.js diff --git a/playwright-tests/code/near-call/object-params.js b/playwright-tests/code/near-call/object-params.js new file mode 100644 index 0000000..bddd608 --- /dev/null +++ b/playwright-tests/code/near-call/object-params.js @@ -0,0 +1,7 @@ +const { tx } = props; + +function handleClick() { + Near.call(tx); +} + +return ; diff --git a/playwright-tests/code/near-call/positional-params.js b/playwright-tests/code/near-call/positional-params.js new file mode 100644 index 0000000..c52fde7 --- /dev/null +++ b/playwright-tests/code/near-call/positional-params.js @@ -0,0 +1,13 @@ +const { contractName, methodName, args, gas, deposit, extra } = props; + +function handleClick() { + if (extra) { + Near.call(contractName, methodName, args, gas, deposit, extra); + } else if (contractName && !methodName && !args && !gas && !deposit) { + Near.call(contractName); + } else { + Near.call(contractName, methodName, args, gas, deposit); + } +} + +return ; diff --git a/playwright-tests/storage-states/wallet-connected.json b/playwright-tests/storage-states/wallet-connected.json new file mode 100644 index 0000000..4e8bde5 --- /dev/null +++ b/playwright-tests/storage-states/wallet-connected.json @@ -0,0 +1,31 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:3000", + "localStorage": [ + { + "name": "near-wallet-selector:selectedWalletId", + "value": "\"my-near-wallet\"" + }, + { + "name": "near_app_wallet_auth_key", + "value": "{\"accountId\":\"anybody.near\",\"allKeys\":[\"ed25519:CziSGowWUKiP5N5pqGUgXCJXtqpySAk29YAU6zEs5RAi\"]}}" + }, + { + "name": "near-social-vm:v01::accountId:", + "value": "anybody.near" + }, + { + "name": "near-api-js:keystore:anybody.near:mainnet", + "value": "ed25519:67p9ygtfVNZz5AzMkeN4bqstCck8RWxWDthcTa7JaBvxkrBRTc6A43SsuPy9LdtiR6XtSRD1HiS4KQTWCZw83FKS" + }, + { + "name": "near-wallet-selector:contract", + "value": "{\"contractId\":\"social.near\",\"methodNames\":[]}" + } + ] + } + ], + "sessionStorage": [] +} \ No newline at end of file diff --git a/playwright-tests/storage-states/wallet-not-connected.json b/playwright-tests/storage-states/wallet-not-connected.json new file mode 100644 index 0000000..2aaa5a3 --- /dev/null +++ b/playwright-tests/storage-states/wallet-not-connected.json @@ -0,0 +1,10 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:3000", + "localStorage": [] + } + ], + "sessionStorage": [] +} diff --git a/playwright-tests/testUtils.js b/playwright-tests/testUtils.js index 7c89430..dd04514 100644 --- a/playwright-tests/testUtils.js +++ b/playwright-tests/testUtils.js @@ -1,4 +1,6 @@ import { expect } from "@playwright/test"; +import path from "path"; +import fs from "fs"; export const pauseIfVideoRecording = async (page) => { let isVideoRecorded = (await page.video()) ? true : false; @@ -40,3 +42,28 @@ export const escapeHtml = (html) => { .replace(/"/g, """) .replace(/'/g, "'"); }; + +export const useCode = async (page, filePath, props) => { + const fullPath = path.join(__dirname, "code", filePath); + try { + const code = fs.readFileSync(fullPath, "utf8"); + const initialProps = props ? JSON.stringify(props) : ""; + + // Set code and initialProps attribute + await page.evaluate( + ({ code, initialProps }) => { + const viewer = document.querySelector("near-social-viewer"); + viewer.setAttribute("code", code); + viewer.setAttribute("initialprops", initialProps); + }, + { code, initialProps } + ); + + // Verify the viewer is visible + await waitForSelectorToBeVisible(page, "near-social-viewer"); + + await pauseIfVideoRecording(page); + } catch (err) { + throw new Error(`Error loading file: ${err.message}`); + } +}; diff --git a/playwright-tests/tests/vm/near-call.spec.js b/playwright-tests/tests/vm/near-call.spec.js new file mode 100644 index 0000000..9553794 --- /dev/null +++ b/playwright-tests/tests/vm/near-call.spec.js @@ -0,0 +1,216 @@ +import { describe, expect, test } from "@playwright/test"; +import { pauseIfVideoRecording, useCode } from "../../testUtils"; + +describe("Near.call", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/"); + }); + + describe("User is not logged in", () => { + test.use({ + storageState: "playwright-tests/storage-states/wallet-not-connected.json", + }); + + test("should throw 'No wallet selected' error", async ({ page }) => { + const expectedErrorMessage = "No wallet selected"; + + await useCode(page, "near-call/positional-params.js", { + contractName: "hello.near-examples.near", + methodName: "set_greeting", + }); + + // Expect error message to be displayed + const [error] = await Promise.all([ + page.waitForEvent("pageerror"), + await page.getByRole("button", { name: "click" }).click(), + ]); + + await pauseIfVideoRecording(page); + + // Verify that the expected error message was logged + expect(error).toBeTruthy(); + expect(error.message).toContain(expectedErrorMessage); + }); + }); + + describe("User is logged in", () => { + test.use({ + storageState: "playwright-tests/storage-states/wallet-connected.json", + }); + + describe("arguments: (contractName, methodName, args?, gas?, deposit?)", () => { + test("should throw error if appropriate arguments are not provide (over 5 args)", async ({ + page, + }) => { + const expectedErrorMessage = + "Method: Near.call. Required argument: 'contractName'. If the first argument is a string: 'methodName'. Optional: 'args', 'gas' (defaults to 300Tg), 'deposit' (defaults to 0)"; + + await useCode(page, "near-call/positional-params.js", { + contractName: "hello.near-examples.near", + methodName: "set_greeting", + args: { message: "Hello, World!" }, + gas: "300000000000000", + deposit: "1000000000000000000000000", + extra: "extra argument", + }); + + // Expect error message to be displayed + const [error] = await Promise.all([ + page.waitForEvent("pageerror"), + await page.getByRole("button", { name: "click" }).click(), + ]); + + await pauseIfVideoRecording(page); + + // Verify that the expected error message was logged + expect(error).toBeTruthy(); + expect(error.message).toContain(expectedErrorMessage); + }); + test("should open confirmation modal with appropriate details", async ({ + page, + }) => { + const expectedTransactionData = { message: "Hello, World!" }; + + await useCode(page, "near-call/positional-params.js", { + contractName: "hello.near-examples.near", + methodName: "set_greeting", + args: { message: "Hello, World!" }, + gas: "300000000000000", + deposit: "1000000000000000000000000", + }); + + await page.getByRole("button", { name: "click" }).click(); + + const transactionObj = JSON.parse( + await page.locator("div.modal-body code").innerText() + ); + + await pauseIfVideoRecording(page); + + // do something with transactionObj + expect(transactionObj).toMatchObject(expectedTransactionData); + }); + }); + + describe("arguments: ({ tx })", () => { + test("should throw error if transaction object argument is invalid (single string provided)", async ({ + page, + }) => { + const expectedErrorMessage = + "Method: Near.call. Required argument: 'tx/txs'. A single argument call requires an TX object or an array of TX objects."; + + await useCode(page, "near-call/positional-params.js", { + contractName: "hello.near-examples.near", + }); + + // Expect error message to be displayed + const [error] = await Promise.all([ + page.waitForEvent("pageerror"), + await page.getByRole("button", { name: "click" }).click(), + ]); + + await pauseIfVideoRecording(page); + + // Verify that the expected error message was logged + expect(error).toBeTruthy(); + expect(error.message).toContain(expectedErrorMessage); + }); + test("should open confirmation modal with appropriate details", async ({ + page, + }) => { + const expectedTransactionData = { message: "Hello, World!" }; + + await useCode(page, "near-call/object-params.js", { + tx: { + contractName: "hello.near-examples.near", + methodName: "set_greeting", + args: { message: "Hello, World!" }, + gas: "300000000000000", + deposit: "1000000000000000000000000", + }, + }); + + await page.getByRole("button", { name: "click" }).click(); + + const transactionObj = JSON.parse( + await page.locator("div.modal-body code").innerText() + ); + const modalBody = await page.locator(".modal-body"); + const transactionNumber = await modalBody.locator("h4").textContent(); + const values = await modalBody + .locator(".font-monospace") + .allInnerTexts(); + const [contractId, methodName, deposit, gas] = values; + + await pauseIfVideoRecording(page); + + // do something with transactionObj + expect(transactionObj).toMatchObject(expectedTransactionData); + expect(transactionNumber).toBe("Transaction #1"); + expect(contractId).toBe("hello.near-examples.near"); + expect(methodName).toBe("set_greeting"); + expect(deposit).toBe("1 NEAR"); + expect(gas).toBe("300 TGas"); + }); + }); + + describe("arguments: [{ tx }, ...]", () => { + test("should open confirmation modal with appropriate details, multiple transactions", async ({ + page, + }) => { + await useCode(page, "near-call/object-params.js", { + tx: [ + { + contractName: "hello.near-examples.near", + methodName: "set_greeting", + args: { message: "Hello, World!" }, + gas: "300000000000000", + deposit: "1000000000000000000000000", + }, + { + contractName: "goodbye.near-examples.near", + methodName: "set_goobye", + args: { message: "Goodbye, World!" }, + gas: "600000000000000", + deposit: "2000000000000000000000000", + }, + ], + }); + + await page.getByRole("button", { name: "click" }).click(); + + const blocks = await page + .locator("div.modal-body code") + .allInnerTexts(); + const modalBody = await page.locator(".modal-body"); + const transactionNumbers = await modalBody.locator("h4").allInnerTexts(); + const values = await modalBody + .locator(".font-monospace") + .allInnerTexts(); + + const [firstBlock, secondBlock] = blocks; + + await pauseIfVideoRecording(page); + + expect(transactionNumbers).toEqual(["Transaction #1", "Transaction #2"]); + expect(values).toEqual([ + "hello.near-examples.near", + "set_greeting", + "1 NEAR", + "300 TGas", + "goodbye.near-examples.near", + "set_goobye", + "2 NEAR", + "600 TGas", + ]); + + expect(JSON.parse(firstBlock)).toMatchObject({ + message: "Hello, World!", + }); + expect(JSON.parse(secondBlock)).toMatchObject({ + message: "Goodbye, World!", + }); + }); + }); + }); +});