diff --git a/.github/workflows/continuous-integration-workflow-events.yml b/.github/workflows/continuous-integration-workflow-events.yml index 26a500dbc..c0c62b5a3 100644 --- a/.github/workflows/continuous-integration-workflow-events.yml +++ b/.github/workflows/continuous-integration-workflow-events.yml @@ -36,10 +36,7 @@ jobs: npx playwright install - name: Run tests run: | - # The whole proposal folder does not work yet - # INSTANCE=events npx playwright test --project=events playwright-tests/tests/proposal - INSTANCE=events npx playwright test --project=events playwright-tests/tests/proposal/comment.spec.js - INSTANCE=events npx playwright test --project=events playwright-tests/tests/proposal/links.spec.js + INSTANCE=events npx playwright test --project=events playwright-tests/tests/proposal playwright-tests-events: name: Events Committee - Playwright tests runs-on: ubuntu-latest diff --git a/.github/workflows/continuous-integration-workflow-infra.yml b/.github/workflows/continuous-integration-workflow-infra.yml index 446b60122..65ca1439a 100644 --- a/.github/workflows/continuous-integration-workflow-infra.yml +++ b/.github/workflows/continuous-integration-workflow-infra.yml @@ -52,6 +52,9 @@ jobs: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/mpeterdev/bos-loader/releases/download/v0.7.1/bos-loader-v0.7.1-installer.sh | sh npx playwright install-deps npx playwright install - - name: Run tests + - name: Run infrastructure specific tests run: | INSTANCE=infrastructure npx playwright test --project=infrastructure playwright-tests/tests/infrastructure + - name: Run generic proposal tests + run: | + INSTANCE=infrastructure npx playwright test --project=infrastructure playwright-tests/tests/proposal diff --git a/.prettierignore b/.prettierignore index 87364a273..86c1b7031 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,3 @@ test-results +build +instances/**/src/** diff --git a/instances/events-committee.near/widget/devhub/entity/proposal/Editor.jsx b/instances/events-committee.near/widget/devhub/entity/proposal/Editor.jsx index 45df82be7..3e80ab118 100644 --- a/instances/events-committee.near/widget/devhub/entity/proposal/Editor.jsx +++ b/instances/events-committee.near/widget/devhub/entity/proposal/Editor.jsx @@ -442,9 +442,23 @@ useEffect(() => { Array.isArray(proposalIdsArray) && proposalIds.length !== proposalIdsArray.length ) { - setCreateTxn(false); - setProposalId(proposalIds[proposalIds.length - 1]); - setShowProposalPage(true); + const latestProposalId = proposalIds[proposalIds.length - 1]; + const latestProposal = Near.view( + "${REPL_DEVHUB_CONTRACT}", + "get_proposal", + { + proposal_id: latestProposalId, + } + ); + + if ( + latestProposal.snapshot.name === title && + latestProposal.snapshot.description === description + ) { + setCreateTxn(false); + setProposalId(proposalIds[proposalIds.length - 1]); + setShowProposalPage(true); + } } } } diff --git a/instances/infrastructure-committee.near/widget/components/proposals/Editor.jsx b/instances/infrastructure-committee.near/widget/components/proposals/Editor.jsx index 02ed5d1ca..d9c39e0f2 100644 --- a/instances/infrastructure-committee.near/widget/components/proposals/Editor.jsx +++ b/instances/infrastructure-committee.near/widget/components/proposals/Editor.jsx @@ -492,9 +492,23 @@ useEffect(() => { Array.isArray(proposalIdsArray) && proposalIds.length !== proposalIdsArray.length ) { - setCreateTxn(false); - setProposalId(proposalIds[proposalIds.length - 1]); - setShowProposalViewModal(true); + const latestProposalId = proposalIds[proposalIds.length - 1]; + const latestProposal = Near.view( + "${REPL_INFRASTRUCTURE_COMMITTEE_CONTRACT}", + "get_proposal", + { + proposal_id: latestProposalId, + } + ); + + if ( + latestProposal.snapshot.name === title && + latestProposal.snapshot.description === description + ) { + setCreateTxn(false); + setProposalId(proposalIds[proposalIds.length - 1]); + setShowProposalViewModal(true); + } } } } diff --git a/playwright-tests/storage-states/wallet-connected-with-devhub-moderator-access-key.json b/playwright-tests/storage-states/wallet-connected-with-devhub-moderator-access-key.json index 300fddab2..f1bb4c7dc 100644 --- a/playwright-tests/storage-states/wallet-connected-with-devhub-moderator-access-key.json +++ b/playwright-tests/storage-states/wallet-connected-with-devhub-moderator-access-key.json @@ -24,6 +24,10 @@ "name": "events-committee.near_wallet_auth_key", "value": "{\"accountId\":\"theori.near\",\"allKeys\":[\"ed25519:9enAAsDiQ2NvKU2TsHLETp96sYeN2qkbHxhpXLjCQThS\"]}" }, + { + "name": "infrastructure-committee.near_wallet_auth_key", + "value": "{\"accountId\":\"theori.near\",\"allKeys\":[\"ed25519:G6PXTFq5xNvyYL4LttWVvHo7srmfGuTumX5W7JyXV21P\"]}" + }, { "name": "near-api-js:keystore:theori.near:mainnet", "value": "ed25519:67p9ygtfVNZz5AzMkeN4bqstCck8RWxWDthcTa7JaBvxkrBRTc6A43SsuPy9LdtiR6XtSRD1HiS4KQTWCZw83FKS" @@ -40,6 +44,10 @@ "name": "events-committee.near:keystore:theori.near:mainnet", "value": "ed25519:2fBVgJF9NpbAsGfNsGVZGbA1wX46Xyqcn9pZZ9hLA6BpawvDbeD9CAuXDteHMGa3mRuTg9pMQJqvqNhGRfUzYhtc" }, + { + "name": "infrastructure-committee.near:keystore:theori.near:mainnet", + "value": "ed25519:3WZFLnory8LU1iji6dXPrhQr4bq7SvaAH68i7odEzJ8N1acMn8LjAU8py84LmiQkgVs4x5Ma1XaFy1BpQGuBRVS1" + }, { "name": "near-social-vm:v01::accountId:", "value": "\"theori.near\"" diff --git a/playwright-tests/tests/events/proposals.spec.js b/playwright-tests/tests/events/proposals.spec.js index fddc7f9af..5545397a5 100644 --- a/playwright-tests/tests/events/proposals.spec.js +++ b/playwright-tests/tests/events/proposals.spec.js @@ -131,23 +131,40 @@ test.describe("Don't ask again enabled", () => { await pauseIfVideoRecording(page); await expect(disabledSubmitButton).not.toBeAttached(); + let newProposalId = 0; await mockTransactionSubmitRPCResponses( page, async ({ route, request, transaction_completed, last_receiver_id }) => { const postData = request.postDataJSON(); - if ( - transaction_completed && - postData.params?.method_name === "get_all_proposal_ids" - ) { + if (postData.params?.method_name === "get_all_proposal_ids") { const response = await route.fetch(); const json = await response.json(); - console.log( - "transaction completed, modifying get_proposal_ids result" - ); + await new Promise((resolve) => setTimeout(() => resolve(), 4000)); const resultObj = decodeResultJSON(json.result.result); - resultObj.push(1); - console.log(JSON.stringify(resultObj)); + newProposalId = resultObj[resultObj.length - 1] + 1; + if (transaction_completed) { + resultObj.push(newProposalId); + } + json.result.result = encodeResultJSON(resultObj); + + await route.fulfill({ response, json }); + } else if ( + postData.params?.method_name === "get_proposal" && + postData.params.args_base64 === + btoa(JSON.stringify({ proposal_id: newProposalId })) + ) { + postData.params.args_base64 = btoa( + JSON.stringify({ proposal_id: newProposalId - 1 }) + ); + const response = await route.fetch({ + postData: JSON.stringify(postData), + }); + const json = await response.json(); + + let resultObj = decodeResultJSON(json.result.result); + resultObj.snapshot.name = "Test proposal 123456"; + resultObj.snapshot.description = "The test proposal description."; json.result.result = encodeResultJSON(resultObj); await route.fulfill({ response, json }); diff --git a/playwright-tests/tests/other/proposals.devhub.spec.js b/playwright-tests/tests/other/proposals.devhub.spec.js new file mode 100644 index 000000000..86cbd71d2 --- /dev/null +++ b/playwright-tests/tests/other/proposals.devhub.spec.js @@ -0,0 +1,289 @@ +import { test as base, expect } from "@playwright/test"; +import { pauseIfVideoRecording } from "../../testUtils.js"; +import { MOCK_RPC_URL } from "../../util/rpcmock.js"; + +const test = base.extend({ + // Define an option and provide a default value. + // We can later override it in the config. + account: ["devhub.near", { option: true }], + proposalAuthorAccountId: ["megha19.near", { option: true }], +}); + +test.afterEach( + async ({ page }) => await page.unrouteAll({ behavior: "ignoreErrors" }) +); + +let acceptedTermsVersion = 122927956; +async function getCurrentBlockHeight(page) { + // set current block height for accepted terms and conditions + await page.route(MOCK_RPC_URL, async (route) => { + const request = route.request(); + const requestPostData = request.postDataJSON(); + if ( + requestPostData?.method === "block" && + requestPostData?.params?.finality === "optimistic" + ) { + const response = await route.fetch(); + const json = await response.json(); + json.result.header.height = acceptedTermsVersion; + await route.fulfill({ response, json }); + } else { + await route.continue(); + } + }); +} + +test.describe("Wallet is connected", () => { + test.use({ + storageState: "playwright-tests/storage-states/wallet-connected.json", + }); + + test("should show relevant users in mention autocomplete", async ({ + page, + account, + }) => { + await page.goto(`/${account}/widget/app?page=proposal&id=112`); + + await page.waitForSelector(`iframe`, { + state: "visible", + }); + + const comment = page.getByRole("link", { name: "geforcy.near" }); + await comment.scrollIntoViewIfNeeded(); + await expect(comment).toBeVisible(); + await page.waitForTimeout(5000); + + const delay_milliseconds_between_keypress_when_typing = 0; + const commentEditor = page + .frameLocator("iframe") + .last() + .locator(".CodeMirror textarea"); + await commentEditor.focus(); + await commentEditor.pressSequentially( + `Make sure relevant users show up in a mention. @`, + { + delay: delay_milliseconds_between_keypress_when_typing, + } + ); + + await pauseIfVideoRecording(page); + const iframe = page.frameLocator("iframe").last(); + const liFrameLocators = iframe.frameLocator( + 'ul[id="mentiondropdown"] > li' + ); + const liLocators = await liFrameLocators.owner().all(); + const expected = [ + "thomasguntenaar.near", // author, + "theori.near", // supervisor, + "neardevdao.near", // requested_sponsor, + "geforcy.near", // comment author, + ]; + let mentionSuggestions = []; + for (let i = 0; i < liLocators.length; i++) { + const text = await liLocators[i].innerText(); + mentionSuggestions.push(text); + } + + // When I manually test, it shows the correct 4 users + expect(mentionSuggestions.slice(0, 4)).toEqual(expected); + await pauseIfVideoRecording(page); + }); + + test("should show only valid input in amount field and show error for invalid", async ({ + page, + account, + }) => { + test.setTimeout(120000); + const delay_milliseconds_between_keypress_when_typing = 0; + await page.goto(`/${account}/widget/app?page=create-proposal`); + const input = page.locator('input[type="text"]').nth(2); + const errorText = await page.getByText( + "Please enter the nearest positive whole number." + ); + await input.pressSequentially("12345de", { + delay: delay_milliseconds_between_keypress_when_typing, + }); + await expect(errorText).toBeVisible(); + // clear input field + for (let i = 0; i < 7; i++) { + await input.press("Backspace", { + delay: delay_milliseconds_between_keypress_when_typing, + }); + } + await input.pressSequentially("12334", { + delay: delay_milliseconds_between_keypress_when_typing, + }); + await expect(errorText).toBeHidden(); + await pauseIfVideoRecording(page); + }); + + test("should create a proposal, autolink reference to existing proposal", async ({ + page, + account, + }) => { + test.setTimeout(120000); + await getCurrentBlockHeight(page); + await page.goto(`/${account}/widget/app?page=create-proposal`); + + const delay_milliseconds_between_keypress_when_typing = 0; + const titleArea = await page.getByRole("textbox").first(); + await expect(titleArea).toBeEditable({ timeout: 10_000 }); + await titleArea.pressSequentially("Test proposal 123456", { + delay: delay_milliseconds_between_keypress_when_typing, + }); + + await pauseIfVideoRecording(page); + + const categoryDropdown = await page.locator(".dropdown-toggle").first(); + await categoryDropdown.click(); + await page.locator(".dropdown-menu > div > div:nth-child(2) > div").click(); + + const disabledSubmitButton = await page.locator( + ".submit-draft-button.disabled" + ); + + const summary = await page.locator('textarea[type="text"]'); + await expect(summary).toBeEditable(); + await summary.pressSequentially("Test proposal summary 123456789", { + delay: delay_milliseconds_between_keypress_when_typing, + }); + + await pauseIfVideoRecording(page); + + const descriptionArea = await page + .frameLocator("iframe") + .locator(".CodeMirror textarea"); + await descriptionArea.focus(); + await descriptionArea.pressSequentially( + `The test proposal description. And referencing #`, + { + delay: delay_milliseconds_between_keypress_when_typing, + } + ); + await descriptionArea.pressSequentially("2", { delay: 10 }); + await pauseIfVideoRecording(page); + + await page + .frameLocator("iframe") + .getByText( + "#2 DevHub Developer Contributor report by Thomas for 03/11/2024 – 04/12/2024" + ) + .click({ timeout: 10000 }); + + await page.locator('input[type="text"]').nth(2).pressSequentially("12345", { + delay: delay_milliseconds_between_keypress_when_typing, + }); + await pauseIfVideoRecording(page); + await page.getByRole("checkbox").first().click(); + await pauseIfVideoRecording(page); + await expect(disabledSubmitButton).toBeAttached(); + await page.getByRole("checkbox").nth(1).click(); + await pauseIfVideoRecording(page); + await expect(disabledSubmitButton).not.toBeAttached(); + + const submitButton = await page.getByText("Submit Draft"); + await submitButton.scrollIntoViewIfNeeded(); + await submitButton.hover(); + await pauseIfVideoRecording(page); + await submitButton.click(); + const transactionText = JSON.stringify( + JSON.parse(await page.locator("div.modal-body code").innerText()), + null, + 1 + ); + await expect(transactionText).toEqual( + JSON.stringify( + { + labels: [], + body: { + proposal_body_version: "V0", + name: "Test proposal 123456", + description: + "The test proposal description. And referencing [#2 DevHub Developer Contributor report by Thomas for 03/11/2024 – 04/12/2024](https://near.social/devhub.near/widget/app?page=proposal&id=2)", + category: "DevDAO Platform", + summary: "Test proposal summary 123456789", + linked_proposals: [], + requested_sponsorship_usd_amount: "12345", + requested_sponsorship_paid_in_currency: "USDC", + receiver_account: "efiz.near", + supervisor: null, + requested_sponsor: "neardevdao.near", + timeline: { + status: "DRAFT", + }, + }, + accepted_terms_and_conditions_version: acceptedTermsVersion, + }, + null, + 1 + ) + ); + + await pauseIfVideoRecording(page); + }); + + test.describe("filter proposals using different mechanism", () => { + test.beforeEach(async ({ page, account }) => { + await page.goto(`/${account}/widget/app?page=proposals`); + expect(page.locator(".proposal-card").first()).toBeVisible({ + timeout: 10000, + }); + }); + test("should filter proposals by categories", async ({ page, account }) => { + test.setTimeout(60000); + const category = "DevDAO Operations"; + await page.getByRole("button", { name: "Category" }).click(); + await page.getByRole("list").getByText(category).click(); + await expect( + page.getByRole("button", { name: `Category : ${category}` }) + ).toBeVisible(); + const loader = page.getByRole("img", { name: "loader" }); + expect(loader).toBeHidden({ timeout: 10000 }); + const categoryTag = await page.locator(".purple-bg").first(); + await expect(categoryTag).toContainText(category); + }); + + test("should filter proposals by timeline", async ({ page }) => { + test.setTimeout(60000); + const stage = "Funded"; + await page.getByRole("button", { name: "Stage" }).click(); + await page.getByRole("list").getByText(stage).click(); + await expect( + page.getByRole("button", { name: `Stage : ${stage}` }) + ).toBeVisible(); + const loader = page.getByRole("img", { name: "loader" }); + expect(loader).toBeHidden({ timeout: 10000 }); + const timelineTag = await page.locator(".green-tag").first(); + await expect(timelineTag).toContainText(stage.toUpperCase()); + }); + + test("should filter proposals by author", async ({ page }) => { + test.setTimeout(60000); + const accountId = "megha19.near"; + const profileName = "Megha"; + await page.getByRole("button", { name: "Author" }).click(); + await page.getByRole("list").getByText(accountId).click(); + await expect( + page.getByRole("button", { name: `Author : ${accountId}` }) + ).toBeVisible(); + const loader = page.getByRole("img", { name: "loader" }); + expect(loader).toBeHidden({ timeout: 10000 }); + await expect(page.getByText(`By ${profileName} ・`).first()).toBeVisible({ + timeout: 10_000, + }); + }); + + test("should filter proposals by search text", async ({ page }) => { + test.setTimeout(60000); + const term = "DevHub Developer Contributor report by Megha"; + const input = await page.getByPlaceholder("Search by content"); + await input.click(); + await input.fill(term); + await input.press("Enter"); + const loader = page.getByRole("img", { name: "loader" }); + expect(loader).toBeHidden({ timeout: 10000 }); + const element = page.locator(`:has-text("${term}")`).nth(1); + await expect(element).toBeVisible({ timeout: 10_000 }); + }); + }); +}); diff --git a/playwright-tests/tests/proposal/proposals.spec.js b/playwright-tests/tests/proposal/proposals.spec.js index a3c48cfc0..3e36a4df2 100644 --- a/playwright-tests/tests/proposal/proposals.spec.js +++ b/playwright-tests/tests/proposal/proposals.spec.js @@ -1,11 +1,6 @@ import { test as base, expect } from "@playwright/test"; import { pauseIfVideoRecording } from "../../testUtils.js"; -import { - getCacheValue, - setCacheValue, - setCommitWritePermissionDontAskAgainCacheValues, - setDontAskAgainCacheValues, -} from "../../util/cache.js"; +import { setCacheValue, setDontAskAgainCacheValues } from "../../util/cache.js"; import { mockTransactionSubmitRPCResponses, decodeResultJSON, @@ -86,9 +81,15 @@ test.describe("Don't ask again enabled", () => { test("should create a proposal", async ({ page, account }) => { test.setTimeout(120000); + const proposalSubmitterAccount = "petersalomonsen.near"; + await page.goto(`/${account}/widget/app?page=proposals`); - const widgetSrc = `${account}/widget/devhub.entity.proposal.Editor`; + const widgetSrc = + account === "infrastructure-committee.near" + ? `${account}/widget/components.proposals.Editor` + : `${account}/widget/devhub.entity.proposal.Editor`; + await setCacheValue({ page, key: JSON.stringify({ @@ -115,7 +116,9 @@ test.describe("Don't ask again enabled", () => { await titleArea.blur(); await pauseIfVideoRecording(page); - const categoryDropdown = await page.locator(".dropdown-toggle").first(); + const categoryDropdown = await page.locator(".dropdown-toggle", { + hasText: "Select Category", + }); await categoryDropdown.click(); await page.locator(".dropdown-menu > div > div:nth-child(2) > div").click(); @@ -135,16 +138,19 @@ test.describe("Don't ask again enabled", () => { const proposalAmount = "1000"; await page.locator('input[type="text"]').nth(2).fill(proposalAmount); await pauseIfVideoRecording(page); - await page.getByRole("checkbox").first().click(); + + const consentCheckBoxes = await page.getByRole("checkbox"); + await consentCheckBoxes.first().click(); await pauseIfVideoRecording(page); const disabledSubmitButton = await page.locator( ".submit-draft-button.disabled" ); - - await expect(disabledSubmitButton).toBeAttached(); - await page.getByRole("checkbox").nth(1).click(); - await pauseIfVideoRecording(page); + if ((await consentCheckBoxes.count()) === 2) { + await expect(disabledSubmitButton).toBeAttached(); + await page.getByRole("checkbox").nth(1).click(); + await pauseIfVideoRecording(page); + } await expect(disabledSubmitButton).not.toBeAttached(); let newProposalId = 0; @@ -183,10 +189,10 @@ test.describe("Don't ask again enabled", () => { resultObj = { proposal_version: "V0", id: newProposalId, - author_id: "petersalomonsen.near", + author_id: proposalSubmitterAccount, social_db_post_block_height: "128860426", snapshot: { - editor_id: "petersalomonsen.near", + editor_id: proposalSubmitterAccount, timestamp: "1727265468109441208", labels: [], proposal_body_version: "V2", @@ -197,7 +203,7 @@ test.describe("Don't ask again enabled", () => { linked_proposals: [], requested_sponsorship_usd_amount: proposalAmount, requested_sponsorship_paid_in_currency: "USDT", - receiver_account: "petersalomonsen.near", + receiver_account: proposalSubmitterAccount, requested_sponsor: "neardevdao.near", supervisor: "theori.near", timeline: { @@ -211,7 +217,7 @@ test.describe("Don't ask again enabled", () => { }, snapshot_history: [ { - editor_id: "petersalomonsen.near", + editor_id: proposalSubmitterAccount, timestamp: "1727265421865873611", labels: [], proposal_body_version: "V0", @@ -222,7 +228,7 @@ test.describe("Don't ask again enabled", () => { linked_proposals: [], requested_sponsorship_usd_amount: proposalAmount, requested_sponsorship_paid_in_currency: "USDT", - receiver_account: "petersalomonsen.near", + receiver_account: proposalSubmitterAccount, requested_sponsor: "neardevdao.near", supervisor: "theori.near", timeline: { @@ -235,6 +241,28 @@ test.describe("Don't ask again enabled", () => { json.result.result = encodeResultJSON(resultObj); await route.fulfill({ response, json }); + } else if ( + postData.params?.method_name === "is_allowed_to_edit_proposal" && + postData.params.args_base64 === + btoa( + JSON.stringify({ + proposal_id: newProposalId, + editor: proposalSubmitterAccount, + }) + ) + ) { + await route.fulfill({ + json: { + jsonrpc: "2.0", + result: { + result: encodeResultJSON(true), + logs: [], + block_height: 17817336, + block_hash: "4qkA4sUUG8opjH5Q9bL5mWJTnfR4ech879Db1BZXbx6P", + }, + id: "dontcare", + }, + }); } else { await route.continue(); } @@ -264,13 +292,16 @@ test.describe("Don't ask again enabled", () => { await loadingIndicator.waitFor({ state: "detached", timeout: 10000 }); await expect(loadingIndicator).not.toBeVisible(); + if (account === "infrastructure-committee.near") { + await page.getByRole("button", { name: "View Proposal" }).click(); + } + await expect(await page.getByText(`#${newProposalId}`)).toBeVisible(); + await expect(await page.getByText("DRAFT", { exact: true })).toBeVisible(); + await expect( await page.getByRole("button", { name: "Edit" }) ).toBeVisible(); - await expect(await page.getByText(`#${newProposalId}`)).toBeVisible(); - await expect(await page.getByText("DRAFT", { exact: true })).toBeVisible(); - await pauseIfVideoRecording(page); }); }); @@ -286,6 +317,8 @@ test.describe('Moderator with "Don\'t ask again" enabled', () => { }) => { test.setTimeout(70000); let isTransactionCompleted = false; + const isInfrastructureCommittee = + account === "infrastructure-committee.near"; await mockRpcRequest({ page, @@ -329,13 +362,23 @@ test.describe('Moderator with "Don\'t ask again" enabled', () => { } ); - await page.goto(`/${account}/widget/app?page=proposal&id=17`); + await page.goto( + `/${account}/widget/app?page=proposal&id=${ + isInfrastructureCommittee ? "4" : "17" + }` + ); + + const widgetSrc = isInfrastructureCommittee + ? `${account}/widget/components.proposals.Proposal` + : `${account}/widget/devhub.entity.proposal.Proposal`; await setDontAskAgainCacheValues({ page, contractId: account, - widgetSrc: `${account}/widget/devhub.entity.proposal.Proposal`, - methodName: "edit_proposal_versioned_timeline", + widgetSrc, + methodName: isInfrastructureCommittee + ? "edit_proposal_timeline" + : "edit_proposal_versioned_timeline", }); const firstStatusBadge = await page @@ -343,7 +386,13 @@ test.describe('Moderator with "Don\'t ask again" enabled', () => { .first(); await expect(firstStatusBadge).toHaveText("REVIEW", { timeout: 10000 }); await page.locator(".d-flex > div > .bi").click(); - await page.getByTestId("Sponsor verifies KYC/KYB").check(); + + await page + .locator("label") + .filter({ hasText: "Sponsor verifies KYC/KYB" }) + .locator("xpath=preceding-sibling::*[1]") + .check(); + await page.getByRole("button", { name: "Review", exact: true }).click(); await page.getByText("Approved", { exact: true }).first().click(); @@ -356,25 +405,31 @@ test.describe('Moderator with "Don\'t ask again" enabled', () => { const callContractToast = await page.getByText("Sending transaction"); await expect(callContractToast).toBeVisible(); await expect(callContractToast).not.toBeAttached(); - const timeLineStatusSubmittedToast = await page - .getByText("Timeline status submitted successfully") - .first(); - await expect(timeLineStatusSubmittedToast).toBeVisible(); - + let timeLineStatusSubmittedToast; + if (!isInfrastructureCommittee) { + timeLineStatusSubmittedToast = await page + .getByText("Timeline status submitted successfully") + .first(); + await expect(timeLineStatusSubmittedToast).toBeVisible(); + } await expect(firstStatusBadge).toHaveText("APPROVED"); + await firstStatusBadge.scrollIntoViewIfNeeded(); await pauseIfVideoRecording(page); - const lastLogItem = await page.locator( - "div.flex-1.gap-1.w-100.text-wrap.text-muted.align-items-center", - { hasText: /.*s ago/ } - ); - await expect(lastLogItem).toContainText( - "moved proposal from REVIEW to APPROVED", - { timeout: 10000 } - ); - await lastLogItem.scrollIntoViewIfNeeded(); - await expect(timeLineStatusSubmittedToast).not.toBeAttached(); - await pauseIfVideoRecording(page); + + if (!isInfrastructureCommittee) { + const lastLogItem = await page.locator( + "div.flex-1.gap-1.w-100.text-wrap.text-muted.align-items-center", + { hasText: /.*s ago/ } + ); + await expect(lastLogItem).toContainText( + "moved proposal from REVIEW to APPROVED", + { timeout: 10000 } + ); + await lastLogItem.scrollIntoViewIfNeeded(); + await expect(timeLineStatusSubmittedToast).not.toBeAttached(); + await pauseIfVideoRecording(page); + } }); test("should not be able to move proposal timeline to decision stage without approving KYC in review stage", async ({ @@ -383,6 +438,9 @@ test.describe('Moderator with "Don\'t ask again" enabled', () => { }) => { test.setTimeout(60000); + const isInfrastructureCommittee = + account === "infrastructure-committee.near"; + await mockRpcRequest({ page, filterParams: { @@ -393,7 +451,11 @@ test.describe('Moderator with "Don\'t ask again" enabled', () => { return originalResult; }, }); - await page.goto(`/${account}/widget/app?page=proposal&id=17`); + await page.goto( + `/${account}/widget/app?page=proposal&id=${ + isInfrastructureCommittee ? "4" : "17" + }` + ); const firstStatusBadge = await page .locator("div.fw-bold.rounded-2.p-1.px-2") @@ -401,13 +463,22 @@ test.describe('Moderator with "Don\'t ask again" enabled', () => { await expect(firstStatusBadge).toHaveText("REVIEW", { timeout: 10000 }); await page.locator(".d-flex > div > .bi").click(); - await page.getByTestId("Sponsor verifies KYC/KYB").uncheck(); - await page.getByRole("button", { name: "Review", exact: true }).click(); - await page.getByText("Approved", { exact: true }).first().click(); + const sponsorCheckbox = await page + .locator("label") + .filter({ hasText: "Sponsor verifies KYC/KYB" }) + .locator("xpath=preceding-sibling::*[1]"); + if (isInfrastructureCommittee) { + await expect(sponsorCheckbox).toBeDisabled(); + } else { + await sponsorCheckbox.uncheck(); - const saveButton = await page.getByRole("button", { name: "Save" }); - await saveButton.scrollIntoViewIfNeeded(); - await expect(saveButton).toBeDisabled(); + await page.getByRole("button", { name: "Review", exact: true }).click(); + await page.getByText("Approved", { exact: true }).first().click(); + + const saveButton = await page.getByRole("button", { name: "Save" }); + await saveButton.scrollIntoViewIfNeeded(); + await expect(saveButton).toBeDisabled(); + } }); }); @@ -425,20 +496,23 @@ test.describe("Wallet is connected", () => { const delay_milliseconds_between_keypress_when_typing = 0; const titleArea = await page.getByRole("textbox").first(); - await expect(titleArea).toBeEditable(); + await expect(titleArea).toBeEditable({ timeout: 10_000 }); await titleArea.pressSequentially("Test proposal 123456", { delay: delay_milliseconds_between_keypress_when_typing, }); await pauseIfVideoRecording(page); - const categoryDropdown = await page.locator(".dropdown-toggle").first(); - await categoryDropdown.click(); - await page.locator(".dropdown-menu > div > div:nth-child(2) > div").click(); + const categoryDropdown = await page.locator(".dropdown-toggle", { + hasText: "Select Category", + }); - const disabledSubmitButton = await page.locator( - ".submit-draft-button.disabled" + await categoryDropdown.click(); + const categoryItem = await page.locator( + ".dropdown-menu > div > div:nth-child(2) > div" ); + const selectedCategory = (await categoryItem.innerText()).split("\n")[0]; + await categoryItem.click(); const summary = await page.locator('textarea[type="text"]'); await expect(summary).toBeEditable(); @@ -485,195 +559,19 @@ test.describe("Wallet is connected", () => { delay: delay_milliseconds_between_keypress_when_typing, }); await pauseIfVideoRecording(page); - await page.getByRole("checkbox").first().click(); - await pauseIfVideoRecording(page); - await expect(disabledSubmitButton).toBeAttached(); - await page.getByRole("checkbox").nth(1).click(); - await pauseIfVideoRecording(page); - await expect(disabledSubmitButton).not.toBeAttached(); - const submitButton = await page.getByText("Submit Draft"); - await submitButton.scrollIntoViewIfNeeded(); - await submitButton.hover(); + const consentCheckBoxes = await page.getByRole("checkbox"); + await consentCheckBoxes.first().click(); await pauseIfVideoRecording(page); - await submitButton.click(); - const transactionText = JSON.stringify( - JSON.parse(await page.locator("div.modal-body code").innerText()), - null, - 1 - ); - expect(transactionText).toEqual( - JSON.stringify( - { - labels: [], - body: { - proposal_body_version: "V0", - name: "Test proposal 123456", - description: - "The test proposal description. And mentioning @petersalomonsen.near. Also mentioning @megha19.near", - category: "DevDAO Platform", - summary: "Test proposal summary 123456789", - linked_proposals: [], - requested_sponsorship_usd_amount: "12345", - requested_sponsorship_paid_in_currency: "USDC", - receiver_account: "efiz.near", - supervisor: null, - requested_sponsor: "neardevdao.near", - timeline: { - status: "DRAFT", - }, - }, - accepted_terms_and_conditions_version: acceptedTermsVersion, - }, - null, - 1 - ) - ); - - await pauseIfVideoRecording(page); - }); - - test("should show relevant users in mention autocomplete", async ({ - page, - account, - }) => { - await page.goto(`/${account}/widget/app?page=proposal&id=112`); - - await page.waitForSelector(`iframe`, { - state: "visible", - }); - - const comment = page.getByRole("link", { name: "geforcy.near" }); - await comment.scrollIntoViewIfNeeded(); - await expect(comment).toBeVisible(); - await page.waitForTimeout(5000); - - const delay_milliseconds_between_keypress_when_typing = 0; - const commentEditor = page - .frameLocator("iframe") - .last() - .locator(".CodeMirror textarea"); - await commentEditor.focus(); - await commentEditor.pressSequentially( - `Make sure relevant users show up in a mention. @`, - { - delay: delay_milliseconds_between_keypress_when_typing, - } - ); - - await pauseIfVideoRecording(page); - const iframe = page.frameLocator("iframe").last(); - const liFrameLocators = iframe.frameLocator( - 'ul[id="mentiondropdown"] > li' - ); - const liLocators = await liFrameLocators.owner().all(); - const expected = [ - "thomasguntenaar.near", // author, - "theori.near", // supervisor, - "neardevdao.near", // requested_sponsor, - "geforcy.near", // comment author, - ]; - let mentionSuggestions = []; - for (let i = 0; i < liLocators.length; i++) { - const text = await liLocators[i].innerText(); - mentionSuggestions.push(text); - } - - // When I manually test, it shows the correct 4 users - expect(mentionSuggestions.slice(0, 4)).toEqual(expected); - await pauseIfVideoRecording(page); - }); - - test("should show only valid input in amount field and show error for invalid", async ({ - page, - account, - }) => { - test.setTimeout(120000); - const delay_milliseconds_between_keypress_when_typing = 0; - await page.goto(`/${account}/widget/app?page=create-proposal`); - const input = page.locator('input[type="text"]').nth(2); - const errorText = await page.getByText( - "Please enter the nearest positive whole number." - ); - await input.pressSequentially("12345de", { - delay: delay_milliseconds_between_keypress_when_typing, - }); - await expect(errorText).toBeVisible(); - // clear input field - for (let i = 0; i < 7; i++) { - await input.press("Backspace", { - delay: delay_milliseconds_between_keypress_when_typing, - }); - } - await input.pressSequentially("12334", { - delay: delay_milliseconds_between_keypress_when_typing, - }); - await expect(errorText).toBeHidden(); - await pauseIfVideoRecording(page); - }); - - test("should create a proposal, autolink reference to existing proposal", async ({ - page, - account, - }) => { - test.setTimeout(120000); - await getCurrentBlockHeight(page); - await page.goto(`/${account}/widget/app?page=create-proposal`); - - const delay_milliseconds_between_keypress_when_typing = 0; - const titleArea = await page.getByRole("textbox").first(); - await expect(titleArea).toBeEditable({ timeout: 10_000 }); - await titleArea.pressSequentially("Test proposal 123456", { - delay: delay_milliseconds_between_keypress_when_typing, - }); - - await pauseIfVideoRecording(page); - - const categoryDropdown = await page.locator(".dropdown-toggle").first(); - await categoryDropdown.click(); - await page.locator(".dropdown-menu > div > div:nth-child(2) > div").click(); const disabledSubmitButton = await page.locator( ".submit-draft-button.disabled" ); - - const summary = await page.locator('textarea[type="text"]'); - await expect(summary).toBeEditable(); - await summary.pressSequentially("Test proposal summary 123456789", { - delay: delay_milliseconds_between_keypress_when_typing, - }); - - await pauseIfVideoRecording(page); - - const descriptionArea = await page - .frameLocator("iframe") - .locator(".CodeMirror textarea"); - await descriptionArea.focus(); - await descriptionArea.pressSequentially( - `The test proposal description. And referencing #`, - { - delay: delay_milliseconds_between_keypress_when_typing, - } - ); - await descriptionArea.pressSequentially("2", { delay: 10 }); - await pauseIfVideoRecording(page); - - await page - .frameLocator("iframe") - .getByText( - "#2 DevHub Developer Contributor report by Thomas for 03/11/2024 – 04/12/2024" - ) - .click({ timeout: 10000 }); - - await page.locator('input[type="text"]').nth(2).pressSequentially("12345", { - delay: delay_milliseconds_between_keypress_when_typing, - }); - await pauseIfVideoRecording(page); - await page.getByRole("checkbox").first().click(); - await pauseIfVideoRecording(page); - await expect(disabledSubmitButton).toBeAttached(); - await page.getByRole("checkbox").nth(1).click(); - await pauseIfVideoRecording(page); + if ((await consentCheckBoxes.count()) === 2) { + await expect(disabledSubmitButton).toBeAttached(); + await page.getByRole("checkbox").nth(1).click(); + await pauseIfVideoRecording(page); + } await expect(disabledSubmitButton).not.toBeAttached(); const submitButton = await page.getByText("Submit Draft"); @@ -681,104 +579,47 @@ test.describe("Wallet is connected", () => { await submitButton.hover(); await pauseIfVideoRecording(page); await submitButton.click(); - const transactionText = JSON.stringify( - JSON.parse(await page.locator("div.modal-body code").innerText()), - null, - 1 + const transactionObject = JSON.parse( + await page.locator("div.modal-body code").innerText() ); - await expect(transactionText).toEqual( - JSON.stringify( - { - labels: [], - body: { - proposal_body_version: "V0", - name: "Test proposal 123456", - description: - "The test proposal description. And referencing [#2 DevHub Developer Contributor report by Thomas for 03/11/2024 – 04/12/2024](https://near.social/devhub.near/widget/app?page=proposal&id=2)", - category: "DevDAO Platform", - summary: "Test proposal summary 123456789", - linked_proposals: [], - requested_sponsorship_usd_amount: "12345", - requested_sponsorship_paid_in_currency: "USDC", - receiver_account: "efiz.near", - supervisor: null, - requested_sponsor: "neardevdao.near", - timeline: { - status: "DRAFT", - }, - }, - accepted_terms_and_conditions_version: acceptedTermsVersion, + const expectedTransactionObject = { + labels: [ + "events-committee.near", + "infrastructure-committee.near", + ].includes(account) + ? [selectedCategory] + : [], + body: { + proposal_body_version: + account === "infrastructure-committee.near" ? "V1" : "V0", + name: "Test proposal 123456", + description: + "The test proposal description. And mentioning @petersalomonsen.near. Also mentioning @megha19.near", + category: + account === "events-committee.near" + ? "Bounty" + : account === "infrastructure-committee.near" + ? "Infrastructure Committee" + : selectedCategory, + summary: "Test proposal summary 123456789", + linked_proposals: [], + requested_sponsorship_usd_amount: "12345", + requested_sponsorship_paid_in_currency: "USDC", + receiver_account: "efiz.near", + supervisor: null, + requested_sponsor: + account === "devhub.near" ? "neardevdao.near" : account, + timeline: { + status: "DRAFT", }, - null, - 1 - ) - ); + }, + }; + if (account !== "infrastructure-committee.near") { + expectedTransactionObject.accepted_terms_and_conditions_version = + acceptedTermsVersion; + } + expect(transactionObject).toStrictEqual(expectedTransactionObject); await pauseIfVideoRecording(page); }); - - test.describe("filter proposals using different mechanism", () => { - test.beforeEach(async ({ page, account }) => { - await page.goto(`/${account}/widget/app?page=proposals`); - expect(page.locator(".proposal-card").first()).toBeVisible({ - timeout: 10000, - }); - }); - test("should filter proposals by categories", async ({ page, account }) => { - test.setTimeout(60000); - const category = "DevDAO Operations"; - await page.getByRole("button", { name: "Category" }).click(); - await page.getByRole("list").getByText(category).click(); - await expect( - page.getByRole("button", { name: `Category : ${category}` }) - ).toBeVisible(); - const loader = page.getByRole("img", { name: "loader" }); - expect(loader).toBeHidden({ timeout: 10000 }); - const categoryTag = await page.locator(".purple-bg").first(); - await expect(categoryTag).toContainText(category); - }); - - test("should filter proposals by timeline", async ({ page }) => { - test.setTimeout(60000); - const stage = "Funded"; - await page.getByRole("button", { name: "Stage" }).click(); - await page.getByRole("list").getByText(stage).click(); - await expect( - page.getByRole("button", { name: `Stage : ${stage}` }) - ).toBeVisible(); - const loader = page.getByRole("img", { name: "loader" }); - expect(loader).toBeHidden({ timeout: 10000 }); - const timelineTag = await page.locator(".green-tag").first(); - await expect(timelineTag).toContainText(stage.toUpperCase()); - }); - - test("should filter proposals by author", async ({ page }) => { - test.setTimeout(60000); - const accountId = "megha19.near"; - const profileName = "Megha"; - await page.getByRole("button", { name: "Author" }).click(); - await page.getByRole("list").getByText(accountId).click(); - await expect( - page.getByRole("button", { name: `Author : ${accountId}` }) - ).toBeVisible(); - const loader = page.getByRole("img", { name: "loader" }); - expect(loader).toBeHidden({ timeout: 10000 }); - await expect(page.getByText(`By ${profileName} ・`).first()).toBeVisible({ - timeout: 10_000, - }); - }); - - test("should filter proposals by search text", async ({ page }) => { - test.setTimeout(60000); - const term = "DevHub Developer Contributor report by Megha"; - const input = await page.getByPlaceholder("Search by content"); - await input.click(); - await input.fill(term); - await input.press("Enter"); - const loader = page.getByRole("img", { name: "loader" }); - expect(loader).toBeHidden({ timeout: 10000 }); - const element = page.locator(`:has-text("${term}")`).nth(1); - await expect(element).toBeVisible({ timeout: 10_000 }); - }); - }); }); diff --git a/playwright-tests/util/transaction.js b/playwright-tests/util/transaction.js index 4fc1e14ee..b323dc27b 100644 --- a/playwright-tests/util/transaction.js +++ b/playwright-tests/util/transaction.js @@ -66,6 +66,19 @@ const access_keys = [ }, public_key: "ed25519:H8bRwTywvrCYbGuJtBuCAaZLjBBgAPKPAZKrWjA8Pj17", }, + { + access_key: { + nonce: 109629226000005, + permission: { + FunctionCall: { + allowance: "241917078840755500000000", + method_names: [], + receiver_id: "infrastructure-committee.near", + }, + }, + }, + public_key: "ed25519:G6PXTFq5xNvyYL4LttWVvHo7srmfGuTumX5W7JyXV21P", + }, ]; export function decodeResultJSON(resultArray) { diff --git a/playwright.config.js b/playwright.config.js index e3bfc199a..01a6ee567 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -48,6 +48,7 @@ export default defineConfig({ projects: [ { name: "infrastructure", + testMatch: /(infrastructure|proposal)\/.*.spec.js/, use: { ...devices["Desktop Chrome"], account: "infrastructure-committee.near",