From faa49282d20177b0b57d6830f613d3325cfc3db9 Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Fri, 24 May 2024 14:56:55 +0545 Subject: [PATCH 01/49] WIP Update stress tests --- .../govtool/actions/AdaHolderAction.java | 31 +++++++- .../govtool/actions/AuthenticationAction.java | 3 +- .../govtool/simulations/VvaSimulation.java | 73 ++++++++++--------- 3 files changed, 71 insertions(+), 36 deletions(-) diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/actions/AdaHolderAction.java b/tests/load-testing/src/test/java/org/cardano/govtool/actions/AdaHolderAction.java index 437202bcf..03007af17 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/actions/AdaHolderAction.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/actions/AdaHolderAction.java @@ -1,11 +1,17 @@ package org.cardano.govtool.actions; +import static io.gatling.javaapi.http.HttpDsl.http; +import io.gatling.javaapi.core.ScenarioBuilder; import org.cardano.govtool.ApiService; -import org.cardano.govtool.configs.HeaderConfig; import io.gatling.javaapi.core.ChainBuilder; import org.cardano.govtool.feeders.RandomDataFeeder; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + import static io.gatling.javaapi.core.CoreDsl.*; +import static io.gatling.javaapi.http.HttpDsl.status; public class AdaHolderAction { public static ChainBuilder delegateToDRep = group("Delegation").on(exec(ApiService.getAllDReps) @@ -16,4 +22,27 @@ public class AdaHolderAction { .exec(ApiService.pollTxStatus) .feed(RandomDataFeeder.stakeKey) .exec(ApiService.getCurrentDelegation)); + + + /** + * E + * + */ + public static ChainBuilder exploreDrepsChain = exec(http("get_dReps").get("/drep/list?page=0&size=10") + .check(jmesPath("elements[].drepId").ofList().transform(dreps ->{ + Collections.shuffle(dreps); // Shuffle the list to randomize the order + int visited_dreps = ThreadLocalRandom.current().nextInt(2, 7); + return dreps.subList(0, Math.min(visited_dreps, dreps.size())); + }).exists().saveAs("drepIds")) + ) + .pause(2) + .foreach("#{drepIds}", "drepId").on( + exec( session -> { + http("get_dRepDetails").get("/drep/info/#{drepId}") + .check(status().is(200)); + return session; + } + ).pause(ThreadLocalRandom.current().nextLong(1,4)) + ); } + diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/actions/AuthenticationAction.java b/tests/load-testing/src/test/java/org/cardano/govtool/actions/AuthenticationAction.java index fb46e53e4..85c5bb0ab 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/actions/AuthenticationAction.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/actions/AuthenticationAction.java @@ -11,8 +11,7 @@ public class AuthenticationAction { public static ChainBuilder connect = group("Login") - .on(exec(ApiService.getAllDReps) - .feed(RandomDataFeeder.stakeKey) + .on(feed(RandomDataFeeder.stakeKey) .exec(ApiService.getCurrentDelegation) .exec(ApiService.getParams) .feed(RandomDataFeeder.dRepId) diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java b/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java index ff61cf533..3594be597 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java @@ -8,10 +8,11 @@ import static io.gatling.javaapi.core.CoreDsl.*; import static io.gatling.javaapi.http.HttpDsl.http; +import static org.cardano.govtool.actions.AdaHolderAction.drepListDelegateScenario; public class VvaSimulation extends Simulation { - private static final String API_URL = Optional.ofNullable(System.getenv("API_URL")).orElse("http://localhost:3000/api"); + private static final String API_URL = Optional.ofNullable(System.getenv("API_URL")).orElse("https://govtool.cardanoapi.io/api"); private static final int TARGET_USER_RATE = Integer.parseInt(Optional.ofNullable(System.getenv("TARGET_USER_RATE")).orElse("5")); private static final int PEAK_USERS = Integer.parseInt(Optional.ofNullable(System.getenv("PEAK_USERS")).orElse("10")); private static final int STRESS_DURATION = Integer.parseInt(Optional.ofNullable(System.getenv("STRESS_DURATION")).orElse("10")); @@ -37,42 +38,48 @@ public void before() { // Load Simulation { setUp( - Scenario.userConnectAndLeave.injectOpen( - nothingFor(5), - rampUsersPerSec(1).to(TARGET_USER_RATE * 0.2).during(RAMP_DURATION), - stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) - ), - Scenario.userRegisterAsDRep.injectOpen( +// Scenario.userConnectAndLeave.injectOpen( +// nothingFor(5), +// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.2).during(RAMP_DURATION), +// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) +// ) + + drepListDelegateScenario.injectOpen( nothingFor(5), rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3).during(RAMP_DURATION), stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) - ), - Scenario.userOnlyViewsProposal.injectOpen( - nothingFor(5), - rampUsersPerSec(1).to(TARGET_USER_RATE * 0.1).during(RAMP_DURATION), - stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) - ), - Scenario.adaHolderDelegateToDRep.injectOpen( - nothingFor(5), - rampUsersPerSec(1).to(TARGET_USER_RATE * 0.4).during(RAMP_DURATION), - stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) - ), - // Further DRep scenarios - Scenario.dRepVoteOnProposal.injectOpen( - nothingFor(5), - rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3 * 0.4).during(RAMP_DURATION), - stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) - ), - Scenario.dRepViewVotes.injectOpen( - nothingFor(5), - rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3 * 0.5).during(RAMP_DURATION), - stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) - ), - Scenario.dRepRetires.injectOpen( - nothingFor(5), - rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3 * 0.1).during(RAMP_DURATION), - stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) ) +// Scenario.userRegisterAsDRep.injectOpen( +// nothingFor(5), +// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3).during(RAMP_DURATION), +// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) +// ), +// Scenario.userOnlyViewsProposal.injectOpen( +// nothingFor(5), +// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.1).during(RAMP_DURATION), +// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) +// ), +// Scenario.adaHolderDelegateToDRep.injectOpen( +// nothingFor(5), +// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.4).during(RAMP_DURATION), +// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) +// ), +// // Further DRep scenarios +// Scenario.dRepVoteOnProposal.injectOpen( +// nothingFor(5), +// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3 * 0.4).during(RAMP_DURATION), +// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) +// ), +// Scenario.dRepViewVotes.injectOpen( +// nothingFor(5), +// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3 * 0.5).during(RAMP_DURATION), +// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) +// ), +// Scenario.dRepRetires.injectOpen( +// nothingFor(5), +// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3 * 0.1).during(RAMP_DURATION), +// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) +// ) ).protocols(httpProtocol); } From f69a78d78bdbcce33e6b46cb5b7849df0b04ccb2 Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Mon, 3 Jun 2024 15:17:00 +0545 Subject: [PATCH 02/49] Fetch Drep and Proposal Ids for seeding --- tests/load-testing/README.md | 26 +++--- .../java/org/cardano/govtool/ApiService.java | 23 ++--- .../java/org/cardano/govtool/Scenario.java | 53 ----------- .../org/cardano/govtool/actions/Action.java | 4 +- .../govtool/actions/AdaHolderAction.java | 53 ++++++----- .../govtool/actions/AuthenticationAction.java | 32 +++++-- .../cardano/govtool/actions/DRepAction.java | 14 +-- .../govtool/feeders/DrepListFetcher.java | 47 ++++++++++ .../cardano/govtool/feeders/PageVisits.java | 81 ++++++++++++++++ .../govtool/simulations/VvaSimulation.java | 93 ++++++++----------- 10 files changed, 258 insertions(+), 168 deletions(-) delete mode 100644 tests/load-testing/src/test/java/org/cardano/govtool/Scenario.java create mode 100644 tests/load-testing/src/test/java/org/cardano/govtool/feeders/DrepListFetcher.java create mode 100644 tests/load-testing/src/test/java/org/cardano/govtool/feeders/PageVisits.java diff --git a/tests/load-testing/README.md b/tests/load-testing/README.md index 8bbc1cca1..dd81fc547 100644 --- a/tests/load-testing/README.md +++ b/tests/load-testing/README.md @@ -17,25 +17,29 @@ Before you start, ensure you have the following prerequisites installed: ## Manual Run ```bash -export TARGET_USER_RATE= RAMP_DURATION= PEAK_USERS= STRESS_DURATION= API_URL=; ./mvnw gatling:test +export API_URL=https://govtool.cardanoapi.io/api +export PEAK_USERS=100 +export RAMP_DURATION=40 # in seconds +export STRESS_DURATION=40 # in seconds +./mvnw gatling:test ``` -## Docker Build and Run +## Run stress test with docker -1. Build the Docker image: ```bash -docker build -t load-testing . -``` -2. Run the Docker container: -```bash -docker run -e TARGET_USER_RATE= -e RAMP_DURATION= -e PEAK_USERS= -e STRESS_DURATION= -e API_URL= load-testing +docker build -t govtool/load-testing . # build the image +docker run \ + -e RAMP_DURATION=40 \ + -e PEAK_USERS=100 \ + -e STRESS_DURATION=40 \ + -e API_URL='https://govtool.cardanoapi.io/api'\ + govtool/load-testing ``` ## Environment Variables Explain the environment variables used in the project and their purpose. -- TARGET_USER_RATE: The target rate of users per second during the test. +- API_URL: The URL of the API being tested. +- PEAK_USERS: The number of users to be injected during the test for each scenario. - RAMP_DURATION:The duration over which the user rate gradually increases. -- PEAK_USERS: The number of users to be injected during the stress peak. - STRESS_DURATION: The duration over which the stress peak occurs. -- API_URL: The URL of the API being tested. diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/ApiService.java b/tests/load-testing/src/test/java/org/cardano/govtool/ApiService.java index 20fed22a4..7caa530a1 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/ApiService.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/ApiService.java @@ -4,32 +4,33 @@ import io.gatling.javaapi.core.ChainBuilder; import io.gatling.javaapi.http.HttpRequestActionBuilder; +import java.util.concurrent.ThreadLocalRandom; + import static io.gatling.javaapi.core.CoreDsl.*; import static io.gatling.javaapi.http.HttpDsl.http; -import static io.gatling.javaapi.http.internal.HttpCheckBuilders.status; public class ApiService { - + static String example_metadata_validate="{\"url\":\"https://metadata-govtool.cardanoapi.io/data/milanfile\",\"hash\":\"dcc25ca74534a1fa3b4c86447c1e28dd86e103e1ead5e5308b64b34338af00a9\"}"; + public static ChainBuilder validate_sample_metadata= exec(http("validate_metadata").post("/metadata/validate").body(StringBody(example_metadata_validate)).header("content-type","application/json")); public static ChainBuilder getCurrentDelegation = exec(http("get_current_delegation").get("/ada-holder/get-current-delegation/#{stakeKey}")); public static HttpRequestActionBuilder getAdaHolderVotingPower = http("get_AdaHolder_voting_power").get("/ada-holder/get-voting-power/#{stakeKey}"); - public static HttpRequestActionBuilder getDRepVotingPower = http("get_DRep_voting_power").get("/drep/get-voting-power/#{dRepId}"); + public static HttpRequestActionBuilder getDRepVotingPower = http("get_dRep_voting_power").get("/drep/get-voting-power/#{dRepId}"); + public static HttpRequestActionBuilder getDrepInfo = http("get_dRep_info").get("/drep/info/#{stakeKey}"); public static ChainBuilder getVotes = exec(http("get_votes").get("/drep/getVotes/#{dRepId}")); - public static ChainBuilder getAllDReps = exec(http("get_dReps").get("/drep/list")); + public static ChainBuilder getAllDReps = exec(http("get_dReps").get("/drep/list?page=0&size=10")); public static ChainBuilder getParams = exec(http("get_epoch_params").get("/epoch/params")); - public static ChainBuilder getProposal = exec(http("get_proposal_detail").get("/proposal/get/#{proposalId}").check(status().is(404))); - - public static ChainBuilder getAllProposals = exec(http("get_proposals").get("/proposal/list")); - public static ChainBuilder getTxStatus = exec(http("get_tx_status").get("/transaction/status/#{txId}")); - //TODO randomize the number of repeats, Range 2 to 6 - public static ChainBuilder pollTxStatus = repeat(5).on( - exec(http("get_tx_status").get("/transaction/status/#{txId}")).pause(4)); + public static ChainBuilder getMetrics = exec(http("get_metrics").get("/network/metrics")); + + public static ChainBuilder pollTxStatus = repeat(session -> ThreadLocalRandom.current() + .nextInt(3,6)).on( + exec(http("get_tx_status").get("/transaction/status/#{txId}")).pause(4)); } diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/Scenario.java b/tests/load-testing/src/test/java/org/cardano/govtool/Scenario.java deleted file mode 100644 index 32fc360ab..000000000 --- a/tests/load-testing/src/test/java/org/cardano/govtool/Scenario.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.cardano.govtool; - -import io.gatling.javaapi.core.ScenarioBuilder; -import org.cardano.govtool.actions.Action; -import org.cardano.govtool.actions.AdaHolderAction; -import org.cardano.govtool.actions.AuthenticationAction; -import org.cardano.govtool.actions.DRepAction; - -import static io.gatling.javaapi.core.CoreDsl.scenario; - -public class Scenario { - // User connects and leave - public static final ScenarioBuilder userConnectAndLeave = scenario("User connect and leave") - .exec(AuthenticationAction.connect); - - // User register as DRep - public static final ScenarioBuilder userRegisterAsDRep = scenario("User registers as DRep") - .exec(AuthenticationAction.connect) - .pause(2) - .exec(DRepAction.registerAsDRep); - - // User views proposal - public static final ScenarioBuilder userOnlyViewsProposal = scenario("User views proposal") - .exec(AuthenticationAction.connect) - .pause(2) - .exec(Action.viewProposals); - - // DRep votes on proposal - public static final ScenarioBuilder dRepVoteOnProposal = scenario("DRep vote on proposal") - .exec(AuthenticationAction.connect) - .pause(2) - .exec(DRepAction.vote); - - // DRep view votes - public static final ScenarioBuilder dRepViewVotes = scenario("DRep view votes") - .exec(AuthenticationAction.connect) - .pause(2) - .exec(Action.viewProposals) - .pause(2) - .exec(Action.viewVotes); - - // User retire as DRep - public static final ScenarioBuilder dRepRetires = scenario("DRep retirement") - .exec(AuthenticationAction.connect) - .pause(2) - .exec(DRepAction.retireAsDRep); - - // Ada holder delegate to DRep - public static final ScenarioBuilder adaHolderDelegateToDRep = scenario("DRep delegation") - .exec(AuthenticationAction.connect) - .pause(2) - .exec(AdaHolderAction.delegateToDRep); -} diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/actions/Action.java b/tests/load-testing/src/test/java/org/cardano/govtool/actions/Action.java index a29f798fe..47760a9a2 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/actions/Action.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/actions/Action.java @@ -2,13 +2,13 @@ import io.gatling.javaapi.core.ChainBuilder; import org.cardano.govtool.ApiService; +import org.cardano.govtool.feeders.PageVisits; import static io.gatling.javaapi.core.CoreDsl.*; // Common Actions public class Action { - public static ChainBuilder viewProposals = group("View Proposals").on(exec(ApiService.getAllProposals) + public static ChainBuilder viewProposals = group("View Proposals").on(exec(PageVisits.visitProposalPage()) .exec(ApiService.getVotes)); - public static ChainBuilder viewVotes = exec(ApiService.getVotes); } diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/actions/AdaHolderAction.java b/tests/load-testing/src/test/java/org/cardano/govtool/actions/AdaHolderAction.java index 03007af17..06ef1e1ea 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/actions/AdaHolderAction.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/actions/AdaHolderAction.java @@ -1,48 +1,55 @@ package org.cardano.govtool.actions; import static io.gatling.javaapi.http.HttpDsl.http; -import io.gatling.javaapi.core.ScenarioBuilder; import org.cardano.govtool.ApiService; import io.gatling.javaapi.core.ChainBuilder; import org.cardano.govtool.feeders.RandomDataFeeder; import java.util.Collections; -import java.util.List; import java.util.concurrent.ThreadLocalRandom; import static io.gatling.javaapi.core.CoreDsl.*; import static io.gatling.javaapi.http.HttpDsl.status; +import static org.cardano.govtool.feeders.PageVisits.listAndSelectDreps; public class AdaHolderAction { - public static ChainBuilder delegateToDRep = group("Delegation").on(exec(ApiService.getAllDReps) + + + public static ChainBuilder delegateToDRep = group("Delegation").on( + feed(RandomDataFeeder.stakeKey) + ) + .exec(adaHolderBasicApis()) + .exec( AdaHolderAction.exploreDrepsChain().exec(adaHolderBasicApis())) .pause(6) + .feed(RandomDataFeeder.txId) - .exec(ApiService.getTxStatus) - .pause(4) - .exec(ApiService.pollTxStatus) - .feed(RandomDataFeeder.stakeKey) - .exec(ApiService.getCurrentDelegation)); + .repeat(6).on(ApiService.getTxStatus.pause(4)) + .exec(adaHolderBasicApis()); + + public static ChainBuilder adaHolderBasicApis(){ + return exec(ApiService.getDrepInfo) + .exec(ApiService.getAdaHolderVotingPower) + .exec(ApiService.getMetrics) + .exec(ApiService.getCurrentDelegation); + } /** - * E * */ - public static ChainBuilder exploreDrepsChain = exec(http("get_dReps").get("/drep/list?page=0&size=10") - .check(jmesPath("elements[].drepId").ofList().transform(dreps ->{ - Collections.shuffle(dreps); // Shuffle the list to randomize the order - int visited_dreps = ThreadLocalRandom.current().nextInt(2, 7); - return dreps.subList(0, Math.min(visited_dreps, dreps.size())); - }).exists().saveAs("drepIds")) - ) - .pause(2) + public static ChainBuilder exploreDrepsChain () { + return exec(adaHolderBasicApis().exec(listAndSelectDreps(2,5))) + .pause(2).exitHereIf(session -> { + return session.get("drepIds") == null; + }) .foreach("#{drepIds}", "drepId").on( - exec( session -> { - http("get_dRepDetails").get("/drep/info/#{drepId}") - .check(status().is(200)); - return session; - } - ).pause(ThreadLocalRandom.current().nextLong(1,4)) + exec( + http("get_dRepDetails").get("/drep/info/#{drepId}") + .check(status().is(200)) + ).exec(adaHolderBasicApis().exec(ApiService.getAllDReps)) + .pause(ThreadLocalRandom.current().nextLong(1,4)) ); + + } } diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/actions/AuthenticationAction.java b/tests/load-testing/src/test/java/org/cardano/govtool/actions/AuthenticationAction.java index 85c5bb0ab..37b31e49a 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/actions/AuthenticationAction.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/actions/AuthenticationAction.java @@ -4,17 +4,31 @@ import org.cardano.govtool.ApiService; import org.cardano.govtool.feeders.RandomDataFeeder; -import java.util.UUID; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; +import java.util.stream.Stream; import static io.gatling.javaapi.core.CoreDsl.*; -import static io.gatling.javaapi.http.HttpDsl.poll; public class AuthenticationAction { - public static ChainBuilder connect = group("Login") - .on(feed(RandomDataFeeder.stakeKey) - .exec(ApiService.getCurrentDelegation) - .exec(ApiService.getParams) - .feed(RandomDataFeeder.dRepId) - .exec(ApiService.getDRepVotingPower) - .exec(ApiService.getAdaHolderVotingPower)); + public static ChainBuilder connect (List knownDrepIds) { + + var drepStream = Stream.generate((Supplier>) () -> { + var randomIndex=ThreadLocalRandom.current().nextInt(0,knownDrepIds.size()); + var dRepKey = knownDrepIds.get(randomIndex); + return Collections.singletonMap("dRepId", dRepKey); + } + ).iterator(); + + return group("Login") + .on(feed(RandomDataFeeder.stakeKey) + .feed(drepStream) + .exec(ApiService.getCurrentDelegation) + .exec(ApiService.getParams) + .exec(ApiService.getDRepVotingPower) + .exec(ApiService.getAdaHolderVotingPower)); + } } diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/actions/DRepAction.java b/tests/load-testing/src/test/java/org/cardano/govtool/actions/DRepAction.java index 1860f6130..52f2fd88a 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/actions/DRepAction.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/actions/DRepAction.java @@ -5,35 +5,35 @@ import io.gatling.javaapi.core.ChainBuilder; import static io.gatling.javaapi.core.CoreDsl.*; -import static io.gatling.javaapi.http.HttpDsl.http; +import static org.cardano.govtool.feeders.PageVisits.visitProposalPage; public class DRepAction { public static ChainBuilder registerAsDRep = group("RegisterAsDRep").on( feed(RandomDataFeeder.txId).exec(ApiService.getTxStatus) + .exec(ApiService.validate_sample_metadata) .pause(4) .exec(ApiService.pollTxStatus) .exec(ApiService.getAllDReps) ); + public static ChainBuilder retireAsDRep = group("RetireAsDRep").on( feed(RandomDataFeeder.txId).exec(ApiService.getTxStatus) .pause(4) .exec(ApiService.pollTxStatus) .exec(ApiService.getAllDReps)); - public static ChainBuilder vote = group("Voting").on( feed(RandomDataFeeder.txId) .feed(RandomDataFeeder.dRepId) .feed(RandomDataFeeder.proposalId) - .exec(ApiService.getAllProposals) - .exec(ApiService.getVotes) + .exec(visitProposalPage()) .pause(4) - .exec(ApiService.getProposal) + // viewing propsals refetches the entire proposal list + .exec(repeat(3).on(visitProposalPage())) .pause(4) .exec(ApiService.getTxStatus) .pause(4) .exec(ApiService.pollTxStatus) - .exec(ApiService.getAllProposals) - .exec(ApiService.getVotes)); + .exec(visitProposalPage())); } diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/feeders/DrepListFetcher.java b/tests/load-testing/src/test/java/org/cardano/govtool/feeders/DrepListFetcher.java new file mode 100644 index 000000000..ae85fe931 --- /dev/null +++ b/tests/load-testing/src/test/java/org/cardano/govtool/feeders/DrepListFetcher.java @@ -0,0 +1,47 @@ +package org.cardano.govtool.feeders; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class DrepListFetcher { + + private static final int PAGE_SIZE = 20; + + public static List fetchDrepIds(String baseUrl) { + baseUrl = baseUrl.endsWith("/")?baseUrl : baseUrl + "/"; + try { + HttpClient client = HttpClient.newHttpClient(); + List allIds = new ArrayList<>(); + ObjectMapper mapper = new ObjectMapper(); + + // Fetch about 5 pages + for (int page = 0; page < 5; page++) { + String requestUrl = baseUrl + "/drep/list" + "?page=" + page + "&size=" + PAGE_SIZE; + HttpRequest request = HttpRequest.newBuilder() + .uri(new URI(requestUrl)) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + JsonNode rootNode = mapper.readTree(response.body()).get("elements"); + + // select the drepId field + List idList = rootNode.findValues("drepId").stream() + .map(JsonNode::asText) + .toList(); + allIds.addAll(idList); + } + return allIds; + + } catch (Exception e) { + throw new RuntimeException("Failed to fetch available dreps"); + } + } +} diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/feeders/PageVisits.java b/tests/load-testing/src/test/java/org/cardano/govtool/feeders/PageVisits.java new file mode 100644 index 000000000..ec3afa34f --- /dev/null +++ b/tests/load-testing/src/test/java/org/cardano/govtool/feeders/PageVisits.java @@ -0,0 +1,81 @@ +package org.cardano.govtool.feeders; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.gatling.javaapi.core.ChainBuilder; +import io.gatling.javaapi.http.HttpRequestActionBuilder; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; + +import static io.gatling.javaapi.core.CoreDsl.*; +import static io.gatling.javaapi.http.HttpDsl.http; +import static io.gatling.javaapi.http.HttpDsl.status; + +public class PageVisits { + public static List proposalTypes = Arrays.asList( + "NoConfidence", + "NewCommittee", + "NewConstitution", + "HardForkInitiation", + "ParameterChange", + "TreasuryWithdrawals", + "InfoAction" + ); + public static ObjectMapper objectMapper=new ObjectMapper(); + public static HttpRequestActionBuilder listAndSelectDreps(int min, int max){ + return http("get_dReps").get("/drep/list?page=0&size=10") + .check(jmesPath("elements[].drepId").ofList().transform(dreps ->{ + Collections.shuffle(dreps); + int visited_dreps = ThreadLocalRandom.current().nextInt(min, max); + return dreps.subList(0, Math.min(visited_dreps, dreps.size())); + }).exists().saveAs("drepIds")); + } + + + public static ChainBuilder visitProposalPage(){ + + + return exec(proposalTypes.stream().map(pType ->exec(requestProposal(pType)) + .doIf(session -> session.get("metadataList"+pType)!=null) + .then(foreach("#{metadataList" +pType + "}","metadata"+pType) + .on(exec( + http("validate proposal metadata") + .post("/metadata/validate") + .body(StringBody(session1 -> session1.get("metadata"+pType) + )).header("content-type","application/json") + .check(status().is(200)) + ))) + + ).toList()); + } + public static HttpRequestActionBuilder requestProposal(String type){ + return http("list proposal type "+type) + .get("/proposal/list") + .queryParam("page","0") + .queryParam("size","7") + .multivaluedQueryParam("type",List.of(type)) + .check(status().is(200)) + .check(jmesPath("elements[*]").ofList().transform(rawElements -> { + + List> elements = rawElements.stream().map(v -> (Map) v).toList(); + + List metaDataList = elements.stream().map(proposal -> { + var metadataHash = proposal.get("metadataHash").toString(); + var url = proposal.get("url"); + var mp= Map.of("url",url,"hash",metadataHash); + try { + return objectMapper.writeValueAsString(mp); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + return metaDataList; + }).exists().saveAs("metadataList"+type)); + } +} diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java b/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java index 3594be597..d12a232ec 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java @@ -1,30 +1,45 @@ package org.cardano.govtool.simulations; -import org.cardano.govtool.Scenario; +import io.gatling.javaapi.core.ChainBuilder; +import io.gatling.javaapi.core.PopulationBuilder; import io.gatling.javaapi.core.Simulation; import io.gatling.javaapi.http.HttpProtocolBuilder; +import org.cardano.govtool.actions.Action; +import org.cardano.govtool.actions.AdaHolderAction; +import org.cardano.govtool.actions.AuthenticationAction; +import org.cardano.govtool.actions.DRepAction; +import org.cardano.govtool.feeders.DrepListFetcher; +import org.cardano.govtool.feeders.PageVisits; +import java.util.List; import java.util.Optional; import static io.gatling.javaapi.core.CoreDsl.*; import static io.gatling.javaapi.http.HttpDsl.http; -import static org.cardano.govtool.actions.AdaHolderAction.drepListDelegateScenario; public class VvaSimulation extends Simulation { - private static final String API_URL = Optional.ofNullable(System.getenv("API_URL")).orElse("https://govtool.cardanoapi.io/api"); - private static final int TARGET_USER_RATE = Integer.parseInt(Optional.ofNullable(System.getenv("TARGET_USER_RATE")).orElse("5")); - private static final int PEAK_USERS = Integer.parseInt(Optional.ofNullable(System.getenv("PEAK_USERS")).orElse("10")); - private static final int STRESS_DURATION = Integer.parseInt(Optional.ofNullable(System.getenv("STRESS_DURATION")).orElse("10")); - private static final int RAMP_DURATION = Integer.parseInt(Optional.ofNullable(System.getenv("RAMP_DURATION")).orElse("10")); - + private static final String API_URL = Optional.ofNullable(System.getenv("API_URL")).orElse("https://govtool.cardanoapi.io/api2"); + private static final int PEAK_USERS = Integer.parseInt(Optional.ofNullable(System.getenv("PEAK_USERS")).orElse("600")); + private static final int STRESS_DURATION = Integer.parseInt(Optional.ofNullable(System.getenv("STRESS_DURATION")).orElse("20")); + private static final int RAMP_DURATION = Integer.parseInt(Optional.ofNullable(System.getenv("RAMP_DURATION")).orElse("20")); + private ListknownDreps; @Override public void before() { System.out.printf("Base API URL: %s%n", API_URL); - System.out.printf("Target user rate: %d users/sec%n", TARGET_USER_RATE); - System.out.printf("Ramping users over %d seconds%n", RAMP_DURATION); System.out.printf("Peak users count: %d%n", PEAK_USERS); + System.out.printf("Ramping users over %d seconds%n", RAMP_DURATION); System.out.printf("Stress interval %d seconds%n", STRESS_DURATION); + + } + + private PopulationBuilder makeScenario(String name, ChainBuilder chain, double userPercent) { + var rampUserRate = ((double) PEAK_USERS) * userPercent / (double) RAMP_DURATION; + return scenario(name).exec(AuthenticationAction.connect(knownDreps).pause(2).exec(chain)).injectOpen( + nothingFor(5), + constantUsersPerSec(rampUserRate).during(RAMP_DURATION), + stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) + ); } private final HttpProtocolBuilder httpProtocol = http @@ -37,49 +52,23 @@ public void before() { // Load Simulation { - setUp( -// Scenario.userConnectAndLeave.injectOpen( -// nothingFor(5), -// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.2).during(RAMP_DURATION), -// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) -// ) + knownDreps= DrepListFetcher.fetchDrepIds(API_URL); - drepListDelegateScenario.injectOpen( - nothingFor(5), - rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3).during(RAMP_DURATION), - stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) - ) -// Scenario.userRegisterAsDRep.injectOpen( -// nothingFor(5), -// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3).during(RAMP_DURATION), -// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) -// ), -// Scenario.userOnlyViewsProposal.injectOpen( -// nothingFor(5), -// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.1).during(RAMP_DURATION), -// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) -// ), -// Scenario.adaHolderDelegateToDRep.injectOpen( -// nothingFor(5), -// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.4).during(RAMP_DURATION), -// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) -// ), -// // Further DRep scenarios -// Scenario.dRepVoteOnProposal.injectOpen( -// nothingFor(5), -// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3 * 0.4).during(RAMP_DURATION), -// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) -// ), -// Scenario.dRepViewVotes.injectOpen( -// nothingFor(5), -// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3 * 0.5).during(RAMP_DURATION), -// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) -// ), -// Scenario.dRepRetires.injectOpen( -// nothingFor(5), -// rampUsersPerSec(1).to(TARGET_USER_RATE * 0.3 * 0.1).during(RAMP_DURATION), -// stressPeakUsers(PEAK_USERS).during(STRESS_DURATION) -// ) + setUp( + makeScenario("User Connects and Leave", exec(), 0.1) + , makeScenario("User Registers as Drep", + exec(DRepAction.registerAsDRep), 0.1), + makeScenario("User Views Proposals", + exec(Action.viewProposals), 0.2) + , makeScenario("AdaHolder delegates to Drep", + exec(AdaHolderAction.delegateToDRep), 0.1) + , makeScenario("Drep votes on Proposal", + exec(DRepAction.vote), 0.3 * 0.4) + , makeScenario("Drep view Votes", + exec(Action.viewProposals).pause(2).exec(Action.viewProposals), 0.3 * 0.5) + , makeScenario("Drep Retirement", + exec(DRepAction.retireAsDRep), 0.3 * 0.1) + , makeScenario("ListProposals", PageVisits.visitProposalPage(), 0.2) ).protocols(httpProtocol); } From 4aa67f8dd4a65cfd69453180963bde7ae08a1559 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Mon, 17 Jun 2024 17:08:35 +0545 Subject: [PATCH 03/49] Setup deployment for storybook --- govtool/frontend/Dockerfile.story | 28 +++++++++++++++++++ tests/test-infrastructure/build-and-deploy.sh | 1 + .../docker-compose-govtool.yml | 14 +++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 govtool/frontend/Dockerfile.story diff --git a/govtool/frontend/Dockerfile.story b/govtool/frontend/Dockerfile.story new file mode 100644 index 000000000..599f88e16 --- /dev/null +++ b/govtool/frontend/Dockerfile.story @@ -0,0 +1,28 @@ +FROM node:18-alpine as deps +ARG NPMRC_TOKEN + +WORKDIR /src + +# Set npm configuration settings using environment variables +RUN npm config set @intersect.mbo:registry "https://registry.npmjs.org/" --location=global \ + && npm config set //registry.npmjs.org/:_authToken ${NPMRC_TOKEN} --location=global + +COPY package.json package-lock.json ./ +RUN npm install + +FROM node:18-alpine as builder +ARG NPMRC_TOKEN +ENV NODE_OPTIONS=--max_old_space_size=8192 +WORKDIR /src + +COPY --from=deps /src/node_modules ./node_modules +COPY . . + +RUN npm run build-storybook --quiet + +FROM nginx:stable-alpine +EXPOSE 80 + +COPY --from=builder /src/storybook-static /usr/share/nginx/html + +CMD ["nginx", "-g", "daemon off;"] diff --git a/tests/test-infrastructure/build-and-deploy.sh b/tests/test-infrastructure/build-and-deploy.sh index 919f64a15..4c6e6e166 100755 --- a/tests/test-infrastructure/build-and-deploy.sh +++ b/tests/test-infrastructure/build-and-deploy.sh @@ -24,6 +24,7 @@ then update-service govtool_backend "$BASE_IMAGE_NAME"/backend:${GOVTOOL_TAG} update-service govtool_frontend "$BASE_IMAGE_NAME"/frontend:${GOVTOOL_TAG} update-service govtool_metadata-validation "$BASE_IMAGE_NAME"/metadata-validation:${GOVTOOL_TAG} + update-service govtool_storybook "$BASE_IMAGE_NAME"/storybook:${GOVTOOL_TAG} update-service govaction-loader_backend "$BASE_IMAGE_NAME"/gov-action-loader-backend:${GOVTOOL_TAG} update-service govaction-loader_frontend "$BASE_IMAGE_NAME"/gov-action-loader-frontend:${GOVTOOL_TAG} diff --git a/tests/test-infrastructure/docker-compose-govtool.yml b/tests/test-infrastructure/docker-compose-govtool.yml index eebd897e0..2195fab32 100644 --- a/tests/test-infrastructure/docker-compose-govtool.yml +++ b/tests/test-infrastructure/docker-compose-govtool.yml @@ -59,7 +59,7 @@ services: context: ../../govtool/metadata-validation environment: VIRTUAL_HOST: https://${BASE_DOMAIN}/metadata-validation/ -> :3000 - PORT: '3000' + PORT: "3000" networks: - frontend deploy: @@ -68,3 +68,15 @@ services: placement: constraints: - node.labels.govtool==true + storybook: + image: govtool/storybook:${GOVTOOL_TAG} + build: + context: ../../govtool/frontend + dockerfile: Dockerfile.story + args: + NPMRC_TOKEN: ${NPMRC_TOKEN} + environments: + VIRTUAL_HOST: https://${BASE_DOMAIN}-storybook + deploy: + restart_policy: + delay: "30s" From d1bdf0612f862f21485eaa75e6f66e3594e8ae18 Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Wed, 19 Jun 2024 14:38:41 +0545 Subject: [PATCH 04/49] Fix Storybook deployment setup --- govtool/frontend/{Dockerfile.story => storybook.Dockerfile} | 0 tests/test-infrastructure/docker-compose-govtool.yml | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename govtool/frontend/{Dockerfile.story => storybook.Dockerfile} (100%) diff --git a/govtool/frontend/Dockerfile.story b/govtool/frontend/storybook.Dockerfile similarity index 100% rename from govtool/frontend/Dockerfile.story rename to govtool/frontend/storybook.Dockerfile diff --git a/tests/test-infrastructure/docker-compose-govtool.yml b/tests/test-infrastructure/docker-compose-govtool.yml index 2195fab32..d0bbda1bd 100644 --- a/tests/test-infrastructure/docker-compose-govtool.yml +++ b/tests/test-infrastructure/docker-compose-govtool.yml @@ -72,11 +72,11 @@ services: image: govtool/storybook:${GOVTOOL_TAG} build: context: ../../govtool/frontend - dockerfile: Dockerfile.story + dockerfile: storybook.Dockerfile args: NPMRC_TOKEN: ${NPMRC_TOKEN} - environments: - VIRTUAL_HOST: https://${BASE_DOMAIN}-storybook + environment: + VIRTUAL_HOST: https://storybook-${BASE_DOMAIN} deploy: restart_policy: delay: "30s" From 789a330f2b7a061005e6563031321955c6aab071 Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Wed, 19 Jun 2024 14:47:54 +0545 Subject: [PATCH 05/49] Configure Google tag manager in test stack --- tests/test-infrastructure/docker-compose-govtool.yml | 1 + tests/test-infrastructure/playbook.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/test-infrastructure/docker-compose-govtool.yml b/tests/test-infrastructure/docker-compose-govtool.yml index eebd897e0..7a390234e 100644 --- a/tests/test-infrastructure/docker-compose-govtool.yml +++ b/tests/test-infrastructure/docker-compose-govtool.yml @@ -43,6 +43,7 @@ services: VITE_SENTRY_DSN: ${SENTRY_DSN_FRONTEND} NPMRC_TOKEN: ${NPMRC_TOKEN} VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED: "true" + GTM_ID: ${GTM_ID} environment: VIRTUAL_HOST: https://${BASE_DOMAIN} networks: diff --git a/tests/test-infrastructure/playbook.yml b/tests/test-infrastructure/playbook.yml index 6eef0d7c8..eb8100b6a 100644 --- a/tests/test-infrastructure/playbook.yml +++ b/tests/test-infrastructure/playbook.yml @@ -18,6 +18,7 @@ args: chdir: "/opt/govtool/tests/test-infrastructure" environment: + GTM_ID: "{{ lookup ('env', 'GTM_ID') }}" GOVTOOL_TAG: "{{ lookup('env', 'GOVTOOL_TAG') }}" NPMRC_TOKEN: "{{ lookup('env','NPMRC_TOKEN') }}" SENTRY_DSN_FRONTEND: "{{ lookup ('env', 'SENTRY_DSN_FRONTEND') }}" From 6d82a29c3fd36aba0eeb33e13a6b5965c72353f3 Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Wed, 19 Jun 2024 15:04:29 +0545 Subject: [PATCH 06/49] Fix SonarQube analysis --- .github/workflows/frontend_sonar_scan.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/frontend_sonar_scan.yml b/.github/workflows/frontend_sonar_scan.yml index 09b8f994f..8ddf8bc3b 100644 --- a/.github/workflows/frontend_sonar_scan.yml +++ b/.github/workflows/frontend_sonar_scan.yml @@ -31,21 +31,11 @@ jobs: working-directory: govtool/frontend env: NODE_OPTIONS: "--max_old_space_size=4096" + NODE_AUTH_TOKEN: ${{ secrets.NPMRC_TOKEN }} run: | - npm install + npm ci npm run test:coverage -# Running with docker -# -# - name: Run SonarQube Scanner -# run: | -# docker run --rm \ -# -e SONAR_HOST_URL="https://sonarcloud.io" \ -# -e SONAR_TOKEN="ec4183646e59dd70c8077acfabe52062ccbea7a9" \ -# -v "$(pwd):/usr/src" \ -# --workdir=/usr/src/govtool/frontend \ -# sonarsource/sonar-scanner-cli:5.0.1 - - uses: sonarsource/sonarqube-scan-action@master with: projectBaseDir: govtool/frontend From e13cb49584497a384cbe27424e37872749436e9c Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Wed, 19 Jun 2024 16:04:44 +0545 Subject: [PATCH 07/49] Workflow: Configure registry url and scope --- .github/workflows/frontend_sonar_scan.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/frontend_sonar_scan.yml b/.github/workflows/frontend_sonar_scan.yml index 8ddf8bc3b..c46f94a4f 100644 --- a/.github/workflows/frontend_sonar_scan.yml +++ b/.github/workflows/frontend_sonar_scan.yml @@ -26,7 +26,8 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: "govtool/frontend/.nvmrc" - + registry-url: "https://registry.npmjs.org/" + scope: "@intersect.mbo" - name: đŸ§ª Test working-directory: govtool/frontend env: From 7fcac21a06b4ac87af8442c35598fb2855aa8c5f Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Wed, 19 Jun 2024 16:13:30 +0545 Subject: [PATCH 08/49] Increase heap size for test run --- .github/workflows/frontend_sonar_scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/frontend_sonar_scan.yml b/.github/workflows/frontend_sonar_scan.yml index c46f94a4f..e2cff137d 100644 --- a/.github/workflows/frontend_sonar_scan.yml +++ b/.github/workflows/frontend_sonar_scan.yml @@ -31,7 +31,7 @@ jobs: - name: đŸ§ª Test working-directory: govtool/frontend env: - NODE_OPTIONS: "--max_old_space_size=4096" + NODE_OPTIONS: "--max_old_space_size=6144" NODE_AUTH_TOKEN: ${{ secrets.NPMRC_TOKEN }} run: | npm ci From 3b09d374e9f2f64629edaf7d2b76203f7f310620 Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Wed, 19 Jun 2024 16:19:13 +0545 Subject: [PATCH 09/49] Run SonarQube scan on test failure --- .github/workflows/frontend_sonar_scan.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/frontend_sonar_scan.yml b/.github/workflows/frontend_sonar_scan.yml index e2cff137d..5a78a37be 100644 --- a/.github/workflows/frontend_sonar_scan.yml +++ b/.github/workflows/frontend_sonar_scan.yml @@ -38,6 +38,7 @@ jobs: npm run test:coverage - uses: sonarsource/sonarqube-scan-action@master + if: always() with: projectBaseDir: govtool/frontend env: From aeda2cde491abdc47d2254d0c8a13c212ffa9f35 Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Wed, 19 Jun 2024 15:43:06 +0545 Subject: [PATCH 10/49] Update sentry DNS variable in test stack --- .github/workflows/build-and-deploy-test-stack.yml | 3 ++- tests/test-infrastructure/.env.example | 3 ++- tests/test-infrastructure/docker-compose-govtool.yml | 3 ++- tests/test-infrastructure/playbook.yml | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-deploy-test-stack.yml b/.github/workflows/build-and-deploy-test-stack.yml index 76a40607f..bd59c79c7 100644 --- a/.github/workflows/build-and-deploy-test-stack.yml +++ b/.github/workflows/build-and-deploy-test-stack.yml @@ -22,9 +22,10 @@ jobs: SENTRY_DSN_BACKEND: ${{ secrets.SENTRY_DSN_BACKEND }} GTM_ID: ${{ secrets.GTM_ID }} NPMRC_TOKEN: ${{ secrets.NPMRC_TOKEN }} - SENTRY_DSN_FRONTEND: ${{ secrets.INTERSECT_SENTRY_DSN_FRONTEND }} + SENTRY_DSN_FRONTEND: ${{ secrets.SENTRY_DSN_FRONTEND }} PIPELINE_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} USERSNAP_SPACE_API_KEY: ${{ secrets.USERSNAP_SPACE_API_KEY }} + APP_ENV: test steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/tests/test-infrastructure/.env.example b/tests/test-infrastructure/.env.example index ace81dd8f..9d3654b2f 100644 --- a/tests/test-infrastructure/.env.example +++ b/tests/test-infrastructure/.env.example @@ -4,4 +4,5 @@ SENTRY_DSN_FRONTEND= SENTRY_DSN_BACKEND= CARDANO_NETWORK=sanchonet BASE_DOMAIN=govtool.cardanoapi.io -GOVTOOL_TAG=test \ No newline at end of file +GOVTOOL_TAG=test +APP_ENV=test \ No newline at end of file diff --git a/tests/test-infrastructure/docker-compose-govtool.yml b/tests/test-infrastructure/docker-compose-govtool.yml index 7a390234e..0661ef27a 100644 --- a/tests/test-infrastructure/docker-compose-govtool.yml +++ b/tests/test-infrastructure/docker-compose-govtool.yml @@ -20,9 +20,9 @@ services: - -c - vva-be -c /config.json start-app environment: + APP_ENV: ${APP_ENV:-test} VIRTUAL_HOST: https://${BASE_DOMAIN}/api/ -> :8080/ VIRTUAL_HOST_2: https://${BASE_DOMAIN}/swagger -> :8080/swagger - networks: - frontend - postgres @@ -42,6 +42,7 @@ services: VITE_BASE_URL: "/api" VITE_SENTRY_DSN: ${SENTRY_DSN_FRONTEND} NPMRC_TOKEN: ${NPMRC_TOKEN} + VITE_APP_ENV: ${APP_ENV:-test} VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED: "true" GTM_ID: ${GTM_ID} environment: diff --git a/tests/test-infrastructure/playbook.yml b/tests/test-infrastructure/playbook.yml index eb8100b6a..7a6337f3c 100644 --- a/tests/test-infrastructure/playbook.yml +++ b/tests/test-infrastructure/playbook.yml @@ -18,6 +18,7 @@ args: chdir: "/opt/govtool/tests/test-infrastructure" environment: + APP_ENV: :{{ lookup('env', 'APP_ENV') }} GTM_ID: "{{ lookup ('env', 'GTM_ID') }}" GOVTOOL_TAG: "{{ lookup('env', 'GOVTOOL_TAG') }}" NPMRC_TOKEN: "{{ lookup('env','NPMRC_TOKEN') }}" From b6d611c280dc6a87510d4ea856647491b5440fe5 Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Wed, 19 Jun 2024 16:37:42 +0545 Subject: [PATCH 11/49] Add placement constraint on storybook service --- tests/test-infrastructure/docker-compose-govtool.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test-infrastructure/docker-compose-govtool.yml b/tests/test-infrastructure/docker-compose-govtool.yml index d0bbda1bd..135f91703 100644 --- a/tests/test-infrastructure/docker-compose-govtool.yml +++ b/tests/test-infrastructure/docker-compose-govtool.yml @@ -80,3 +80,6 @@ services: deploy: restart_policy: delay: "30s" + placement: + constraints: + - node.labels.govtool==true From 3dd1b06ae5962b034b712ad25be6b94d98e778fc Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Wed, 19 Jun 2024 16:50:36 +0545 Subject: [PATCH 12/49] Make user-scenarios on stress-test readable --- .../cardano/govtool/simulations/VvaSimulation.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java b/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java index d12a232ec..37c3a4586 100644 --- a/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java +++ b/tests/load-testing/src/test/java/org/cardano/govtool/simulations/VvaSimulation.java @@ -53,7 +53,7 @@ private PopulationBuilder makeScenario(String name, ChainBuilder chain, double u // Load Simulation { knownDreps= DrepListFetcher.fetchDrepIds(API_URL); - + var DREP_USER_RATI0=0.3 setUp( makeScenario("User Connects and Leave", exec(), 0.1) , makeScenario("User Registers as Drep", @@ -62,13 +62,15 @@ private PopulationBuilder makeScenario(String name, ChainBuilder chain, double u exec(Action.viewProposals), 0.2) , makeScenario("AdaHolder delegates to Drep", exec(AdaHolderAction.delegateToDRep), 0.1) - , makeScenario("Drep votes on Proposal", - exec(DRepAction.vote), 0.3 * 0.4) + , makeScenario("ListProposals", PageVisits.visitProposalPage(), 0.2) + + // Further split drep users on scenarios , makeScenario("Drep view Votes", - exec(Action.viewProposals).pause(2).exec(Action.viewProposals), 0.3 * 0.5) + exec(Action.viewProposals).pause(2).exec(Action.viewProposals), DREP_USER_RATI0 * 0.5) + , makeScenario("Drep votes on Proposal", + exec(DRepAction.vote), DREP_USER_RATI0 * 0.4) , makeScenario("Drep Retirement", - exec(DRepAction.retireAsDRep), 0.3 * 0.1) - , makeScenario("ListProposals", PageVisits.visitProposalPage(), 0.2) + exec(DRepAction.retireAsDRep), DREP_USER_RATI0 * 0.1) ).protocols(httpProtocol); } From f37bf633ce3010bcbf9d6585c5bf2435bc3eded4 Mon Sep 17 00:00:00 2001 From: niraj Date: Wed, 12 Jun 2024 14:00:27 +0545 Subject: [PATCH 13/49] feat: Proposal services to create,delete proposal and add poll on proposal --- .../govtool-frontend/playwright/.env.example | 3 + .../playwright/lib/constants/environments.ts | 1 + .../lib/services/proposalDiscussionService.ts | 77 +++++++++++++++++++ .../govtool-frontend/playwright/lib/types.ts | 36 +++++++++ 4 files changed, 117 insertions(+) create mode 100644 tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts diff --git a/tests/govtool-frontend/playwright/.env.example b/tests/govtool-frontend/playwright/.env.example index f86654032..d443e1d96 100644 --- a/tests/govtool-frontend/playwright/.env.example +++ b/tests/govtool-frontend/playwright/.env.example @@ -3,6 +3,9 @@ API_URL=http://localhost:3000/api DOCS_URL=https://docs.sanchogov.tools + +PDF_URL=https://dev.api.pdf.gov.tools + # 0 for testnet, 1 for mainnet NETWORK_ID=0 diff --git a/tests/govtool-frontend/playwright/lib/constants/environments.ts b/tests/govtool-frontend/playwright/lib/constants/environments.ts index 460bd382a..5c0071691 100644 --- a/tests/govtool-frontend/playwright/lib/constants/environments.ts +++ b/tests/govtool-frontend/playwright/lib/constants/environments.ts @@ -10,6 +10,7 @@ const environments = { frontendUrl: SERVER_HOST_URL, apiUrl: `${SERVER_HOST_URL}/api`, docsUrl: process.env.DOCS_URL || "https://docs.sanchogov.tools", + pdfUrl: process.env.PDF_URL || "https://dev.api.pdf.gov.tools", networkId: parseInt(process.env.NETWORK_ID) || 0, faucet: { apiUrl: diff --git a/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts b/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts new file mode 100644 index 000000000..bcfa420fa --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts @@ -0,0 +1,77 @@ +import environments from "@constants/environments"; +import { Logger } from "@helpers/logger"; +import { addPollPayload, proposalCreationPayload } from "@types"; + +import fetch = require("node-fetch"); + +const proposalDiscussionService = { + createProposal: async (data: { data: proposalCreationPayload }) => { + try { + const res = await fetch(`${environments.pdfUrl}/api/proposals`, { + method: "POST", + headers: { + "content-type": "application/json", + authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", + }, + body: JSON.stringify(data), + }); + + const response = await res.json(); + if(res.status === 200){ + console.log(JSON.stringify(data)); + Logger.success("Governance action proposal created successfully"); + return response; + } + + Logger.fail("Failed to create governance action proposal"); + throw new Error(response['error']['message']); + + } catch (err) { + Logger.fail("Failed to create governance action proposal"); + throw err; + } + }, + deleteProposal: async (proposalId: number) => { + try { + const res = await fetch( + `${environments.pdfUrl}/api/proposals/${proposalId}`, + { + method: "DELETE", + headers: { + "content-type": "application/json", + authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", + }, + } + ); + await res.json(); + Logger.success("Governance action proposal deleted successfully"); + } catch (err) { + Logger.fail("Failed to delete governance action proposal"); + throw err; + } + }, + + addPoll:async(data:addPollPayload)=>{ + try{ + const res = await fetch(`${environments.pdfUrl}/api/polls`,{ + method:"POST", + headers: { + "content-type": "application/json", + authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", + }, + body:JSON.stringify(data), + }) + await res.json(); + Logger.success("Poll added successfully"); + } catch (err) { + Logger.fail("Failed to delete governance action proposal"); + throw err; + } + + } +}; + +export default proposalDiscussionService; diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts index 765c955e7..08c07e853 100644 --- a/tests/govtool-frontend/playwright/lib/types.ts +++ b/tests/govtool-frontend/playwright/lib/types.ts @@ -97,3 +97,39 @@ export type ProtocolParams = { dRepDeposit: number; govActionDeposit: number; }; + +export type proposalCreationPayload = { + proposal_links: Array; + gov_action_type_id: number; + prop_name: string; + prop_abstract: string; + prop_motivation: string; + prop_rationale: string; + prop_receiving_address: string; + prop_amount: string; + is_draft: boolean; +}; + +type proposalLinksType = { + prop_link: string; + prop_link_text: string; +}; + +export type proposalCreationResponse = { + data: { + attributes: { + proposal_id: number; + proposal_content_id: number; + }; + }; + meta: Object; +}; + + +export type addPollPayload = { + data: { + proposal_id: string, + poll_start_dt: string, + is_poll_active: boolean + } +} From 9b043677dd2bb45efc60572d669a76bcb747a940 Mon Sep 17 00:00:00 2001 From: niraj Date: Wed, 12 Jun 2024 14:02:18 +0545 Subject: [PATCH 14/49] feat: Add proposal setup and teardown --- .../playwright/lib/proposalManager.ts | 57 +++++++++++++++++++ .../playwright/playwright.config.ts | 5 ++ .../playwright/tests/proposal.setup.ts | 25 ++++++++ .../playwright/tests/proposal.teardown.ts | 8 +++ 4 files changed, 95 insertions(+) create mode 100644 tests/govtool-frontend/playwright/lib/proposalManager.ts create mode 100644 tests/govtool-frontend/playwright/tests/proposal.teardown.ts diff --git a/tests/govtool-frontend/playwright/lib/proposalManager.ts b/tests/govtool-frontend/playwright/lib/proposalManager.ts new file mode 100644 index 000000000..d00f41304 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/proposalManager.ts @@ -0,0 +1,57 @@ +import { proposalCreationPayload } from "@types"; +import * as fs from "fs"; +const path = require("path"); + +const baseFilePath = path.resolve(__dirname, "./_mock"); + +class ProposalManager { + private static instance: ProposalManager; + + public static getInstance(): ProposalManager { + if (!ProposalManager.instance) { + ProposalManager.instance = new ProposalManager(); + } + return ProposalManager.instance; + } + + async writeProposal(response: Object) { + await new Promise((resolve, reject) => + fs.writeFile( + `${baseFilePath}/proposal.json`, + JSON.stringify(response, null, 2), + (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + } + ) + ); + } + + private async readProposal(): Promise { + const data: string = await new Promise((resolve, reject) => + fs.readFile(`${baseFilePath}/proposal.json`, "utf8", (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }) + ); + return JSON.parse(data); + } + + async getProposalId(): Promise { + const proposal = await this.readProposal(); + return proposal["response"]["data"]["attributes"]["proposal_id"]; + } + + async getProposalPayload(): Promise { + const proposal = await this.readProposal(); + return proposal["payload"]["data"]; + } +} + +export default ProposalManager.getInstance(); diff --git a/tests/govtool-frontend/playwright/playwright.config.ts b/tests/govtool-frontend/playwright/playwright.config.ts index 62e7c3287..0533badf8 100644 --- a/tests/govtool-frontend/playwright/playwright.config.ts +++ b/tests/govtool-frontend/playwright/playwright.config.ts @@ -75,6 +75,7 @@ export default defineConfig({ dependencies: environments.ci ? ["auth setup", "wallet bootstrap", "proposal setup"] : [], + teardown: environments.ci && "cleanup proposal", }, { name: "loggedin (desktop)", @@ -124,5 +125,9 @@ export default defineConfig({ name: "cleanup delegation", testMatch: "delegation.teardown.ts", }, + { + name: "cleanup proposal", + testMatch: "proposal.teardown.ts", + }, ], }); diff --git a/tests/govtool-frontend/playwright/tests/proposal.setup.ts b/tests/govtool-frontend/playwright/tests/proposal.setup.ts index 9f7111499..157f85524 100644 --- a/tests/govtool-frontend/playwright/tests/proposal.setup.ts +++ b/tests/govtool-frontend/playwright/tests/proposal.setup.ts @@ -4,7 +4,11 @@ import { ShelleyWallet } from "@helpers/crypto"; import { pollTransaction } from "@helpers/transaction"; import { test as setup } from "@playwright/test"; import kuberService from "@services/kuberService"; +import proposalDiscussionService from "@services/proposalDiscussionService"; +import { addPollPayload, proposalCreationResponse } from "@types"; import walletManager from "lib/walletManager"; +import { mockProposalCreationPayload } from "@mock/index"; +import proposalManager from "lib/proposalManager"; const PROPOSAL_SUBMISSIONS_WALLETS_COUNT = 1; @@ -47,3 +51,24 @@ setup("Setup temporary proposal wallets", async () => { "proposalSubmission" ); }); + + +setup("Create temporary proposal", async () => { + const response: proposalCreationResponse = + await proposalDiscussionService.createProposal(mockProposalCreationPayload); + + const mockAddPollPayload:addPollPayload={ + data:{ + proposal_id: response.data.attributes.proposal_id.toString(), + poll_start_dt:new Date().toISOString(), + is_poll_active:true + } + } + await proposalDiscussionService.addPoll(mockAddPollPayload) + + await proposalManager.writeProposal({ + payload: mockProposalCreationPayload, + response: response, + }); +}); + diff --git a/tests/govtool-frontend/playwright/tests/proposal.teardown.ts b/tests/govtool-frontend/playwright/tests/proposal.teardown.ts new file mode 100644 index 000000000..2cf4db6f4 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/proposal.teardown.ts @@ -0,0 +1,8 @@ +import { test as cleanup } from "@playwright/test"; +import proposalDiscussionService from "@services/proposalDiscussionService"; +import proposalManager from "lib/proposalManager"; + +cleanup("Delete Proposal", async () => { + const proposalId = await proposalManager.getProposalId(); + await proposalDiscussionService.deleteProposal(proposalId); +}); From b489f6eda1fba3a619bad5fc99825bdad7f25459 Mon Sep 17 00:00:00 2001 From: niraj Date: Wed, 12 Jun 2024 14:04:50 +0545 Subject: [PATCH 15/49] chore: Add proposal discussion and its details page --- .../pages/proposalDiscussionDetailsPage.ts | 63 +++++++++++++++++++ .../lib/pages/proposalDiscussionPage.ts | 60 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts create mode 100644 tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts new file mode 100644 index 000000000..587f226be --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts @@ -0,0 +1,63 @@ +import environments from "@constants/environments"; +import { Page } from "@playwright/test"; + +export default class ProposalDiscussionDetailsPage { + private readonly proposalId: number | null; + // Buttons + readonly likeBtn = this.page.getByTestId("like-button"); + readonly dislikeBtn = this.page.getByTestId("dislike-button"); + readonly commentBtn = this.page.getByRole("button", { + name: "Comment", + exact: true, + }); // this.page.getByTestId("comment-button"); + readonly addPollBtn = this.page.getByTestId("add-poll"); + readonly SubmitBtn = this.page.getByTestId("submit-button"); + readonly menuBtn = this.page.getByTestId("menu-button"); + readonly editProposalBtn = this.page.getByTestId("edit-proposal"); + readonly deleteProposalBtn = this.page.getByTestId("delete-proposal"); + readonly reviewVersionsBtn = this.page.getByTestId("review-versions"); + readonly closePollBtn = this.page.getByTestId("close-poll"); + readonly sortBtn = this.page + .locator("div") + .filter({ hasText: /^Comments$/ }) + .getByRole("button"); // this.page.getByTestId("sort-button"); + readonly proposeGovernanceAction = this.page.getByTestId("propose-GA-button"); + readonly replyBtn = this.page.getByTestId("reply-button"); + readonly pollYesBtn = this.page.getByTestId("poll-yes-button"); + readonly pollNoBtn = this.page.getByTestId("poll-No-button"); + + // Inputs + readonly commentInput = this.page.getByRole("textbox"); //this.page.getByTestId("comment-input"); + readonly replyInput = this.page.getByTestId("reply-input"); + + // Indicators + readonly likesCounts = this.page.getByTestId("likes-count"); + readonly dislikesCounts = this.page.getByTestId("dislikse-count"); + readonly commentsCount = this.page.getByTestId("comments-count"); + + readonly pollYesVoteCount = this.page.getByTestId("poll-yes-vote-count"); + readonly pollNoVoteCount = this.page.getByTestId("poll-No-vote-count"); + + // Cards + readonly pollVoteCard = this.page.getByTestId("poll-vote-card"); + readonly pollResultCard = this.page.getByTestId("poll-result-card"); + readonly commentCard = + this.proposeGovernanceAction.getByTestId("comment-card"); + + constructor( + private readonly page: Page, + proposalId?: number + ) { + this.proposalId = proposalId; + } + + get currentPage(): Page { + return this.page; + } + + async goto(proposalId?: number) { + await this.page.goto( + `${environments.frontendUrl}/proposal_discussion/${proposalId ?? this.proposalId}` + ); + } +} diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts new file mode 100644 index 000000000..74fa75623 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts @@ -0,0 +1,60 @@ +import { Page } from "@playwright/test"; +import environments from "lib/constants/environments"; +import ProposalDiscussionDetailsPage from "./proposalDiscussionDetailsPage"; + +export default class ProposalDiscussionPage { + // Buttons + readonly filterBtn = this.page.locator("#filters-button"); // this.page.getByTestId("filters-button"); + readonly shareBtn = this.page + .locator(".MuiCardHeader-action > .MuiButtonBase-root") + .first(); //this.page.getByTestId("share-button"); + readonly sortBtn = this.page.locator("button:nth-child(2)").first(); //this.page.getByTestId("sort-button"); + readonly searchInput = this.page.getByPlaceholder("Search..."); // this.page.getByTestId("search-input"); + readonly showAllBtn = this.page + .getByRole("button", { name: "Show all" }) + .first(); //this.page.getByTestId("show-all-button"); + readonly showLessBtn = this.page.getByRole("button", { name: "Show less" }); + readonly infoRadio = this.page.getByLabel("Info"); + readonly treasuryRadio = this.page.getByLabel("Treasury"); + + constructor(private readonly page: Page) {} + + async goto() { + await this.page.goto(`${environments.frontendUrl}/proposal_discussion`); + await this.page.waitForTimeout(2_000); + } + + async gotoProposal(proposalId: number) { + await this.page.goto( + `${environments.frontendUrl}/proposal_discussion/${proposalId}` + ); + return new ProposalDiscussionDetailsPage(this.page); + } + + async viewProposal( + proposalId: string + ): Promise { + const proposalTestId = `proposal-${proposalId}-view-detail`; + await this.page.getByTestId(proposalTestId).click(); + + return new ProposalDiscussionDetailsPage(this.page); + } + + async viewFirstProposal(): Promise { + await this.page + .locator('[data-testid^="govaction-"][data-testid$="-view-detail"]') + .first() + .click(); + return new ProposalDiscussionDetailsPage(this.page); + } + + async getAllProposals() { + await this.page.waitForTimeout(2_000); + return this.page.locator('[data-testid$="-card"]').all(); // BUG + } + + async getFirstProposal() { + await this.page.waitForTimeout(2_000); + return this.page.locator('[data-testid$="-card"]').first(); + } +} From 41a209169819c48c6b8946fa51ce078bfacdaf9c Mon Sep 17 00:00:00 2001 From: niraj Date: Wed, 12 Jun 2024 14:06:27 +0545 Subject: [PATCH 16/49] chore: Add fixture of proposal discussion details page --- .../fixtures/proposalDiscussionDetailsPage.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts diff --git a/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts new file mode 100644 index 000000000..cc53d5ed2 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts @@ -0,0 +1,26 @@ +import { mockProposalCreationPayload } from "@mock/index"; +import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; +import { test as base } from "@fixtures/walletExtension"; +import proposalDiscussionService from "@services/proposalDiscussionService"; +import { proposalCreationResponse } from "@types"; + +export const test = base.extend<{ + proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; +}>({ + proposalDiscussionDetailsPage: async ({ page }, use) => { + const response: proposalCreationResponse = + await proposalDiscussionService.createProposal( + mockProposalCreationPayload + ); + + const proposalId = response.data.attributes.proposal_id; + const proposalDiscussionDetailPage = new ProposalDiscussionDetailsPage( + page, + proposalId + ); + + await use(proposalDiscussionDetailPage); + + await proposalDiscussionService.deleteProposal(proposalId); + }, +}); From c11724fe728c8634aededce1f64d6ba441ac7880 Mon Sep 17 00:00:00 2001 From: niraj Date: Wed, 12 Jun 2024 14:10:41 +0545 Subject: [PATCH 17/49] test: proposal discussion forum [WIP] --- .../playwright/lib/_mock/index.ts | 26 ++++++ .../proposalDiscussion.loggedin.spec.ts | 22 +++++ .../proposalDiscussion.spec.ts | 27 ++++++ .../proposalDiscussionVisibility.spec.ts | 83 +++++++++++++++++++ 4 files changed, 158 insertions(+) create mode 100644 tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts create mode 100644 tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts create mode 100644 tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts diff --git a/tests/govtool-frontend/playwright/lib/_mock/index.ts b/tests/govtool-frontend/playwright/lib/_mock/index.ts index b9c46e638..a4d2836cf 100644 --- a/tests/govtool-frontend/playwright/lib/_mock/index.ts +++ b/tests/govtool-frontend/playwright/lib/_mock/index.ts @@ -1,4 +1,6 @@ import { faker } from "@faker-js/faker"; +import { proposalCreationPayload } from "@types"; +import { bech32 } from "bech32"; export const invalid = { url: () => { @@ -71,3 +73,27 @@ export const invalid = { return " "; }, }; + +const randomBytes = new Uint8Array(10); +const bech32Address = bech32.encode("addr_test", randomBytes); + +export const mockProposalCreationPayload: { + data: proposalCreationPayload; +} = { + data: { + proposal_links: [ + { + prop_link: faker.internet.url(), + prop_link_text: faker.internet.displayName(), + }, + ], + gov_action_type_id: 1, + prop_name: faker.company.name(), + prop_abstract: faker.lorem.paragraph(2), + prop_motivation: faker.lorem.paragraph(2), + prop_rationale: faker.lorem.paragraph(2), + prop_receiving_address: bech32Address, + prop_amount: faker.number.int({ min: 100, max: 1000 }).toString(), + is_draft: false, + }, +}; diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts new file mode 100644 index 000000000..976e2e9a9 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -0,0 +1,22 @@ +import { user01Wallet } from "@constants/staticWallets"; +import { test } from "@fixtures/proposalDiscussionDetailsPage"; +import { expect } from "@playwright/test"; + +test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); + + +test("8G. Should display the proper likes and dislikes count.", async ({ + proposalDiscussionDetailsPage, +}) => { + await proposalDiscussionDetailsPage.goto(); + + await proposalDiscussionDetailsPage.likeBtn.click(); + + await expect(proposalDiscussionDetailsPage.likesCounts).toHaveValue("1"); + await expect(proposalDiscussionDetailsPage.dislikesCounts).toHaveValue("0"); + + await proposalDiscussionDetailsPage.dislikeBtn.click(); + + await expect(proposalDiscussionDetailsPage.likesCounts).toHaveValue("0"); + await expect(proposalDiscussionDetailsPage.dislikesCounts).toHaveValue("1"); +}); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts new file mode 100644 index 000000000..158fcfae7 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts @@ -0,0 +1,27 @@ +import { test } from "@fixtures/proposalDiscussionDetailsPage"; +import { setAllureEpic } from "@helpers/allure"; +import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; +import { expect } from "@playwright/test"; + +test.beforeEach(() => { + setAllureEpic("Proposal Discussion Forum"); +}); + +test("8A. Should access the list of proposed governance actions in a disconnected state.", async ({ + page, +}) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + await expect(proposalDiscussionPage.searchInput).toBeVisible(); + await expect(page.getByText(/Proposal Discussion/i)).toHaveCount(2); +}); + +test("8E.Should share proposed governance actions.", async ({ page }) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + const proposalCard = await proposalDiscussionPage.getFirstProposal(); + + await proposalCard.getByTestId("share-button").click(); +}); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts new file mode 100644 index 000000000..090519acc --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts @@ -0,0 +1,83 @@ +import { faker } from "@faker-js/faker"; +import { test } from "@fixtures/proposalDiscussionDetailsPage"; +import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; +import { expect } from "@playwright/test"; +import proposalManager from "lib/proposalManager"; + +test("8B. Should filter and sort the list of proposed governance actions.", async ({ + page, +}) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + await proposalDiscussionPage.filterBtn.click(); + await proposalDiscussionPage.infoRadio.click(); + + await expect(page.getByText("Treasury")).toHaveCount(1); + + await proposalDiscussionPage.treasuryRadio.click(); +}); + + +test("8C. Should search the list of proposed governance actions.", async ({ + page, +}) => { + const proposalPayload = await proposalManager.getProposalPayload(); + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + await proposalDiscussionPage.searchInput.fill(proposalPayload.prop_name); + + const proposalCards = await proposalDiscussionPage.getAllProposals(); + + for (const proposalCard of proposalCards) { + expect( + (await proposalCard.textContent()) + .toLowerCase() + .includes(`${proposalPayload.prop_name}`) + ).toBeTruthy(); + } +}); + + +test("8D.Should show the view-all categorized proposed governance actions.", async ({ + page, +}) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + await proposalDiscussionPage.showAllBtn.click(); + + await expect(proposalDiscussionPage.showLessBtn).toBeVisible(); +}); + + +test("8H. Should disable proposal interaction on a disconnected state.", async ({ + page, +}) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + const proposalDiscussionDetailsPage = + await proposalDiscussionPage.viewFirstProposal(); + + await proposalDiscussionDetailsPage.commentInput.fill( + faker.lorem.paragraph() + ); + + await expect(proposalDiscussionDetailsPage.likeBtn).toBeDisabled(); + await expect(proposalDiscussionDetailsPage.dislikeBtn).toBeDisabled(); + await expect(proposalDiscussionDetailsPage.commentBtn).toBeDisabled(); +}); + +test("8I. Should disable poll voting functionality.", async ({ + proposalDiscussionDetailsPage, +}) => { + await proposalDiscussionDetailsPage.goto(); + + await expect(proposalDiscussionDetailsPage.pollVoteCard).not.toBeVisible(); + await expect(proposalDiscussionDetailsPage.pollYesVoteCount).not.toBeVisible(); + + await expect(proposalDiscussionDetailsPage.pollNoVoteCount).not.toBeVisible(); + +}); From 46e796e8ea4c0ecd17287077c73b3df37fdcb58e Mon Sep 17 00:00:00 2001 From: niraj Date: Thu, 13 Jun 2024 09:33:00 +0545 Subject: [PATCH 18/49] chore: Make types first character capital --- .../playwright/lib/_mock/index.ts | 4 +-- .../fixtures/proposalDiscussionDetailsPage.ts | 4 +-- .../playwright/lib/proposalManager.ts | 4 +-- .../lib/services/proposalDiscussionService.ts | 28 +++++++++---------- .../govtool-frontend/playwright/lib/types.ts | 23 ++++++++------- .../playwright/tests/proposal.setup.ts | 22 +++++++-------- 6 files changed, 40 insertions(+), 45 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/_mock/index.ts b/tests/govtool-frontend/playwright/lib/_mock/index.ts index a4d2836cf..3c961b1ce 100644 --- a/tests/govtool-frontend/playwright/lib/_mock/index.ts +++ b/tests/govtool-frontend/playwright/lib/_mock/index.ts @@ -1,5 +1,5 @@ import { faker } from "@faker-js/faker"; -import { proposalCreationPayload } from "@types"; +import { ProposalCreationPayload } from "@types"; import { bech32 } from "bech32"; export const invalid = { @@ -78,7 +78,7 @@ const randomBytes = new Uint8Array(10); const bech32Address = bech32.encode("addr_test", randomBytes); export const mockProposalCreationPayload: { - data: proposalCreationPayload; + data: ProposalCreationPayload; } = { data: { proposal_links: [ diff --git a/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts index cc53d5ed2..4ff07c6e2 100644 --- a/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts @@ -2,13 +2,13 @@ import { mockProposalCreationPayload } from "@mock/index"; import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; import { test as base } from "@fixtures/walletExtension"; import proposalDiscussionService from "@services/proposalDiscussionService"; -import { proposalCreationResponse } from "@types"; +import { ProposalCreationResponse } from "@types"; export const test = base.extend<{ proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; }>({ proposalDiscussionDetailsPage: async ({ page }, use) => { - const response: proposalCreationResponse = + const response: ProposalCreationResponse = await proposalDiscussionService.createProposal( mockProposalCreationPayload ); diff --git a/tests/govtool-frontend/playwright/lib/proposalManager.ts b/tests/govtool-frontend/playwright/lib/proposalManager.ts index d00f41304..2dec757b3 100644 --- a/tests/govtool-frontend/playwright/lib/proposalManager.ts +++ b/tests/govtool-frontend/playwright/lib/proposalManager.ts @@ -1,4 +1,4 @@ -import { proposalCreationPayload } from "@types"; +import { ProposalCreationPayload } from "@types"; import * as fs from "fs"; const path = require("path"); @@ -48,7 +48,7 @@ class ProposalManager { return proposal["response"]["data"]["attributes"]["proposal_id"]; } - async getProposalPayload(): Promise { + async getProposalPayload(): Promise { const proposal = await this.readProposal(); return proposal["payload"]["data"]; } diff --git a/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts b/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts index bcfa420fa..ffaf6a7c8 100644 --- a/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts +++ b/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts @@ -1,11 +1,11 @@ import environments from "@constants/environments"; import { Logger } from "@helpers/logger"; -import { addPollPayload, proposalCreationPayload } from "@types"; +import { AddPollPayload, ProposalCreationPayload } from "@types"; import fetch = require("node-fetch"); const proposalDiscussionService = { - createProposal: async (data: { data: proposalCreationPayload }) => { + createProposal: async (data: { data: ProposalCreationPayload }) => { try { const res = await fetch(`${environments.pdfUrl}/api/proposals`, { method: "POST", @@ -18,15 +18,14 @@ const proposalDiscussionService = { }); const response = await res.json(); - if(res.status === 200){ + if (res.status === 200) { console.log(JSON.stringify(data)); Logger.success("Governance action proposal created successfully"); return response; } Logger.fail("Failed to create governance action proposal"); - throw new Error(response['error']['message']); - + throw new Error(response["error"]["message"]); } catch (err) { Logger.fail("Failed to create governance action proposal"); throw err; @@ -52,26 +51,25 @@ const proposalDiscussionService = { throw err; } }, - - addPoll:async(data:addPollPayload)=>{ - try{ - const res = await fetch(`${environments.pdfUrl}/api/polls`,{ - method:"POST", + + addPoll: async (data: AddPollPayload) => { + try { + const res = await fetch(`${environments.pdfUrl}/api/polls`, { + method: "POST", headers: { "content-type": "application/json", authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", }, - body:JSON.stringify(data), - }) + body: JSON.stringify(data), + }); await res.json(); Logger.success("Poll added successfully"); - } catch (err) { + } catch (err) { Logger.fail("Failed to delete governance action proposal"); throw err; } - - } + }, }; export default proposalDiscussionService; diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts index 08c07e853..d501c2213 100644 --- a/tests/govtool-frontend/playwright/lib/types.ts +++ b/tests/govtool-frontend/playwright/lib/types.ts @@ -98,8 +98,8 @@ export type ProtocolParams = { govActionDeposit: number; }; -export type proposalCreationPayload = { - proposal_links: Array; +export type ProposalCreationPayload = { + proposal_links: Array; gov_action_type_id: number; prop_name: string; prop_abstract: string; @@ -110,12 +110,12 @@ export type proposalCreationPayload = { is_draft: boolean; }; -type proposalLinksType = { +type ProposalLinksType = { prop_link: string; prop_link_text: string; }; -export type proposalCreationResponse = { +export type ProposalCreationResponse = { data: { attributes: { proposal_id: number; @@ -125,11 +125,10 @@ export type proposalCreationResponse = { meta: Object; }; - -export type addPollPayload = { - data: { - proposal_id: string, - poll_start_dt: string, - is_poll_active: boolean - } -} +export type AddPollPayload = { + data: { + proposal_id: string; + poll_start_dt: string; + is_poll_active: boolean; + }; +}; diff --git a/tests/govtool-frontend/playwright/tests/proposal.setup.ts b/tests/govtool-frontend/playwright/tests/proposal.setup.ts index 157f85524..2bc8542a0 100644 --- a/tests/govtool-frontend/playwright/tests/proposal.setup.ts +++ b/tests/govtool-frontend/playwright/tests/proposal.setup.ts @@ -5,7 +5,7 @@ import { pollTransaction } from "@helpers/transaction"; import { test as setup } from "@playwright/test"; import kuberService from "@services/kuberService"; import proposalDiscussionService from "@services/proposalDiscussionService"; -import { addPollPayload, proposalCreationResponse } from "@types"; +import { AddPollPayload, ProposalCreationResponse } from "@types"; import walletManager from "lib/walletManager"; import { mockProposalCreationPayload } from "@mock/index"; import proposalManager from "lib/proposalManager"; @@ -52,23 +52,21 @@ setup("Setup temporary proposal wallets", async () => { ); }); - setup("Create temporary proposal", async () => { - const response: proposalCreationResponse = + const response: ProposalCreationResponse = await proposalDiscussionService.createProposal(mockProposalCreationPayload); - const mockAddPollPayload:addPollPayload={ - data:{ - proposal_id: response.data.attributes.proposal_id.toString(), - poll_start_dt:new Date().toISOString(), - is_poll_active:true - } - } - await proposalDiscussionService.addPoll(mockAddPollPayload) + const mockAddPollPayload: AddPollPayload = { + data: { + proposal_id: response.data.attributes.proposal_id.toString(), + poll_start_dt: new Date().toISOString(), + is_poll_active: true, + }, + }; + await proposalDiscussionService.addPoll(mockAddPollPayload); await proposalManager.writeProposal({ payload: mockProposalCreationPayload, response: response, }); }); - From 64fc37cb9d19b7b19cbdf0c66ef288147f69c18e Mon Sep 17 00:00:00 2001 From: niraj Date: Thu, 13 Jun 2024 09:38:44 +0545 Subject: [PATCH 19/49] feat: Add proposal comment service and use it on temporary proposal creation --- .../lib/services/proposalDiscussionService.ts | 57 +++++++++++++++++-- .../govtool-frontend/playwright/lib/types.ts | 7 +++ .../playwright/tests/proposal.setup.ts | 18 +++++- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts b/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts index ffaf6a7c8..bd044a056 100644 --- a/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts +++ b/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts @@ -1,6 +1,10 @@ import environments from "@constants/environments"; import { Logger } from "@helpers/logger"; -import { AddPollPayload, ProposalCreationPayload } from "@types"; +import { + AddCommentPayload, + AddPollPayload, + ProposalCreationPayload, +} from "@types"; import fetch = require("node-fetch"); @@ -44,8 +48,15 @@ const proposalDiscussionService = { }, } ); - await res.json(); - Logger.success("Governance action proposal deleted successfully"); + + const response = await res.json(); + if (res.status === 200) { + Logger.success("Governance action proposal deleted successfully"); + return response; + } + + Logger.fail("Failed to delete governance action proposal"); + throw new Error(response["error"]["message"]); } catch (err) { Logger.fail("Failed to delete governance action proposal"); throw err; @@ -63,10 +74,44 @@ const proposalDiscussionService = { }, body: JSON.stringify(data), }); - await res.json(); - Logger.success("Poll added successfully"); + + const response = await res.json(); + console.log(response); + if (res.status === 200) { + Logger.success("Poll added successfully"); + return response; + } + + Logger.fail("Failed to add poll governance action proposal"); + throw new Error(response["error"]["message"]); } catch (err) { - Logger.fail("Failed to delete governance action proposal"); + Logger.fail("Failed to add poll governance action proposal"); + throw err; + } + }, + + addComment: async (data: AddCommentPayload) => { + try { + const res = await fetch(`${environments.pdfUrl}/api/comments`, { + method: "POST", + headers: { + "content-type": "application/json", + authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", + }, + body: JSON.stringify(data), + }); + + const response = await res.json(); + if (res.status === 200) { + Logger.success("Comment added successfully"); + return response; + } + + Logger.fail("Failed to add comment on governance action proposal"); + throw new Error(response["error"]["message"]); + } catch (err) { + Logger.fail("Failed to add comment on governance action proposal"); throw err; } }, diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts index d501c2213..a33f9497e 100644 --- a/tests/govtool-frontend/playwright/lib/types.ts +++ b/tests/govtool-frontend/playwright/lib/types.ts @@ -132,3 +132,10 @@ export type AddPollPayload = { is_poll_active: boolean; }; }; + +export type AddCommentPayload = { + data: { + proposal_id: string; + comment_text: string; + }; +}; diff --git a/tests/govtool-frontend/playwright/tests/proposal.setup.ts b/tests/govtool-frontend/playwright/tests/proposal.setup.ts index 2bc8542a0..cc0aafe4d 100644 --- a/tests/govtool-frontend/playwright/tests/proposal.setup.ts +++ b/tests/govtool-frontend/playwright/tests/proposal.setup.ts @@ -5,10 +5,15 @@ import { pollTransaction } from "@helpers/transaction"; import { test as setup } from "@playwright/test"; import kuberService from "@services/kuberService"; import proposalDiscussionService from "@services/proposalDiscussionService"; -import { AddPollPayload, ProposalCreationResponse } from "@types"; +import { + AddCommentPayload, + AddPollPayload, + ProposalCreationResponse, +} from "@types"; import walletManager from "lib/walletManager"; import { mockProposalCreationPayload } from "@mock/index"; import proposalManager from "lib/proposalManager"; +import { faker } from "@faker-js/faker"; const PROPOSAL_SUBMISSIONS_WALLETS_COUNT = 1; @@ -65,6 +70,17 @@ setup("Create temporary proposal", async () => { }; await proposalDiscussionService.addPoll(mockAddPollPayload); + for (let i = 0; i < 4; i++) { + const comment: AddCommentPayload = { + data: { + proposal_id: response.data.attributes.proposal_id.toString(), + comment_text: faker.lorem.paragraph(2), + }, + }; + + await proposalDiscussionService.addComment(comment); + } + await proposalManager.writeProposal({ payload: mockProposalCreationPayload, response: response, From fc16116deb22ea57066f37fe707a202b75396eab Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Thu, 13 Jun 2024 12:04:58 +0545 Subject: [PATCH 20/49] chore: Add helper for create file --- .../playwright/lib/helpers/file.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/govtool-frontend/playwright/lib/helpers/file.ts diff --git a/tests/govtool-frontend/playwright/lib/helpers/file.ts b/tests/govtool-frontend/playwright/lib/helpers/file.ts new file mode 100644 index 000000000..b3782dbac --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/file.ts @@ -0,0 +1,21 @@ +import { writeFile } from "fs"; + +const path = require("path"); + +const baseFilePath = path.resolve(__dirname, "./_mock"); + +export async function createFile(fileName: string, data?: any) { + await new Promise((resolve, reject) => + writeFile( + `${baseFilePath}/${fileName}`, + JSON.stringify(data, null, 2), + (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + } + ) + ); +} From d0031e99340d532bf48a0c8c0736f9f509eecb5f Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Thu, 13 Jun 2024 12:07:12 +0545 Subject: [PATCH 21/49] chore: Refactor proposal-setup to create static proposals --- tests/govtool-frontend/playwright/.gitignore | 1 + .../lib/constants/staticProposals.ts | 5 +++++ .../govtool-frontend/playwright/lib/types.ts | 2 +- .../playwright/tests/proposal.setup.ts | 20 +++++++------------ 4 files changed, 14 insertions(+), 14 deletions(-) create mode 100644 tests/govtool-frontend/playwright/lib/constants/staticProposals.ts diff --git a/tests/govtool-frontend/playwright/.gitignore b/tests/govtool-frontend/playwright/.gitignore index 28723c962..38697b044 100644 --- a/tests/govtool-frontend/playwright/.gitignore +++ b/tests/govtool-frontend/playwright/.gitignore @@ -16,4 +16,5 @@ allure-report/ lib/_mock/registerDRepWallets.json lib/_mock/registeredDRepWallets.json lib/_mock/wallets.json +lib/_mock/proposals.json ./lock_logs.txt diff --git a/tests/govtool-frontend/playwright/lib/constants/staticProposals.ts b/tests/govtool-frontend/playwright/lib/constants/staticProposals.ts new file mode 100644 index 000000000..ae30d5f7a --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/constants/staticProposals.ts @@ -0,0 +1,5 @@ +import { StaticProposal } from "@types"; + +const staticProposals: StaticProposal[] = require("../_mock/proposals.json"); + +export const proposal01 = staticProposals[0]; diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts index a33f9497e..524709e00 100644 --- a/tests/govtool-frontend/playwright/lib/types.ts +++ b/tests/govtool-frontend/playwright/lib/types.ts @@ -115,7 +115,7 @@ type ProposalLinksType = { prop_link_text: string; }; -export type ProposalCreationResponse = { +export type StaticProposal = { data: { attributes: { proposal_id: number; diff --git a/tests/govtool-frontend/playwright/tests/proposal.setup.ts b/tests/govtool-frontend/playwright/tests/proposal.setup.ts index cc0aafe4d..3850a8d79 100644 --- a/tests/govtool-frontend/playwright/tests/proposal.setup.ts +++ b/tests/govtool-frontend/playwright/tests/proposal.setup.ts @@ -1,19 +1,15 @@ import environments from "@constants/environments"; +import { faker } from "@faker-js/faker"; import { setAllureEpic, setAllureStory } from "@helpers/allure"; import { ShelleyWallet } from "@helpers/crypto"; +import { createFile } from "@helpers/file"; import { pollTransaction } from "@helpers/transaction"; +import { mockProposalCreationPayload } from "@mock/index"; import { test as setup } from "@playwright/test"; import kuberService from "@services/kuberService"; import proposalDiscussionService from "@services/proposalDiscussionService"; -import { - AddCommentPayload, - AddPollPayload, - ProposalCreationResponse, -} from "@types"; +import { AddCommentPayload, AddPollPayload, StaticProposal } from "@types"; import walletManager from "lib/walletManager"; -import { mockProposalCreationPayload } from "@mock/index"; -import proposalManager from "lib/proposalManager"; -import { faker } from "@faker-js/faker"; const PROPOSAL_SUBMISSIONS_WALLETS_COUNT = 1; @@ -58,7 +54,7 @@ setup("Setup temporary proposal wallets", async () => { }); setup("Create temporary proposal", async () => { - const response: ProposalCreationResponse = + const response: StaticProposal = await proposalDiscussionService.createProposal(mockProposalCreationPayload); const mockAddPollPayload: AddPollPayload = { @@ -81,8 +77,6 @@ setup("Create temporary proposal", async () => { await proposalDiscussionService.addComment(comment); } - await proposalManager.writeProposal({ - payload: mockProposalCreationPayload, - response: response, - }); + const data = [{ payload: mockProposalCreationPayload, response: response }]; + await createFile("proposals.json", data); }); From e04b7faa4c276f3bfbdec8e528f43f8e996d5c8b Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Thu, 13 Jun 2024 12:09:24 +0545 Subject: [PATCH 22/49] test: Share proposed governance action and proposal discussions accessibility(disconnected state) --- .../proposalDiscussion.proposal.spec.ts | 23 +++++++++++++++++++ .../proposalDiscussion.spec.ts | 14 ++--------- 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts new file mode 100644 index 000000000..25b05d091 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts @@ -0,0 +1,23 @@ +import { proposal01 } from "@constants/staticProposals"; +import { test } from "@fixtures/proposalDiscussionDetailsPage"; +import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; +import { expect } from "@playwright/test"; + +test("8E. Should share proposed governance action", async ({ + page, + context, +}) => { + const proposalId = proposal01.data.attributes.proposal_id; + await context.grantPermissions(["clipboard-read", "clipboard-write"]); + + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + const proposalCard = page.getByTestId(`proposal-${proposalId}`); + await proposalCard.getByTestId("share-button").click(); + await expect(page.getByText("Copied to clipboard")).toBeVisible(); + const copiedTextDRepDirectory = await page.evaluate(() => + navigator.clipboard.readText() + ); + expect(copiedTextDRepDirectory).toEqual(proposalId); +}); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts index 158fcfae7..fd8a04c45 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts @@ -7,21 +7,11 @@ test.beforeEach(() => { setAllureEpic("Proposal Discussion Forum"); }); -test("8A. Should access the list of proposed governance actions in a disconnected state.", async ({ +test("8A. Should access proposed governance actions in disconnected state", async ({ page, }) => { const proposalDiscussionPage = new ProposalDiscussionPage(page); await proposalDiscussionPage.goto(); - await expect(proposalDiscussionPage.searchInput).toBeVisible(); - await expect(page.getByText(/Proposal Discussion/i)).toHaveCount(2); -}); - -test("8E.Should share proposed governance actions.", async ({ page }) => { - const proposalDiscussionPage = new ProposalDiscussionPage(page); - await proposalDiscussionPage.goto(); - - const proposalCard = await proposalDiscussionPage.getFirstProposal(); - - await proposalCard.getByTestId("share-button").click(); + await expect(page.getByText(/Proposed Governance Actions/i)).toHaveCount(2); }); From 4a5115fff3b0bc69a7a0acdf755097fa40d3ad42 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Thu, 13 Jun 2024 12:51:04 +0545 Subject: [PATCH 23/49] chore: Refactor proposal discussion service and creation flow --- .../playwright/lib/_mock/index.ts | 26 ---- .../fixtures/proposalDiscussionDetailsPage.ts | 49 +++++-- .../playwright/lib/helpers/cardano.ts | 7 + .../pages/proposalDiscussionDetailsPage.ts | 12 +- .../lib/services/proposalDiscussion/index.ts | 84 ++++++++++++ .../lib/services/proposalDiscussion/types.ts | 27 ++++ .../lib/services/proposalDiscussionService.ts | 120 ------------------ .../govtool-frontend/playwright/lib/types.ts | 42 +----- .../proposalDiscussion.loggedin.spec.ts | 3 - .../proposalDiscussion.proposal.spec.ts | 5 +- .../proposalDiscussionVisibility.spec.ts | 10 +- .../playwright/tests/proposal.setup.ts | 68 +++++++--- .../playwright/tests/proposal.teardown.ts | 4 +- 13 files changed, 215 insertions(+), 242 deletions(-) create mode 100644 tests/govtool-frontend/playwright/lib/services/proposalDiscussion/index.ts create mode 100644 tests/govtool-frontend/playwright/lib/services/proposalDiscussion/types.ts diff --git a/tests/govtool-frontend/playwright/lib/_mock/index.ts b/tests/govtool-frontend/playwright/lib/_mock/index.ts index 3c961b1ce..b9c46e638 100644 --- a/tests/govtool-frontend/playwright/lib/_mock/index.ts +++ b/tests/govtool-frontend/playwright/lib/_mock/index.ts @@ -1,6 +1,4 @@ import { faker } from "@faker-js/faker"; -import { ProposalCreationPayload } from "@types"; -import { bech32 } from "bech32"; export const invalid = { url: () => { @@ -73,27 +71,3 @@ export const invalid = { return " "; }, }; - -const randomBytes = new Uint8Array(10); -const bech32Address = bech32.encode("addr_test", randomBytes); - -export const mockProposalCreationPayload: { - data: ProposalCreationPayload; -} = { - data: { - proposal_links: [ - { - prop_link: faker.internet.url(), - prop_link_text: faker.internet.displayName(), - }, - ], - gov_action_type_id: 1, - prop_name: faker.company.name(), - prop_abstract: faker.lorem.paragraph(2), - prop_motivation: faker.lorem.paragraph(2), - prop_rationale: faker.lorem.paragraph(2), - prop_receiving_address: bech32Address, - prop_amount: faker.number.int({ min: 100, max: 1000 }).toString(), - is_draft: false, - }, -}; diff --git a/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts index 4ff07c6e2..5a18040a1 100644 --- a/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts @@ -1,26 +1,47 @@ -import { mockProposalCreationPayload } from "@mock/index"; -import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; +import { faker } from "@faker-js/faker"; import { test as base } from "@fixtures/walletExtension"; -import proposalDiscussionService from "@services/proposalDiscussionService"; -import { ProposalCreationResponse } from "@types"; +import { generateWalletAddress } from "@helpers/cardano"; +import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; +import { + deleteProposal, + postCreateProposal, +} from "@services/proposalDiscussion"; +import { ProposalCreateRequest } from "@services/proposalDiscussion/types"; export const test = base.extend<{ proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; }>({ + // setup proposalDiscussionDetailsPage: async ({ page }, use) => { - const response: ProposalCreationResponse = - await proposalDiscussionService.createProposal( - mockProposalCreationPayload - ); + const receivingAddr = generateWalletAddress(); + const proposalRequest: ProposalCreateRequest = { + proposal_links: [ + { + prop_link: faker.internet.url(), + prop_link_text: faker.internet.displayName(), + }, + ], + gov_action_type_id: 1, + prop_name: faker.company.name(), + prop_abstract: faker.lorem.paragraph(2), + prop_motivation: faker.lorem.paragraph(2), + prop_rationale: faker.lorem.paragraph(2), + prop_receiving_address: receivingAddr, + prop_amount: faker.number.int({ min: 100, max: 1000 }).toString(), + is_draft: false, + }; + + const createProposalRes = await postCreateProposal(proposalRequest); - const proposalId = response.data.attributes.proposal_id; - const proposalDiscussionDetailPage = new ProposalDiscussionDetailsPage( - page, - proposalId + const proposalId = createProposalRes.data.attributes.proposal_id; + const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( + page ); + await proposalDiscussionDetailsPage.goto(proposalId); - await use(proposalDiscussionDetailPage); + await use(proposalDiscussionDetailsPage); - await proposalDiscussionService.deleteProposal(proposalId); + // cleanup + await deleteProposal(proposalId); }, }); diff --git a/tests/govtool-frontend/playwright/lib/helpers/cardano.ts b/tests/govtool-frontend/playwright/lib/helpers/cardano.ts index 3718d13a3..42eb3bd4e 100644 --- a/tests/govtool-frontend/playwright/lib/helpers/cardano.ts +++ b/tests/govtool-frontend/playwright/lib/helpers/cardano.ts @@ -1,5 +1,12 @@ +import { bech32 } from "bech32"; + export function lovelaceToAda(lovelace: number) { if (lovelace === 0) return 0; return lovelace / 1e6; } + +export function generateWalletAddress() { + const randomBytes = new Uint8Array(10); + return bech32.encode("addr_test", randomBytes); +} diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts index 587f226be..594759ef3 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts @@ -2,7 +2,6 @@ import environments from "@constants/environments"; import { Page } from "@playwright/test"; export default class ProposalDiscussionDetailsPage { - private readonly proposalId: number | null; // Buttons readonly likeBtn = this.page.getByTestId("like-button"); readonly dislikeBtn = this.page.getByTestId("dislike-button"); @@ -44,20 +43,15 @@ export default class ProposalDiscussionDetailsPage { readonly commentCard = this.proposeGovernanceAction.getByTestId("comment-card"); - constructor( - private readonly page: Page, - proposalId?: number - ) { - this.proposalId = proposalId; - } + constructor(private readonly page: Page) {} get currentPage(): Page { return this.page; } - async goto(proposalId?: number) { + async goto(proposalId: number) { await this.page.goto( - `${environments.frontendUrl}/proposal_discussion/${proposalId ?? this.proposalId}` + `${environments.frontendUrl}/proposal_discussion/${proposalId}` ); } } diff --git a/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/index.ts b/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/index.ts new file mode 100644 index 000000000..d8f7d1c76 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/index.ts @@ -0,0 +1,84 @@ +import environments from "@constants/environments"; +import { Logger } from "@helpers/logger"; +import { RequestInit } from "node-fetch"; +import { CommentRequest, PollRequest, ProposalCreateRequest } from "./types"; + +import fetch = require("node-fetch"); + +export const postCreateProposal = async (data: ProposalCreateRequest) => { + const endpoint = "/proposals"; + const options: RequestInit = { + method: "POST", + headers: { + "content-type": "application/json", + authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", + }, + body: JSON.stringify({ data: { data } }), + }; + + return fetchClient(endpoint, options); +}; + +export const deleteProposal = async (proposalId: number) => { + const endpoint = `/proposals/${proposalId}`; + const options: RequestInit = { + method: "DELETE", + headers: { + "content-type": "application/json", + authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", + }, + }; + + return fetchClient(endpoint, options); +}; + +export const postAddPoll = async (data: PollRequest) => { + const endpoint = `/api/polls`; + const options: RequestInit = { + method: "DELETE", + headers: { + "content-type": "application/json", + authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", + }, + body: JSON.stringify({ data: { data } }), + }; + + return fetchClient(endpoint, options); +}; + +export const postAddComment = async (data: CommentRequest) => { + const endpoint = `/comments`; + const options = { + method: "POST", + headers: { + "content-type": "application/json", + authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", + }, + body: JSON.stringify({ data: { data } }), + }; + + return fetchClient(endpoint, options); +}; + +async function fetchClient( + endpoint: string, + options: RequestInit +): Promise { + try { + const res = await fetch(`${environments.pdfUrl}/api` + endpoint, options); + const response = await res.json(); + + if (res.ok) { + return response; + } else { + throw new Error(response.error?.message || "Unknown error occurred"); + } + } catch (error) { + Logger.fail(`API request failed: ${error.message}`); + throw error; + } +} diff --git a/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/types.ts b/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/types.ts new file mode 100644 index 000000000..36f1d5979 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/types.ts @@ -0,0 +1,27 @@ +type ProposalLinksType = { + prop_link: string; + prop_link_text: string; +}; + +export type ProposalCreateRequest = { + proposal_links: Array; + gov_action_type_id: number; + prop_name: string; + prop_abstract: string; + prop_motivation: string; + prop_rationale: string; + prop_receiving_address: string; + prop_amount: string; + is_draft: boolean; +}; + +export type CommentRequest = { + proposal_id: string; + comment_text: string; +}; + +export type PollRequest = { + proposal_id: string; + poll_start_dt: string; + is_poll_active: boolean; +}; diff --git a/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts b/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts index bd044a056..e69de29bb 100644 --- a/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts +++ b/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts @@ -1,120 +0,0 @@ -import environments from "@constants/environments"; -import { Logger } from "@helpers/logger"; -import { - AddCommentPayload, - AddPollPayload, - ProposalCreationPayload, -} from "@types"; - -import fetch = require("node-fetch"); - -const proposalDiscussionService = { - createProposal: async (data: { data: ProposalCreationPayload }) => { - try { - const res = await fetch(`${environments.pdfUrl}/api/proposals`, { - method: "POST", - headers: { - "content-type": "application/json", - authorization: - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", - }, - body: JSON.stringify(data), - }); - - const response = await res.json(); - if (res.status === 200) { - console.log(JSON.stringify(data)); - Logger.success("Governance action proposal created successfully"); - return response; - } - - Logger.fail("Failed to create governance action proposal"); - throw new Error(response["error"]["message"]); - } catch (err) { - Logger.fail("Failed to create governance action proposal"); - throw err; - } - }, - deleteProposal: async (proposalId: number) => { - try { - const res = await fetch( - `${environments.pdfUrl}/api/proposals/${proposalId}`, - { - method: "DELETE", - headers: { - "content-type": "application/json", - authorization: - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", - }, - } - ); - - const response = await res.json(); - if (res.status === 200) { - Logger.success("Governance action proposal deleted successfully"); - return response; - } - - Logger.fail("Failed to delete governance action proposal"); - throw new Error(response["error"]["message"]); - } catch (err) { - Logger.fail("Failed to delete governance action proposal"); - throw err; - } - }, - - addPoll: async (data: AddPollPayload) => { - try { - const res = await fetch(`${environments.pdfUrl}/api/polls`, { - method: "POST", - headers: { - "content-type": "application/json", - authorization: - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", - }, - body: JSON.stringify(data), - }); - - const response = await res.json(); - console.log(response); - if (res.status === 200) { - Logger.success("Poll added successfully"); - return response; - } - - Logger.fail("Failed to add poll governance action proposal"); - throw new Error(response["error"]["message"]); - } catch (err) { - Logger.fail("Failed to add poll governance action proposal"); - throw err; - } - }, - - addComment: async (data: AddCommentPayload) => { - try { - const res = await fetch(`${environments.pdfUrl}/api/comments`, { - method: "POST", - headers: { - "content-type": "application/json", - authorization: - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", - }, - body: JSON.stringify(data), - }); - - const response = await res.json(); - if (res.status === 200) { - Logger.success("Comment added successfully"); - return response; - } - - Logger.fail("Failed to add comment on governance action proposal"); - throw new Error(response["error"]["message"]); - } catch (err) { - Logger.fail("Failed to add comment on governance action proposal"); - throw err; - } - }, -}; - -export default proposalDiscussionService; diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts index 524709e00..a80e34b62 100644 --- a/tests/govtool-frontend/playwright/lib/types.ts +++ b/tests/govtool-frontend/playwright/lib/types.ts @@ -98,44 +98,12 @@ export type ProtocolParams = { govActionDeposit: number; }; -export type ProposalCreationPayload = { - proposal_links: Array; - gov_action_type_id: number; - prop_name: string; - prop_abstract: string; - prop_motivation: string; - prop_rationale: string; - prop_receiving_address: string; - prop_amount: string; - is_draft: boolean; -}; - -type ProposalLinksType = { - prop_link: string; - prop_link_text: string; +type Comment = { + proposal_id: string; + comment_text: string; }; export type StaticProposal = { - data: { - attributes: { - proposal_id: number; - proposal_content_id: number; - }; - }; - meta: Object; -}; - -export type AddPollPayload = { - data: { - proposal_id: string; - poll_start_dt: string; - is_poll_active: boolean; - }; -}; - -export type AddCommentPayload = { - data: { - proposal_id: string; - comment_text: string; - }; + id: number; + comments?: Comment[]; }; diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts index 976e2e9a9..ffd97f7bb 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -4,12 +4,9 @@ import { expect } from "@playwright/test"; test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); - test("8G. Should display the proper likes and dislikes count.", async ({ proposalDiscussionDetailsPage, }) => { - await proposalDiscussionDetailsPage.goto(); - await proposalDiscussionDetailsPage.likeBtn.click(); await expect(proposalDiscussionDetailsPage.likesCounts).toHaveValue("1"); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts index 25b05d091..1d8b6fa3b 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts @@ -7,17 +7,16 @@ test("8E. Should share proposed governance action", async ({ page, context, }) => { - const proposalId = proposal01.data.attributes.proposal_id; await context.grantPermissions(["clipboard-read", "clipboard-write"]); const proposalDiscussionPage = new ProposalDiscussionPage(page); await proposalDiscussionPage.goto(); - const proposalCard = page.getByTestId(`proposal-${proposalId}`); + const proposalCard = page.getByTestId(`proposal-${proposal01.id}`); await proposalCard.getByTestId("share-button").click(); await expect(page.getByText("Copied to clipboard")).toBeVisible(); const copiedTextDRepDirectory = await page.evaluate(() => navigator.clipboard.readText() ); - expect(copiedTextDRepDirectory).toEqual(proposalId); + expect(copiedTextDRepDirectory).toEqual(proposal01.id); }); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts index 090519acc..9ccb238f5 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts @@ -18,7 +18,6 @@ test("8B. Should filter and sort the list of proposed governance actions.", asyn await proposalDiscussionPage.treasuryRadio.click(); }); - test("8C. Should search the list of proposed governance actions.", async ({ page, }) => { @@ -39,7 +38,6 @@ test("8C. Should search the list of proposed governance actions.", async ({ } }); - test("8D.Should show the view-all categorized proposed governance actions.", async ({ page, }) => { @@ -51,7 +49,6 @@ test("8D.Should show the view-all categorized proposed governance actions.", asy await expect(proposalDiscussionPage.showLessBtn).toBeVisible(); }); - test("8H. Should disable proposal interaction on a disconnected state.", async ({ page, }) => { @@ -73,11 +70,10 @@ test("8H. Should disable proposal interaction on a disconnected state.", async ( test("8I. Should disable poll voting functionality.", async ({ proposalDiscussionDetailsPage, }) => { - await proposalDiscussionDetailsPage.goto(); - await expect(proposalDiscussionDetailsPage.pollVoteCard).not.toBeVisible(); - await expect(proposalDiscussionDetailsPage.pollYesVoteCount).not.toBeVisible(); + await expect( + proposalDiscussionDetailsPage.pollYesVoteCount + ).not.toBeVisible(); await expect(proposalDiscussionDetailsPage.pollNoVoteCount).not.toBeVisible(); - }); diff --git a/tests/govtool-frontend/playwright/tests/proposal.setup.ts b/tests/govtool-frontend/playwright/tests/proposal.setup.ts index 3850a8d79..101a108a5 100644 --- a/tests/govtool-frontend/playwright/tests/proposal.setup.ts +++ b/tests/govtool-frontend/playwright/tests/proposal.setup.ts @@ -1,14 +1,22 @@ import environments from "@constants/environments"; import { faker } from "@faker-js/faker"; import { setAllureEpic, setAllureStory } from "@helpers/allure"; +import { generateWalletAddress } from "@helpers/cardano"; import { ShelleyWallet } from "@helpers/crypto"; import { createFile } from "@helpers/file"; import { pollTransaction } from "@helpers/transaction"; -import { mockProposalCreationPayload } from "@mock/index"; import { test as setup } from "@playwright/test"; import kuberService from "@services/kuberService"; -import proposalDiscussionService from "@services/proposalDiscussionService"; -import { AddCommentPayload, AddPollPayload, StaticProposal } from "@types"; +import { + postAddComment, + postAddPoll, + postCreateProposal, +} from "@services/proposalDiscussion"; +import { + CommentRequest, + ProposalCreateRequest, +} from "@services/proposalDiscussion/types"; +import { StaticProposal } from "@types"; import walletManager from "lib/walletManager"; const PROPOSAL_SUBMISSIONS_WALLETS_COUNT = 1; @@ -54,29 +62,47 @@ setup("Setup temporary proposal wallets", async () => { }); setup("Create temporary proposal", async () => { - const response: StaticProposal = - await proposalDiscussionService.createProposal(mockProposalCreationPayload); - - const mockAddPollPayload: AddPollPayload = { - data: { - proposal_id: response.data.attributes.proposal_id.toString(), - poll_start_dt: new Date().toISOString(), - is_poll_active: true, - }, + const receivingAddr = generateWalletAddress(); + const proposalRequest: ProposalCreateRequest = { + proposal_links: [ + { + prop_link: faker.internet.url(), + prop_link_text: faker.internet.displayName(), + }, + ], + gov_action_type_id: 1, + prop_name: faker.company.name(), + prop_abstract: faker.lorem.paragraph(2), + prop_motivation: faker.lorem.paragraph(2), + prop_rationale: faker.lorem.paragraph(2), + prop_receiving_address: receivingAddr, + prop_amount: faker.number.int({ min: 100, max: 1000 }).toString(), + is_draft: false, }; - await proposalDiscussionService.addPoll(mockAddPollPayload); + + const createProposalRes = await postCreateProposal(proposalRequest); + + await postAddPoll({ + proposal_id: createProposalRes.data.attributes.proposal_id.toString(), + poll_start_dt: new Date().toISOString(), + is_poll_active: true, + }); + + const comments: CommentRequest[] = []; for (let i = 0; i < 4; i++) { - const comment: AddCommentPayload = { - data: { - proposal_id: response.data.attributes.proposal_id.toString(), - comment_text: faker.lorem.paragraph(2), - }, + const comment: CommentRequest = { + proposal_id: createProposalRes.data.attributes.proposal_id.toString(), + comment_text: faker.lorem.paragraph(2), }; + comments.push(comment); - await proposalDiscussionService.addComment(comment); + await postAddComment(comment); } - const data = [{ payload: mockProposalCreationPayload, response: response }]; - await createFile("proposals.json", data); + const staticProposal: StaticProposal = { + id: createProposalRes.data.attributes.proposal_id, + comments, + }; + await createFile("proposals.json", [staticProposal]); }); diff --git a/tests/govtool-frontend/playwright/tests/proposal.teardown.ts b/tests/govtool-frontend/playwright/tests/proposal.teardown.ts index 2cf4db6f4..e6345fdf1 100644 --- a/tests/govtool-frontend/playwright/tests/proposal.teardown.ts +++ b/tests/govtool-frontend/playwright/tests/proposal.teardown.ts @@ -1,8 +1,8 @@ import { test as cleanup } from "@playwright/test"; -import proposalDiscussionService from "@services/proposalDiscussionService"; +import { deleteProposal } from "@services/proposalDiscussion"; import proposalManager from "lib/proposalManager"; cleanup("Delete Proposal", async () => { const proposalId = await proposalManager.getProposalId(); - await proposalDiscussionService.deleteProposal(proposalId); + await deleteProposal(proposalId); }); From 779f5a6770936a2163ff8d8116e744bede5cf429 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Thu, 13 Jun 2024 16:27:34 +0545 Subject: [PATCH 24/49] test: Poll voting and Proposal creation restriction --- .../lib/pages/proposalDiscussionPage.ts | 4 + .../proposalDiscussion.loggedin.spec.ts | 2 +- .../proposalDiscussion.spec.ts | 85 +++++++++++++++++++ .../proposalDiscussionVisibility.spec.ts | 79 ----------------- 4 files changed, 90 insertions(+), 80 deletions(-) delete mode 100644 tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts index 74fa75623..63ab45887 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts @@ -4,6 +4,10 @@ import ProposalDiscussionDetailsPage from "./proposalDiscussionDetailsPage"; export default class ProposalDiscussionPage { // Buttons + readonly proposalCreateBtn = this.page.getByRole("button", { + name: "Propose a Governance Action", + }); + readonly filterBtn = this.page.locator("#filters-button"); // this.page.getByTestId("filters-button"); readonly shareBtn = this.page .locator(".MuiCardHeader-action > .MuiButtonBase-root") diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts index ffd97f7bb..fc842ccae 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -4,7 +4,7 @@ import { expect } from "@playwright/test"; test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); -test("8G. Should display the proper likes and dislikes count.", async ({ +test("8G. Should display the proper likes and dislikes count", async ({ proposalDiscussionDetailsPage, }) => { await proposalDiscussionDetailsPage.likeBtn.click(); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts index fd8a04c45..94144488b 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts @@ -1,7 +1,9 @@ +import { faker } from "@faker-js/faker"; import { test } from "@fixtures/proposalDiscussionDetailsPage"; import { setAllureEpic } from "@helpers/allure"; import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; import { expect } from "@playwright/test"; +import proposalManager from "lib/proposalManager"; test.beforeEach(() => { setAllureEpic("Proposal Discussion Forum"); @@ -15,3 +17,86 @@ test("8A. Should access proposed governance actions in disconnected state", asyn await expect(page.getByText(/Proposed Governance Actions/i)).toHaveCount(2); }); + +test("8B. Should filter and sort the list of proposed governance actions.", async ({ + page, +}) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + await proposalDiscussionPage.filterBtn.click(); + await proposalDiscussionPage.infoRadio.click(); + + await expect(page.getByText("Treasury")).toHaveCount(1); + + await proposalDiscussionPage.treasuryRadio.click(); +}); + +test("8C. Should search the list of proposed governance actions.", async ({ + page, +}) => { + const proposalPayload = await proposalManager.getProposalPayload(); + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + await proposalDiscussionPage.searchInput.fill(proposalPayload.prop_name); + + const proposalCards = await proposalDiscussionPage.getAllProposals(); + + for (const proposalCard of proposalCards) { + expect( + (await proposalCard.textContent()) + .toLowerCase() + .includes(`${proposalPayload.prop_name}`) + ).toBeTruthy(); + } +}); + +test("8D.Should show the view-all categorized proposed governance actions.", async ({ + page, +}) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + await proposalDiscussionPage.showAllBtn.click(); + + await expect(proposalDiscussionPage.showLessBtn).toBeVisible(); +}); + +test("8H. Should disable proposal interaction on a disconnected state.", async ({ + page, +}) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + const proposalDiscussionDetailsPage = + await proposalDiscussionPage.viewFirstProposal(); + + await proposalDiscussionDetailsPage.commentInput.fill( + faker.lorem.paragraph() + ); + + await expect(proposalDiscussionDetailsPage.likeBtn).toBeDisabled(); + await expect(proposalDiscussionDetailsPage.dislikeBtn).toBeDisabled(); + await expect(proposalDiscussionDetailsPage.commentBtn).toBeDisabled(); +}); + +test("8I. Should disable poll voting functionality.", async ({ + proposalDiscussionDetailsPage, +}) => { + await expect(proposalDiscussionDetailsPage.pollVoteCard).not.toBeVisible(); + await expect( + proposalDiscussionDetailsPage.pollYesVoteCount + ).not.toBeVisible(); + + await expect(proposalDiscussionDetailsPage.pollNoVoteCount).not.toBeVisible(); +}); + +test("8R. Should restrict proposal creation on disconnected state", async ({ + page, +}) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + + await expect(proposalDiscussionPage.proposalCreateBtn).not.toBeVisible(); +}); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts deleted file mode 100644 index 9ccb238f5..000000000 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussionVisibility.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { faker } from "@faker-js/faker"; -import { test } from "@fixtures/proposalDiscussionDetailsPage"; -import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; -import { expect } from "@playwright/test"; -import proposalManager from "lib/proposalManager"; - -test("8B. Should filter and sort the list of proposed governance actions.", async ({ - page, -}) => { - const proposalDiscussionPage = new ProposalDiscussionPage(page); - await proposalDiscussionPage.goto(); - - await proposalDiscussionPage.filterBtn.click(); - await proposalDiscussionPage.infoRadio.click(); - - await expect(page.getByText("Treasury")).toHaveCount(1); - - await proposalDiscussionPage.treasuryRadio.click(); -}); - -test("8C. Should search the list of proposed governance actions.", async ({ - page, -}) => { - const proposalPayload = await proposalManager.getProposalPayload(); - const proposalDiscussionPage = new ProposalDiscussionPage(page); - await proposalDiscussionPage.goto(); - - await proposalDiscussionPage.searchInput.fill(proposalPayload.prop_name); - - const proposalCards = await proposalDiscussionPage.getAllProposals(); - - for (const proposalCard of proposalCards) { - expect( - (await proposalCard.textContent()) - .toLowerCase() - .includes(`${proposalPayload.prop_name}`) - ).toBeTruthy(); - } -}); - -test("8D.Should show the view-all categorized proposed governance actions.", async ({ - page, -}) => { - const proposalDiscussionPage = new ProposalDiscussionPage(page); - await proposalDiscussionPage.goto(); - - await proposalDiscussionPage.showAllBtn.click(); - - await expect(proposalDiscussionPage.showLessBtn).toBeVisible(); -}); - -test("8H. Should disable proposal interaction on a disconnected state.", async ({ - page, -}) => { - const proposalDiscussionPage = new ProposalDiscussionPage(page); - await proposalDiscussionPage.goto(); - - const proposalDiscussionDetailsPage = - await proposalDiscussionPage.viewFirstProposal(); - - await proposalDiscussionDetailsPage.commentInput.fill( - faker.lorem.paragraph() - ); - - await expect(proposalDiscussionDetailsPage.likeBtn).toBeDisabled(); - await expect(proposalDiscussionDetailsPage.dislikeBtn).toBeDisabled(); - await expect(proposalDiscussionDetailsPage.commentBtn).toBeDisabled(); -}); - -test("8I. Should disable poll voting functionality.", async ({ - proposalDiscussionDetailsPage, -}) => { - await expect(proposalDiscussionDetailsPage.pollVoteCard).not.toBeVisible(); - await expect( - proposalDiscussionDetailsPage.pollYesVoteCount - ).not.toBeVisible(); - - await expect(proposalDiscussionDetailsPage.pollNoVoteCount).not.toBeVisible(); -}); From 68ef3aae7d1abfe0675ee1769360f20402689d5a Mon Sep 17 00:00:00 2001 From: niraj Date: Thu, 13 Jun 2024 16:56:33 +0545 Subject: [PATCH 25/49] chore: Remove proposal manager --- .../playwright/lib/proposalManager.ts | 57 ------------------- .../proposalDiscussion.spec.ts | 7 +-- 2 files changed, 3 insertions(+), 61 deletions(-) delete mode 100644 tests/govtool-frontend/playwright/lib/proposalManager.ts diff --git a/tests/govtool-frontend/playwright/lib/proposalManager.ts b/tests/govtool-frontend/playwright/lib/proposalManager.ts deleted file mode 100644 index 2dec757b3..000000000 --- a/tests/govtool-frontend/playwright/lib/proposalManager.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ProposalCreationPayload } from "@types"; -import * as fs from "fs"; -const path = require("path"); - -const baseFilePath = path.resolve(__dirname, "./_mock"); - -class ProposalManager { - private static instance: ProposalManager; - - public static getInstance(): ProposalManager { - if (!ProposalManager.instance) { - ProposalManager.instance = new ProposalManager(); - } - return ProposalManager.instance; - } - - async writeProposal(response: Object) { - await new Promise((resolve, reject) => - fs.writeFile( - `${baseFilePath}/proposal.json`, - JSON.stringify(response, null, 2), - (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - } - ) - ); - } - - private async readProposal(): Promise { - const data: string = await new Promise((resolve, reject) => - fs.readFile(`${baseFilePath}/proposal.json`, "utf8", (err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }) - ); - return JSON.parse(data); - } - - async getProposalId(): Promise { - const proposal = await this.readProposal(); - return proposal["response"]["data"]["attributes"]["proposal_id"]; - } - - async getProposalPayload(): Promise { - const proposal = await this.readProposal(); - return proposal["payload"]["data"]; - } -} - -export default ProposalManager.getInstance(); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts index 94144488b..be2c229ce 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts @@ -1,9 +1,9 @@ +import { proposal01 } from "@constants/staticProposals"; import { faker } from "@faker-js/faker"; import { test } from "@fixtures/proposalDiscussionDetailsPage"; import { setAllureEpic } from "@helpers/allure"; import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; import { expect } from "@playwright/test"; -import proposalManager from "lib/proposalManager"; test.beforeEach(() => { setAllureEpic("Proposal Discussion Forum"); @@ -35,11 +35,10 @@ test("8B. Should filter and sort the list of proposed governance actions.", asyn test("8C. Should search the list of proposed governance actions.", async ({ page, }) => { - const proposalPayload = await proposalManager.getProposalPayload(); const proposalDiscussionPage = new ProposalDiscussionPage(page); await proposalDiscussionPage.goto(); - await proposalDiscussionPage.searchInput.fill(proposalPayload.prop_name); + await proposalDiscussionPage.searchInput.fill(proposal01.title); const proposalCards = await proposalDiscussionPage.getAllProposals(); @@ -47,7 +46,7 @@ test("8C. Should search the list of proposed governance actions.", async ({ expect( (await proposalCard.textContent()) .toLowerCase() - .includes(`${proposalPayload.prop_name}`) + .includes(`${proposal01.title}`) ).toBeTruthy(); } }); From 19dfcd614d386ee262c745780e82b8a568e27d02 Mon Sep 17 00:00:00 2001 From: niraj Date: Thu, 13 Jun 2024 16:58:23 +0545 Subject: [PATCH 26/49] test: Sort comment and disable poll voting --- .../pages/proposalDiscussionDetailsPage.ts | 32 ++++++++++++++++++- .../proposalDiscussion.proposal.spec.ts | 27 ++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts index 594759ef3..6a533e58c 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts @@ -1,5 +1,6 @@ import environments from "@constants/environments"; -import { Page } from "@playwright/test"; +import { Page, expect } from "@playwright/test"; +import { CommentResponse } from "@types"; export default class ProposalDiscussionDetailsPage { // Buttons @@ -24,6 +25,7 @@ export default class ProposalDiscussionDetailsPage { readonly replyBtn = this.page.getByTestId("reply-button"); readonly pollYesBtn = this.page.getByTestId("poll-yes-button"); readonly pollNoBtn = this.page.getByTestId("poll-No-button"); + readonly showReplyButton = this.page.getByTestId("show-more-reply"); // Inputs readonly commentInput = this.page.getByRole("textbox"); //this.page.getByTestId("comment-input"); @@ -54,4 +56,32 @@ export default class ProposalDiscussionDetailsPage { `${environments.frontendUrl}/proposal_discussion/${proposalId}` ); } + + async getFirstComment() { + await this.page.waitForTimeout(2_000); + return this.page.locator('[data-testid$="-comment-card"]').first(); + } + + async sortAndValidate( + order: string, + validationFn: (date1: string, date2: string) => boolean + ) { + const responsePromise = this.page.waitForResponse((response) => + response.url().includes(`&sort[createdAt]=${order}`) + ); + + await this.sortBtn.click(); + const response = await responsePromise; + + const comments: CommentResponse[] = (await response.json()).data; + + // API validation + for (let i = 0; i < comments.length - 1; i++) { + const isValid = validationFn( + comments[i].attributes.updatedAt, + comments[i + 1].attributes.updatedAt + ); + expect(isValid).toBe(true); + } + } } diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts index 1d8b6fa3b..344bad41f 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts @@ -1,5 +1,6 @@ import { proposal01 } from "@constants/staticProposals"; import { test } from "@fixtures/proposalDiscussionDetailsPage"; +import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; import { expect } from "@playwright/test"; @@ -12,6 +13,8 @@ test("8E. Should share proposed governance action", async ({ const proposalDiscussionPage = new ProposalDiscussionPage(page); await proposalDiscussionPage.goto(); + await proposalDiscussionPage.searchInput.fill(proposal01.title); + const proposalCard = page.getByTestId(`proposal-${proposal01.id}`); await proposalCard.getByTestId("share-button").click(); await expect(page.getByText("Copied to clipboard")).toBeVisible(); @@ -20,3 +23,27 @@ test("8E. Should share proposed governance action", async ({ ); expect(copiedTextDRepDirectory).toEqual(proposal01.id); }); + +test("8I. Should disable poll voting functionality.", async ({ page }) => { + const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(page); + await proposalDiscussionDetailsPage.goto(proposal01.id); + + await expect(proposalDiscussionDetailsPage.pollVoteCard).not.toBeVisible(); + await expect( + proposalDiscussionDetailsPage.pollYesVoteCount + ).not.toBeVisible(); + + await expect(proposalDiscussionDetailsPage.pollNoVoteCount).not.toBeVisible(); +}); + +test("8J. Should sort the proposed governance action comments.", async ({ + page, +}) => { + const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(page); + await proposalDiscussionDetailsPage.goto(proposal01.id); + + await proposalDiscussionDetailsPage.sortAndValidate( + "asc", + (date1, date2) => new Date(date1) <= new Date(date2) + ); +}); From dec2a08c2c8bfa4319ab52f415e7b2d3c334aa54 Mon Sep 17 00:00:00 2001 From: niraj Date: Thu, 13 Jun 2024 16:58:50 +0545 Subject: [PATCH 27/49] fix: Base file path of mock --- tests/govtool-frontend/playwright/lib/helpers/file.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/helpers/file.ts b/tests/govtool-frontend/playwright/lib/helpers/file.ts index b3782dbac..a803ab288 100644 --- a/tests/govtool-frontend/playwright/lib/helpers/file.ts +++ b/tests/govtool-frontend/playwright/lib/helpers/file.ts @@ -1,8 +1,7 @@ import { writeFile } from "fs"; - const path = require("path"); -const baseFilePath = path.resolve(__dirname, "./_mock"); +const baseFilePath = path.resolve(__dirname, "../_mock"); export async function createFile(fileName: string, data?: any) { await new Promise((resolve, reject) => From e67caf0adc9ac18dea09e994577c6924281e1081 Mon Sep 17 00:00:00 2001 From: niraj Date: Thu, 13 Jun 2024 16:59:45 +0545 Subject: [PATCH 28/49] test: Set username --- .../miscellaneous.loggedin.spec.ts | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) 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 index 22f58d5da..c1a9ecbf3 100644 --- a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts @@ -1,5 +1,6 @@ import environments from "@constants/environments"; -import { user01Wallet } from "@constants/staticWallets"; +import { adaHolder01Wallet, user01Wallet } from "@constants/staticWallets"; +import { faker } from "@faker-js/faker"; import { test } from "@fixtures/walletExtension"; import { setAllureEpic } from "@helpers/allure"; import DRepDirectoryPage from "@pages/dRepDirectoryPage"; @@ -102,3 +103,50 @@ test("6G. Should restrict edit dRep for non dRep", async ({ page }) => { await page.waitForTimeout(2_000); await expect(editDrepPage.nameInput).not.toBeVisible(); }); + +test("6I. Should prompt for a username after clicking on proposal discussion link if username is not set", async ({ + page, +}) => { + await page.goto("/"); + await page.getByTestId("proposal-discussion-link").click(); + + await expect( + page.getByText( + "Hey, setup your usernameUsername cannot be changed in the Future. Some subtext" + ) + ).toBeVisible(); //BUG Add modal testid instead should be username-modal + + await expect(page.getByLabel("Username *")).toBeVisible(); // BUG use testid instead +}); + +test.describe("Add username", () => { + test.use({ + storageState: ".auth/adaHolder01.json", + wallet: adaHolder01Wallet, + }); + + test("6J. Should add a username.", async ({ page }) => { + const username = faker.person.firstName(); + + await page.goto("/"); + await page.getByTestId("proposal-discussion-link").click(); + + await page.getByLabel("Username *").fill(username); //BUG used testid instead + + await page + .getByRole("button", { name: "Proceed with this username" }) + .click(); //BUG used testid instead + + await page + .getByRole("button", { name: "Proceed with this username" }) + .click(); + + await page.getByRole("button", { name: "Close" }).click(); + + await expect( + page.getByRole("button", { name: "Propose a Governance Action" }) + ).toBeVisible(); + + await expect(page.getByPlaceholder("Search...")).toBeVisible(); + }); +}); From d546c00333d02b2ac541dc4a7fb650718089771b Mon Sep 17 00:00:00 2001 From: niraj Date: Thu, 13 Jun 2024 17:00:38 +0545 Subject: [PATCH 29/49] chore: Add mulple proposal deletion --- .../govtool-frontend/playwright/tests/proposal.setup.ts | 1 + .../playwright/tests/proposal.teardown.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/govtool-frontend/playwright/tests/proposal.setup.ts b/tests/govtool-frontend/playwright/tests/proposal.setup.ts index 101a108a5..a72270a0a 100644 --- a/tests/govtool-frontend/playwright/tests/proposal.setup.ts +++ b/tests/govtool-frontend/playwright/tests/proposal.setup.ts @@ -103,6 +103,7 @@ setup("Create temporary proposal", async () => { const staticProposal: StaticProposal = { id: createProposalRes.data.attributes.proposal_id, comments, + title: proposalRequest.prop_name, }; await createFile("proposals.json", [staticProposal]); }); diff --git a/tests/govtool-frontend/playwright/tests/proposal.teardown.ts b/tests/govtool-frontend/playwright/tests/proposal.teardown.ts index e6345fdf1..8862cdf49 100644 --- a/tests/govtool-frontend/playwright/tests/proposal.teardown.ts +++ b/tests/govtool-frontend/playwright/tests/proposal.teardown.ts @@ -1,8 +1,11 @@ import { test as cleanup } from "@playwright/test"; import { deleteProposal } from "@services/proposalDiscussion"; -import proposalManager from "lib/proposalManager"; +import { StaticProposal } from "@types"; + +const staticProposals: StaticProposal[] = require("../lib/_mock/proposals.json"); cleanup("Delete Proposal", async () => { - const proposalId = await proposalManager.getProposalId(); - await deleteProposal(proposalId); + for (let i = 0; i < staticProposals.length; i++) { + await deleteProposal(staticProposals[i].id); + } }); From b891e6282f43484926d1c8dc5f4246efd71687d1 Mon Sep 17 00:00:00 2001 From: niraj Date: Thu, 13 Jun 2024 17:01:47 +0545 Subject: [PATCH 30/49] test: Comment and reply anonymously --- .../govtool-frontend/playwright/lib/types.ts | 15 ++++++++++ .../proposalDiscussion.loggedin.spec.ts | 28 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts index a80e34b62..ea587206c 100644 --- a/tests/govtool-frontend/playwright/lib/types.ts +++ b/tests/govtool-frontend/playwright/lib/types.ts @@ -106,4 +106,19 @@ type Comment = { export type StaticProposal = { id: number; comments?: Comment[]; + title: string; +}; + +export type CommentResponse = { + id: number; + attributes: { + proposal_id: string; + comment_parent_id: null | string; + user_id: string; + comment_text: string; + createdAt: string; + updatedAt: string; + user_govtool_username: string; + subcommens_number: number; + }; }; diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts index fc842ccae..ffe3d8db1 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -1,4 +1,5 @@ import { user01Wallet } from "@constants/staticWallets"; +import { faker } from "@faker-js/faker"; import { test } from "@fixtures/proposalDiscussionDetailsPage"; import { expect } from "@playwright/test"; @@ -17,3 +18,30 @@ test("8G. Should display the proper likes and dislikes count", async ({ await expect(proposalDiscussionDetailsPage.likesCounts).toHaveValue("0"); await expect(proposalDiscussionDetailsPage.dislikesCounts).toHaveValue("1"); }); + +test("8M. Should comment anonymously if a username is not set", async ({ + proposalDiscussionDetailsPage, +}) => { + const randomComment = faker.lorem.paragraph(2); + await proposalDiscussionDetailsPage.commentInput.fill(randomComment); + await proposalDiscussionDetailsPage.commentBtn.click(); + + await expect( + await proposalDiscussionDetailsPage.getFirstComment() + ).toHaveText(/anonymous/i); +}); + +test("8N. Should reply to comments", async ({ + proposalDiscussionDetailsPage, +}) => { + const randomComment = faker.lorem.paragraph(2); + + await proposalDiscussionDetailsPage.replyBtn.click(); + await proposalDiscussionDetailsPage.replyInput.fill(randomComment); + + await proposalDiscussionDetailsPage.showReplyButton.click(); + + await expect( + await proposalDiscussionDetailsPage.getFirstComment() + ).toHaveText(randomComment); +}); From bcebe1be2ad84f281024a365fb8d6f68ee37e9ad Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 13:33:15 +0545 Subject: [PATCH 31/49] chore: Setup proposal wallet --- .../playwright/lib/constants/staticWallets.ts | 5 +++++ .../govtool-frontend/playwright/package.json | 2 +- .../playwright/tests/auth.setup.ts | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/lib/constants/staticWallets.ts b/tests/govtool-frontend/playwright/lib/constants/staticWallets.ts index f30c090d3..38e6dfa8a 100644 --- a/tests/govtool-frontend/playwright/lib/constants/staticWallets.ts +++ b/tests/govtool-frontend/playwright/lib/constants/staticWallets.ts @@ -16,6 +16,9 @@ export const adaHolder06Wallet = staticWallets[9]; // Does not takes part in transaction export const user01Wallet: StaticWallet = staticWallets[5]; +// Username is already set +export const proposal01Wallet: StaticWallet = staticWallets[10]; + export const adaHolderWallets = [ adaHolder01Wallet, adaHolder02Wallet, @@ -28,3 +31,5 @@ export const adaHolderWallets = [ export const userWallets = [user01Wallet]; export const dRepWallets = [dRep01Wallet, dRep02Wallet]; + +export const proposalWallets = [proposal01Wallet]; diff --git a/tests/govtool-frontend/playwright/package.json b/tests/govtool-frontend/playwright/package.json index a6799bc66..52f83da01 100644 --- a/tests/govtool-frontend/playwright/package.json +++ b/tests/govtool-frontend/playwright/package.json @@ -25,7 +25,7 @@ "allure:serve": "npx allure serve", "test": "npx playwright test", "format": "prettier . --write", - "generate-wallets": "ts-node ./generate_wallets.ts 10" + "generate-wallets": "ts-node ./generate_wallets.ts 11" }, "dependencies": { "@cardanoapi/cardano-test-wallet": "^1.1.2", diff --git a/tests/govtool-frontend/playwright/tests/auth.setup.ts b/tests/govtool-frontend/playwright/tests/auth.setup.ts index 0edd09915..1093c81b3 100644 --- a/tests/govtool-frontend/playwright/tests/auth.setup.ts +++ b/tests/govtool-frontend/playwright/tests/auth.setup.ts @@ -9,12 +9,15 @@ import { adaHolder06Wallet, dRep01Wallet, dRep02Wallet, + proposal01Wallet, user01Wallet, } from "@constants/staticWallets"; +import { faker } from "@faker-js/faker"; import { importWallet } from "@fixtures/importWallet"; import { test as setup } from "@fixtures/walletExtension"; import { setAllureEpic, setAllureStory } from "@helpers/allure"; import LoginPage from "@pages/loginPage"; +import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; const dRep01AuthFile = ".auth/dRep01.json"; const dRep02AuthFile = ".auth/dRep02.json"; @@ -28,6 +31,8 @@ const adaHolder06AuthFile = ".auth/adaHolder06.json"; const user01AuthFile = ".auth/user01.json"; +const proposal01AuthFile = ".auth/proposal01.json"; + setup.beforeEach(async () => { await setAllureEpic("Setup"); await setAllureStory("Authentication"); @@ -122,3 +127,17 @@ setup("Create AdaHolder 06 auth", async ({ page, context }) => { await context.storageState({ path: adaHolder06AuthFile }); }); + +setup("Create Proposal 01 auth", async ({ page, context }) => { + await importWallet(page, proposal01Wallet); + + const loginPage = new LoginPage(page); + await loginPage.login(); + await loginPage.isLoggedIn(); + + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + await proposalDiscussionPage.setUsername(faker.internet.userName()); + + await context.storageState({ path: proposal01AuthFile }); +}); From 1e92c89e74ef288070714e57a4311cf7b7ade9cb Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 13:34:54 +0545 Subject: [PATCH 32/49] tempFix: Slow page loading by changing test timeout to 90 sec --- tests/govtool-frontend/playwright/playwright.config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/govtool-frontend/playwright/playwright.config.ts b/tests/govtool-frontend/playwright/playwright.config.ts index 0533badf8..0f5f94f09 100644 --- a/tests/govtool-frontend/playwright/playwright.config.ts +++ b/tests/govtool-frontend/playwright/playwright.config.ts @@ -14,6 +14,10 @@ export default defineConfig({ testDir: "./tests", /* Run tests in files in parallel */ fullyParallel: true, + /**TODO: Remove this timeout * + * It has been intentionally used to slow loading of govtool. + */ + timeout: 90_000, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!environments.ci, /* Retry on CI only */ From c4f286aa6b76f4b48993c077470d87609a3b6d22 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 13:35:29 +0545 Subject: [PATCH 33/49] chore: Setup/Teardown proposal using ui approach --- .../fixtures/proposalDiscussionDetailsPage.ts | 64 ++++++------- .../playwright/lib/helpers/string.ts | 3 + .../playwright/lib/pages/loginPage.ts | 12 ++- .../pages/proposalDiscussionDetailsPage.ts | 20 ++++- .../lib/pages/proposalDiscussionPage.ts | 82 +++++++++++++---- .../proposalDiscussion.loggedin.spec.ts | 89 ++++++++++--------- 6 files changed, 170 insertions(+), 100 deletions(-) create mode 100644 tests/govtool-frontend/playwright/lib/helpers/string.ts diff --git a/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts index 5a18040a1..c918ebf63 100644 --- a/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts @@ -1,47 +1,39 @@ -import { faker } from "@faker-js/faker"; +import { proposal01Wallet } from "@constants/staticWallets"; import { test as base } from "@fixtures/walletExtension"; -import { generateWalletAddress } from "@helpers/cardano"; +import { createNewPageWithWallet } from "@helpers/page"; import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; -import { - deleteProposal, - postCreateProposal, -} from "@services/proposalDiscussion"; -import { ProposalCreateRequest } from "@services/proposalDiscussion/types"; +import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; -export const test = base.extend<{ +type TestOptions = { proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; -}>({ - // setup - proposalDiscussionDetailsPage: async ({ page }, use) => { - const receivingAddr = generateWalletAddress(); - const proposalRequest: ProposalCreateRequest = { - proposal_links: [ - { - prop_link: faker.internet.url(), - prop_link_text: faker.internet.displayName(), - }, - ], - gov_action_type_id: 1, - prop_name: faker.company.name(), - prop_abstract: faker.lorem.paragraph(2), - prop_motivation: faker.lorem.paragraph(2), - prop_rationale: faker.lorem.paragraph(2), - prop_receiving_address: receivingAddr, - prop_amount: faker.number.int({ min: 100, max: 1000 }).toString(), - is_draft: false, - }; +}; - const createProposalRes = await postCreateProposal(proposalRequest); +export const test = base.extend({ + proposalDiscussionDetailsPage: async ({ page, browser }, use) => { + // setup + const proposalPage = await createNewPageWithWallet(browser, { + storageState: ".auth/proposal01.json", + wallet: proposal01Wallet, + }); - const proposalId = createProposalRes.data.attributes.proposal_id; - const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( - page - ); - await proposalDiscussionDetailsPage.goto(proposalId); + const proposalDiscussionPage = new ProposalDiscussionPage(proposalPage); + await proposalDiscussionPage.goto(); + const proposalId = await proposalDiscussionPage.createProposal(); - await use(proposalDiscussionDetailsPage); + const userProposalDetailsPage = new ProposalDiscussionDetailsPage(page); + await userProposalDetailsPage.goto(proposalId); + await page + .locator("div") + .filter({ hasText: /^Hey, setup your username$/ }) + .getByRole("button") + .click(); + await use(userProposalDetailsPage); // cleanup - await deleteProposal(proposalId); + const ownerProposalDetailsPage = new ProposalDiscussionDetailsPage( + proposalPage + ); + await ownerProposalDetailsPage.goto(proposalId); + await ownerProposalDetailsPage.deleteProposal(); }, }); diff --git a/tests/govtool-frontend/playwright/lib/helpers/string.ts b/tests/govtool-frontend/playwright/lib/helpers/string.ts new file mode 100644 index 000000000..68c20208e --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/helpers/string.ts @@ -0,0 +1,3 @@ +export function extractProposalIdFromUrl(url: string) { + return parseInt(url.split("/").pop()); +} diff --git a/tests/govtool-frontend/playwright/lib/pages/loginPage.ts b/tests/govtool-frontend/playwright/lib/pages/loginPage.ts index 6ebbc3dba..1ef439887 100644 --- a/tests/govtool-frontend/playwright/lib/pages/loginPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/loginPage.ts @@ -25,7 +25,17 @@ export default class LoginPage { await this.connectWalletBtn.click(); await this.demosWalletBtn.click({ force: true }); - await this.acceptSanchoNetInfoBtn.click({ force: true }); + + /** + * TODO: Remove this + * This has been set to tackle dashboard white screen issue on initial login + */ + await this.page.reload(); + /** + * TODO: Uncomment this + * Accept sanchonet info modal is not showing for now + */ + // await this.acceptSanchoNetInfoBtn.click({ force: true }); const { stakeKeys, rewardAddresses } = await this.page.evaluate( async () => { diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts index 6a533e58c..bf5ed3323 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts @@ -4,8 +4,10 @@ import { CommentResponse } from "@types"; export default class ProposalDiscussionDetailsPage { // Buttons - readonly likeBtn = this.page.getByTestId("like-button"); - readonly dislikeBtn = this.page.getByTestId("dislike-button"); + readonly likeBtn = this.page.getByRole("button").nth(6); + readonly dislikeBtn = this.page.getByRole("button", { + name: "proposal dislikes", + }); readonly commentBtn = this.page.getByRole("button", { name: "Comment", exact: true, @@ -53,7 +55,7 @@ export default class ProposalDiscussionDetailsPage { async goto(proposalId: number) { await this.page.goto( - `${environments.frontendUrl}/proposal_discussion/${proposalId}` + `${environments.frontendUrl}/connected/proposal_pillar/proposal_discussion/${proposalId}` ); } @@ -84,4 +86,16 @@ export default class ProposalDiscussionDetailsPage { expect(isValid).toBe(true); } } + + async deleteProposal() { + await this.page.waitForTimeout(2_000); + + await this.page.locator("#menu-button").click(); + await this.page.getByRole("menuitem", { name: "Delete Proposal" }).click(); + + // confirm deletion + await this.page + .getByRole("button", { name: "Yes, delete my proposal" }) + .click(); + } } diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts index 63ab45887..6192fa123 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts @@ -1,4 +1,8 @@ +import { faker } from "@faker-js/faker"; +import { generateWalletAddress } from "@helpers/cardano"; +import { extractProposalIdFromUrl } from "@helpers/string"; import { Page } from "@playwright/test"; +import { ProposalCreateRequest } from "@services/proposalDiscussion/types"; import environments from "lib/constants/environments"; import ProposalDiscussionDetailsPage from "./proposalDiscussionDetailsPage"; @@ -7,7 +11,7 @@ export default class ProposalDiscussionPage { readonly proposalCreateBtn = this.page.getByRole("button", { name: "Propose a Governance Action", }); - + readonly continueBtn = this.page.getByRole("button", { name: "Continue" }); // #BUG test-id missing readonly filterBtn = this.page.locator("#filters-button"); // this.page.getByTestId("filters-button"); readonly shareBtn = this.page .locator(".MuiCardHeader-action > .MuiButtonBase-root") @@ -24,24 +28,10 @@ export default class ProposalDiscussionPage { constructor(private readonly page: Page) {} async goto() { - await this.page.goto(`${environments.frontendUrl}/proposal_discussion`); - await this.page.waitForTimeout(2_000); - } - - async gotoProposal(proposalId: number) { await this.page.goto( - `${environments.frontendUrl}/proposal_discussion/${proposalId}` + `${environments.frontendUrl}/connected/proposal_pillar/proposal_discussion` ); - return new ProposalDiscussionDetailsPage(this.page); - } - - async viewProposal( - proposalId: string - ): Promise { - const proposalTestId = `proposal-${proposalId}-view-detail`; - await this.page.getByTestId(proposalTestId).click(); - - return new ProposalDiscussionDetailsPage(this.page); + await this.page.waitForTimeout(2_000); } async viewFirstProposal(): Promise { @@ -57,8 +47,62 @@ export default class ProposalDiscussionPage { return this.page.locator('[data-testid$="-card"]').all(); // BUG } - async getFirstProposal() { + async setUsername(name: string) { + await this.page.getByLabel("Username *").fill(name); + + const proceedBtn = this.page.getByRole("button", { + name: "Proceed with this username", + }); + await proceedBtn.click(); + await proceedBtn.click(); + + await this.page.getByRole("button", { name: "Close" }).click(); + } + + async createProposal(): Promise { + const receivingAddr = generateWalletAddress(); + const proposalRequest: ProposalCreateRequest = { + proposal_links: [ + { + prop_link: faker.internet.url(), + prop_link_text: faker.internet.displayName(), + }, + ], + gov_action_type_id: 1, + prop_name: faker.company.name(), + prop_abstract: faker.lorem.paragraph(2), + prop_motivation: faker.lorem.paragraph(2), + prop_rationale: faker.lorem.paragraph(2), + prop_receiving_address: receivingAddr, + prop_amount: faker.number.int({ min: 100, max: 1000 }).toString(), + is_draft: false, + }; + + await this.proposalCreateBtn.click(); + await this.continueBtn.click(); + + await this.fillForm(proposalRequest); + await this.continueBtn.click(); + await this.page.getByRole("button", { name: "Submit" }).click(); + + // Wait for redirection to `proposal-discussion-details` page await this.page.waitForTimeout(2_000); - return this.page.locator('[data-testid$="-card"]').first(); + + const currentPageUrl = this.page.url(); + return extractProposalIdFromUrl(currentPageUrl); + } + + private async fillForm(data: ProposalCreateRequest) { + await this.page.getByLabel("Governance Action Type *").click(); + await this.page.getByRole("option", { name: "Info" }).click(); + await this.page.getByLabel("Title *").fill(data.prop_name); + await this.page.getByPlaceholder("Summary...").fill(data.prop_abstract); + await this.page.getByLabel("Motivation *").fill(data.prop_motivation); + await this.page.getByLabel("Rationale *").fill(data.prop_rationale); + await this.page + .getByLabel("Receiving address *") + .fill(data.prop_receiving_address); + + await this.page.getByPlaceholder("e.g.").fill(data.prop_amount); } } diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts index ffe3d8db1..9e2c8909b 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -1,47 +1,54 @@ import { user01Wallet } from "@constants/staticWallets"; import { faker } from "@faker-js/faker"; import { test } from "@fixtures/proposalDiscussionDetailsPage"; +import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; import { expect } from "@playwright/test"; -test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); - -test("8G. Should display the proper likes and dislikes count", async ({ - proposalDiscussionDetailsPage, -}) => { - await proposalDiscussionDetailsPage.likeBtn.click(); - - await expect(proposalDiscussionDetailsPage.likesCounts).toHaveValue("1"); - await expect(proposalDiscussionDetailsPage.dislikesCounts).toHaveValue("0"); - - await proposalDiscussionDetailsPage.dislikeBtn.click(); - - await expect(proposalDiscussionDetailsPage.likesCounts).toHaveValue("0"); - await expect(proposalDiscussionDetailsPage.dislikesCounts).toHaveValue("1"); -}); - -test("8M. Should comment anonymously if a username is not set", async ({ - proposalDiscussionDetailsPage, -}) => { - const randomComment = faker.lorem.paragraph(2); - await proposalDiscussionDetailsPage.commentInput.fill(randomComment); - await proposalDiscussionDetailsPage.commentBtn.click(); - - await expect( - await proposalDiscussionDetailsPage.getFirstComment() - ).toHaveText(/anonymous/i); -}); - -test("8N. Should reply to comments", async ({ - proposalDiscussionDetailsPage, -}) => { - const randomComment = faker.lorem.paragraph(2); - - await proposalDiscussionDetailsPage.replyBtn.click(); - await proposalDiscussionDetailsPage.replyInput.fill(randomComment); - - await proposalDiscussionDetailsPage.showReplyButton.click(); - - await expect( - await proposalDiscussionDetailsPage.getFirstComment() - ).toHaveText(randomComment); +test.describe("Proposal created state", () => { + test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); + + test("8G. Should display the proper likes and dislikes count", async ({ + proposalDiscussionDetailsPage, + }) => { + await proposalDiscussionDetailsPage.likeBtn.click(); + await proposalDiscussionDetailsPage.currentPage.waitForTimeout(2_000); + await expect( + proposalDiscussionDetailsPage.currentPage.getByText("10") + ).toBeVisible(); + + await proposalDiscussionDetailsPage.dislikeBtn.click(); + await proposalDiscussionDetailsPage.currentPage.waitForTimeout(2_000); + await expect( + proposalDiscussionDetailsPage.currentPage.getByText("01", { exact: true }) + ).toBeVisible(); + }); + + test("8M. Should comment anonymously if a username is not set", async ({ + proposalDiscussionDetailsPage, + }) => { + const randomComment = faker.lorem.paragraph(2); + await proposalDiscussionDetailsPage.commentInput.fill(randomComment); + await proposalDiscussionDetailsPage.commentBtn.click(); + + await expect( + await proposalDiscussionDetailsPage.getFirstComment() + ).toHaveText(/anonymous/i); + }); + + test("8N. Should reply to comments", async ({ page }) => { + const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( + page + ); + + const randomComment = faker.lorem.paragraph(2); + + await proposalDiscussionDetailsPage.replyBtn.click(); + await proposalDiscussionDetailsPage.replyInput.fill(randomComment); + + await proposalDiscussionDetailsPage.showReplyButton.click(); + + await expect( + await proposalDiscussionDetailsPage.getFirstComment() + ).toHaveText(randomComment); + }); }); From 4c5116aac059b5a747739988cc2629846499bc44 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 13:59:37 +0545 Subject: [PATCH 34/49] test: comment and reply proposal --- .../lib/constants/staticProposals.ts | 5 --- .../pages/proposalDiscussionDetailsPage.ts | 27 +++++++-------- .../proposalDiscussion.loggedin.spec.ts | 33 ++++++++----------- 3 files changed, 27 insertions(+), 38 deletions(-) delete mode 100644 tests/govtool-frontend/playwright/lib/constants/staticProposals.ts diff --git a/tests/govtool-frontend/playwright/lib/constants/staticProposals.ts b/tests/govtool-frontend/playwright/lib/constants/staticProposals.ts deleted file mode 100644 index ae30d5f7a..000000000 --- a/tests/govtool-frontend/playwright/lib/constants/staticProposals.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { StaticProposal } from "@types"; - -const staticProposals: StaticProposal[] = require("../_mock/proposals.json"); - -export const proposal01 = staticProposals[0]; diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts index bf5ed3323..65175f40d 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts @@ -4,14 +4,12 @@ import { CommentResponse } from "@types"; export default class ProposalDiscussionDetailsPage { // Buttons - readonly likeBtn = this.page.getByRole("button").nth(6); + readonly likeBtn = this.page.getByRole("button", { + name: "proposal likes", + }); readonly dislikeBtn = this.page.getByRole("button", { name: "proposal dislikes", }); - readonly commentBtn = this.page.getByRole("button", { - name: "Comment", - exact: true, - }); // this.page.getByTestId("comment-button"); readonly addPollBtn = this.page.getByTestId("add-poll"); readonly SubmitBtn = this.page.getByTestId("submit-button"); readonly menuBtn = this.page.getByTestId("menu-button"); @@ -24,15 +22,10 @@ export default class ProposalDiscussionDetailsPage { .filter({ hasText: /^Comments$/ }) .getByRole("button"); // this.page.getByTestId("sort-button"); readonly proposeGovernanceAction = this.page.getByTestId("propose-GA-button"); - readonly replyBtn = this.page.getByTestId("reply-button"); readonly pollYesBtn = this.page.getByTestId("poll-yes-button"); readonly pollNoBtn = this.page.getByTestId("poll-No-button"); readonly showReplyButton = this.page.getByTestId("show-more-reply"); - // Inputs - readonly commentInput = this.page.getByRole("textbox"); //this.page.getByTestId("comment-input"); - readonly replyInput = this.page.getByTestId("reply-input"); - // Indicators readonly likesCounts = this.page.getByTestId("likes-count"); readonly dislikesCounts = this.page.getByTestId("dislikse-count"); @@ -59,9 +52,17 @@ export default class ProposalDiscussionDetailsPage { ); } - async getFirstComment() { - await this.page.waitForTimeout(2_000); - return this.page.locator('[data-testid$="-comment-card"]').first(); + async addComment(comment: string) { + await this.page.getByRole("textbox").fill(comment); + await this.page + .getByRole("button", { name: "Comment", exact: true }) + .click(); + } + + async replyComment(reply: string) { + await this.page.getByRole("button", { name: "Reply" }).click(); + await this.page.getByPlaceholder("Add comment").fill(reply); + await this.page.getByRole("button", { name: "Comment" }).nth(2).click(); } async sortAndValidate( diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts index 9e2c8909b..db79ac4a4 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -1,7 +1,6 @@ import { user01Wallet } from "@constants/staticWallets"; import { faker } from "@faker-js/faker"; import { test } from "@fixtures/proposalDiscussionDetailsPage"; -import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; import { expect } from "@playwright/test"; test.describe("Proposal created state", () => { @@ -25,30 +24,24 @@ test.describe("Proposal created state", () => { test("8M. Should comment anonymously if a username is not set", async ({ proposalDiscussionDetailsPage, + page, }) => { - const randomComment = faker.lorem.paragraph(2); - await proposalDiscussionDetailsPage.commentInput.fill(randomComment); - await proposalDiscussionDetailsPage.commentBtn.click(); + const randComment = faker.lorem.paragraph(2); + await proposalDiscussionDetailsPage.addComment(randComment); - await expect( - await proposalDiscussionDetailsPage.getFirstComment() - ).toHaveText(/anonymous/i); + await expect(page.getByText(randComment)).toBeVisible(); }); - test("8N. Should reply to comments", async ({ page }) => { - const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( - page - ); - - const randomComment = faker.lorem.paragraph(2); - - await proposalDiscussionDetailsPage.replyBtn.click(); - await proposalDiscussionDetailsPage.replyInput.fill(randomComment); + test("8N. Should reply to comments", async ({ + proposalDiscussionDetailsPage, + page, + }) => { + const randComment = faker.lorem.paragraph(2); + const randReply = faker.lorem.paragraph(2); - await proposalDiscussionDetailsPage.showReplyButton.click(); + await proposalDiscussionDetailsPage.addComment(randComment); - await expect( - await proposalDiscussionDetailsPage.getFirstComment() - ).toHaveText(randomComment); + await proposalDiscussionDetailsPage.replyComment(randReply); + await expect(page.getByText(randReply)).toBeVisible(); }); }); From e7336b9df9ac6798d0314c0a24b16699607c5c4e Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 15:39:38 +0545 Subject: [PATCH 35/49] chore: Enhance fixture proposalDiscussionDetailsPage to proposal --- ...alDiscussionDetailsPage.ts => proposal.ts} | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) rename tests/govtool-frontend/playwright/lib/fixtures/{proposalDiscussionDetailsPage.ts => proposal.ts} (54%) diff --git a/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/fixtures/proposal.ts similarity index 54% rename from tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts rename to tests/govtool-frontend/playwright/lib/fixtures/proposal.ts index c918ebf63..dfd535f1d 100644 --- a/tests/govtool-frontend/playwright/lib/fixtures/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/fixtures/proposal.ts @@ -5,11 +5,11 @@ import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage" import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; type TestOptions = { - proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; + proposalId: number; }; export const test = base.extend({ - proposalDiscussionDetailsPage: async ({ page, browser }, use) => { + proposalId: async ({ page, browser }, use) => { // setup const proposalPage = await createNewPageWithWallet(browser, { storageState: ".auth/proposal01.json", @@ -20,20 +20,11 @@ export const test = base.extend({ await proposalDiscussionPage.goto(); const proposalId = await proposalDiscussionPage.createProposal(); - const userProposalDetailsPage = new ProposalDiscussionDetailsPage(page); - await userProposalDetailsPage.goto(proposalId); - await page - .locator("div") - .filter({ hasText: /^Hey, setup your username$/ }) - .getByRole("button") - .click(); - await use(userProposalDetailsPage); + await use(proposalId); // cleanup - const ownerProposalDetailsPage = new ProposalDiscussionDetailsPage( - proposalPage - ); - await ownerProposalDetailsPage.goto(proposalId); - await ownerProposalDetailsPage.deleteProposal(); + const proposalDetailsPage = new ProposalDiscussionDetailsPage(proposalPage); + await proposalDetailsPage.goto(proposalId); + await proposalDetailsPage.deleteProposal(); }, }); From 2e3988a87ef52838412917d36016da87b33589d5 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 15:40:35 +0545 Subject: [PATCH 36/49] tempFix: Reload after sanchonet info on login --- tests/govtool-frontend/playwright/lib/pages/loginPage.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/loginPage.ts b/tests/govtool-frontend/playwright/lib/pages/loginPage.ts index 1ef439887..a4a4c4457 100644 --- a/tests/govtool-frontend/playwright/lib/pages/loginPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/loginPage.ts @@ -35,7 +35,13 @@ export default class LoginPage { * TODO: Uncomment this * Accept sanchonet info modal is not showing for now */ - // await this.acceptSanchoNetInfoBtn.click({ force: true }); + await this.acceptSanchoNetInfoBtn.click({ force: true }); + + /** + * TODO: Remove this + * This has been set to tackle dashboard white screen issue on initial login + */ + await this.page.reload(); const { stakeKeys, rewardAddresses } = await this.page.evaluate( async () => { From db2dd90c47c1abee2dd33f40fc6b911d0f344530 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 15:41:51 +0545 Subject: [PATCH 37/49] test: Update anonymous username to set username in comments --- .../pages/proposalDiscussionDetailsPage.ts | 12 ++- .../proposalDiscussion.loggedin.spec.ts | 80 +++++++++++++++---- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts index 65175f40d..c15a575f1 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts @@ -42,16 +42,20 @@ export default class ProposalDiscussionDetailsPage { constructor(private readonly page: Page) {} - get currentPage(): Page { - return this.page; - } - async goto(proposalId: number) { await this.page.goto( `${environments.frontendUrl}/connected/proposal_pillar/proposal_discussion/${proposalId}` ); } + async closeUsernamePrompt() { + await this.page + .locator("div") + .filter({ hasText: /^Hey, setup your username$/ }) + .getByRole("button") + .click(); + } + async addComment(comment: string) { await this.page.getByRole("textbox").fill(comment); await this.page diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts index db79ac4a4..3132264a3 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -1,29 +1,37 @@ import { user01Wallet } from "@constants/staticWallets"; +import { createTempUserAuth } from "@datafactory/createAuth"; import { faker } from "@faker-js/faker"; -import { test } from "@fixtures/proposalDiscussionDetailsPage"; -import { expect } from "@playwright/test"; +import { test } from "@fixtures/proposal"; +import { ShelleyWallet } from "@helpers/crypto"; +import { createNewPageWithWallet } from "@helpers/page"; +import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; +import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; +import { Page, expect } from "@playwright/test"; -test.describe("Proposal created state", () => { +test.describe("Proposal created logged in state", () => { test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); + let proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; + + test.beforeEach(async ({ page, proposalId }) => { + proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(page); + await proposalDiscussionDetailsPage.goto(proposalId); + await proposalDiscussionDetailsPage.closeUsernamePrompt(); + }); + test("8G. Should display the proper likes and dislikes count", async ({ - proposalDiscussionDetailsPage, + page, }) => { await proposalDiscussionDetailsPage.likeBtn.click(); - await proposalDiscussionDetailsPage.currentPage.waitForTimeout(2_000); - await expect( - proposalDiscussionDetailsPage.currentPage.getByText("10") - ).toBeVisible(); + await page.waitForTimeout(2_000); + await expect(page.getByText("10")).toBeVisible(); await proposalDiscussionDetailsPage.dislikeBtn.click(); - await proposalDiscussionDetailsPage.currentPage.waitForTimeout(2_000); - await expect( - proposalDiscussionDetailsPage.currentPage.getByText("01", { exact: true }) - ).toBeVisible(); + await page.waitForTimeout(2_000); + await expect(page.getByText("01", { exact: true })).toBeVisible(); }); test("8M. Should comment anonymously if a username is not set", async ({ - proposalDiscussionDetailsPage, page, }) => { const randComment = faker.lorem.paragraph(2); @@ -32,10 +40,7 @@ test.describe("Proposal created state", () => { await expect(page.getByText(randComment)).toBeVisible(); }); - test("8N. Should reply to comments", async ({ - proposalDiscussionDetailsPage, - page, - }) => { + test("8N. Should reply to comments", async ({ page }) => { const randComment = faker.lorem.paragraph(2); const randReply = faker.lorem.paragraph(2); @@ -45,3 +50,44 @@ test.describe("Proposal created state", () => { await expect(page.getByText(randReply)).toBeVisible(); }); }); + +test.describe("Proposal created logged out state", () => { + let userPage: Page; + + test.beforeEach(async ({ page, browser }) => { + const wallet = (await ShelleyWallet.generate()).json(); + const tempUserAuth = await createTempUserAuth(page, wallet); + + userPage = await createNewPageWithWallet(browser, { + storageState: tempUserAuth, + wallet, + }); + }); + + test("8O. Should update anonymous username to set username in comments", async ({ + proposalId, + }) => { + test.slow(); + + const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( + userPage + ); + await proposalDiscussionDetailsPage.goto(proposalId); + await proposalDiscussionDetailsPage.closeUsernamePrompt(); + + const randComment = faker.lorem.paragraph(2); + await proposalDiscussionDetailsPage.addComment(randComment); + + await expect(userPage.getByText(/anonymous/i)).toBeVisible(); + + const proposalDiscussionPage = new ProposalDiscussionPage(userPage); + await proposalDiscussionPage.goto(); + + const userName = faker.internet.userName(); + await proposalDiscussionPage.setUsername(userName); + await proposalDiscussionDetailsPage.goto(proposalId); + + await expect(userPage.getByText(/anonymous/i)).not.toBeVisible(); + await expect(userPage.getByText(userName)).toBeVisible(); + }); +}); From 140b5274716052829c138b442a5d0dbdbb3e1949 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 16:04:03 +0545 Subject: [PATCH 38/49] chore: Mock proposal, poll for visibility testing --- .../playwright/lib/_mock/poll.json | 24 +++++++++ .../playwright/lib/_mock/proposal.json | 44 +++++++++++++++++ .../proposalDiscussion.proposal.spec.ts | 49 ------------------- .../proposalDiscussion.spec.ts | 49 ++++++++++++++++++- 4 files changed, 115 insertions(+), 51 deletions(-) create mode 100644 tests/govtool-frontend/playwright/lib/_mock/poll.json create mode 100644 tests/govtool-frontend/playwright/lib/_mock/proposal.json delete mode 100644 tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts diff --git a/tests/govtool-frontend/playwright/lib/_mock/poll.json b/tests/govtool-frontend/playwright/lib/_mock/poll.json new file mode 100644 index 000000000..87c486958 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/_mock/poll.json @@ -0,0 +1,24 @@ +{ + "data": [ + { + "id": 39, + "attributes": { + "proposal_id": "128", + "poll_yes": 0, + "poll_no": 0, + "poll_start_dt": "2024-06-14T08:26:34.400Z", + "is_poll_active": true, + "createdAt": "2024-06-14T08:28:20.210Z", + "updatedAt": "2024-06-14T08:28:20.210Z" + } + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 1, + "pageCount": 1, + "total": 1 + } + } +} diff --git a/tests/govtool-frontend/playwright/lib/_mock/proposal.json b/tests/govtool-frontend/playwright/lib/_mock/proposal.json new file mode 100644 index 000000000..9b9ac9ad6 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/_mock/proposal.json @@ -0,0 +1,44 @@ +{ + "data": { + "id": 128, + "attributes": { + "prop_likes": 0, + "prop_dislikes": 0, + "prop_comments_number": 0, + "prop_submited": false, + "prop_status_id": null, + "user_id": "52", + "createdAt": "2024-06-14T08:28:18.000Z", + "updatedAt": "2024-06-14T08:28:18.000Z", + "content": { + "id": 121, + "attributes": { + "proposal_id": "128", + "prop_rev_active": true, + "prop_abstract": "Calamitas suppono coniuratio aiunt pecto uberrime deleniti tepidus acerbitas. Nihil vitium conservo abeo tametsi odit creator basium.", + "prop_motivation": "Demonstro apparatus torrens patrocinor. Concedo campana possimus agnosco tutamen astrum conventus defendo sublime.", + "prop_rationale": "Brevis suppellex coadunatio vis. Alii terreo carbo sono utilis vicissitudo.", + "gov_action_type_id": "1", + "prop_name": "Labadie, Stehr and Rosenbaum", + "prop_receiving_address": "addr_test1qqqqqqqqqqa4kpmh", + "prop_amount": 402, + "createdAt": "2024-06-14T08:28:18.012Z", + "updatedAt": "2024-06-14T08:28:18.012Z", + "is_draft": false, + "user_id": "52", + "proposal_links": [], + "gov_action_type": { + "id": 1, + "attributes": { + "gov_action_type_name": "Info", + "createdAt": "2024-05-27T15:06:15.640Z", + "updatedAt": "2024-05-27T15:06:15.640Z" + } + } + } + }, + "user_govtool_username": "Jett.Hagenes21" + } + }, + "meta": {} +} diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts deleted file mode 100644 index 344bad41f..000000000 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.proposal.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { proposal01 } from "@constants/staticProposals"; -import { test } from "@fixtures/proposalDiscussionDetailsPage"; -import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; -import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; -import { expect } from "@playwright/test"; - -test("8E. Should share proposed governance action", async ({ - page, - context, -}) => { - await context.grantPermissions(["clipboard-read", "clipboard-write"]); - - const proposalDiscussionPage = new ProposalDiscussionPage(page); - await proposalDiscussionPage.goto(); - - await proposalDiscussionPage.searchInput.fill(proposal01.title); - - const proposalCard = page.getByTestId(`proposal-${proposal01.id}`); - await proposalCard.getByTestId("share-button").click(); - await expect(page.getByText("Copied to clipboard")).toBeVisible(); - const copiedTextDRepDirectory = await page.evaluate(() => - navigator.clipboard.readText() - ); - expect(copiedTextDRepDirectory).toEqual(proposal01.id); -}); - -test("8I. Should disable poll voting functionality.", async ({ page }) => { - const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(page); - await proposalDiscussionDetailsPage.goto(proposal01.id); - - await expect(proposalDiscussionDetailsPage.pollVoteCard).not.toBeVisible(); - await expect( - proposalDiscussionDetailsPage.pollYesVoteCount - ).not.toBeVisible(); - - await expect(proposalDiscussionDetailsPage.pollNoVoteCount).not.toBeVisible(); -}); - -test("8J. Should sort the proposed governance action comments.", async ({ - page, -}) => { - const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(page); - await proposalDiscussionDetailsPage.goto(proposal01.id); - - await proposalDiscussionDetailsPage.sortAndValidate( - "asc", - (date1, date2) => new Date(date1) <= new Date(date2) - ); -}); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts index be2c229ce..0c0dcc61b 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts @@ -1,10 +1,13 @@ -import { proposal01 } from "@constants/staticProposals"; import { faker } from "@faker-js/faker"; -import { test } from "@fixtures/proposalDiscussionDetailsPage"; +import { test } from "@fixtures/proposal"; import { setAllureEpic } from "@helpers/allure"; +import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage"; import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; import { expect } from "@playwright/test"; +const mockProposal = require("../../lib/_mock/proposal.json"); +const mockPoll = require("../../lib/_mock/poll.json"); + test.beforeEach(() => { setAllureEpic("Proposal Discussion Forum"); }); @@ -99,3 +102,45 @@ test("8R. Should restrict proposal creation on disconnected state", async ({ await expect(proposalDiscussionPage.proposalCreateBtn).not.toBeVisible(); }); + +test.describe("Mocked proposal", () => { + let proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; + + test.beforeEach(async ({ page }) => { + await page.route("**/api/proposals/**", async (route) => + route.fulfill({ + body: JSON.stringify(mockProposal), + }) + ); + + await page.route("**/api/polls/**", async (route) => + route.fulfill({ + body: JSON.stringify(mockPoll), + }) + ); + + proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(page); + await proposalDiscussionDetailsPage.goto(10); + }); + + test("8E. Should share proposed governance action", async ({ + page, + context, + }) => { + await context.grantPermissions(["clipboard-read", "clipboard-write"]); + + await page.getByTestId("share-button").click(); + await expect(page.getByText("Copied to clipboard")).toBeVisible(); + const copiedTextDRepDirectory = await page.evaluate(() => + navigator.clipboard.readText() + ); + expect(copiedTextDRepDirectory).toEqual(mockProposal.data.id); + }); + + test("8I. Should disable poll voting functionality.", async () => { + await expect(proposalDiscussionDetailsPage.pollVoteCard).not.toBeVisible(); + await expect(proposalDiscussionDetailsPage.pollYesBtn).not.toBeVisible(); + + await expect(proposalDiscussionDetailsPage.pollNoBtn).not.toBeVisible(); + }); +}); From 17ac9ccd341efe7e620b206ed1fc46120e23eecb Mon Sep 17 00:00:00 2001 From: niraj Date: Fri, 14 Jun 2024 14:29:56 +0545 Subject: [PATCH 39/49] chore: Add pollEnabled option on proposal fixture --- .../playwright/lib/fixtures/proposal.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/fixtures/proposal.ts b/tests/govtool-frontend/playwright/lib/fixtures/proposal.ts index dfd535f1d..686629f9a 100644 --- a/tests/govtool-frontend/playwright/lib/fixtures/proposal.ts +++ b/tests/govtool-frontend/playwright/lib/fixtures/proposal.ts @@ -6,10 +6,13 @@ import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; type TestOptions = { proposalId: number; + pollEnabled: boolean; }; export const test = base.extend({ - proposalId: async ({ page, browser }, use) => { + pollEnabled: [false, { option: true }], + + proposalId: async ({ page, browser, pollEnabled }, use) => { // setup const proposalPage = await createNewPageWithWallet(browser, { storageState: ".auth/proposal01.json", @@ -19,11 +22,15 @@ export const test = base.extend({ const proposalDiscussionPage = new ProposalDiscussionPage(proposalPage); await proposalDiscussionPage.goto(); const proposalId = await proposalDiscussionPage.createProposal(); + const proposalDetailsPage = new ProposalDiscussionDetailsPage(proposalPage); + + if (pollEnabled) { + await proposalDetailsPage.addPollBtn.click(); + } await use(proposalId); // cleanup - const proposalDetailsPage = new ProposalDiscussionDetailsPage(proposalPage); await proposalDetailsPage.goto(proposalId); await proposalDetailsPage.deleteProposal(); }, From 42ce3c3e17e39954674e847a56d425b772862953 Mon Sep 17 00:00:00 2001 From: niraj Date: Fri, 14 Jun 2024 15:14:14 +0545 Subject: [PATCH 40/49] test: Vote and change vote functionality in proposal poll --- .../pages/proposalDiscussionDetailsPage.ts | 28 ++++++++---- .../proposalDiscussion.loggedin.spec.ts | 43 ++++++++++++++++++- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts index c15a575f1..f040ef663 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts @@ -10,30 +10,38 @@ export default class ProposalDiscussionDetailsPage { readonly dislikeBtn = this.page.getByRole("button", { name: "proposal dislikes", }); - readonly addPollBtn = this.page.getByTestId("add-poll"); + readonly commentBtn = this.page.getByRole("button", { + name: "Comment", + exact: true, + }); // this.page.getByTestId("comment-button"); + readonly addPollBtn = this.page.getByRole("button", { name: "Add Poll" }); // BUG missing test id readonly SubmitBtn = this.page.getByTestId("submit-button"); readonly menuBtn = this.page.getByTestId("menu-button"); readonly editProposalBtn = this.page.getByTestId("edit-proposal"); readonly deleteProposalBtn = this.page.getByTestId("delete-proposal"); readonly reviewVersionsBtn = this.page.getByTestId("review-versions"); - readonly closePollBtn = this.page.getByTestId("close-poll"); + readonly closePollBtn = this.page.getByRole("button", { name: "Close Poll" }); // BUG missing test id readonly sortBtn = this.page .locator("div") .filter({ hasText: /^Comments$/ }) .getByRole("button"); // this.page.getByTestId("sort-button"); readonly proposeGovernanceAction = this.page.getByTestId("propose-GA-button"); - readonly pollYesBtn = this.page.getByTestId("poll-yes-button"); - readonly pollNoBtn = this.page.getByTestId("poll-No-button"); - readonly showReplyButton = this.page.getByTestId("show-more-reply"); + readonly replyBtn = this.page.getByTestId("reply-button"); + readonly pollYesBtn = this.page.getByRole("button", { name: "Yes" }); //BUG missing test id + readonly pollNoBtn = this.page.getByRole("button", { name: "No" }); //BUG missing test id + readonly showReplyBtn = this.page.getByTestId("show-more-reply"); + readonly closePollYesBtn = this.page.getByRole("button", { + name: "Yes, close Poll", + }); // BUG missing test id + readonly changeVoteBtn = this.page.getByRole("button", { + name: "Change Vote", + }); // Indicators readonly likesCounts = this.page.getByTestId("likes-count"); readonly dislikesCounts = this.page.getByTestId("dislikse-count"); readonly commentsCount = this.page.getByTestId("comments-count"); - readonly pollYesVoteCount = this.page.getByTestId("poll-yes-vote-count"); - readonly pollNoVoteCount = this.page.getByTestId("poll-No-vote-count"); - // Cards readonly pollVoteCard = this.page.getByTestId("poll-vote-card"); readonly pollResultCard = this.page.getByTestId("poll-result-card"); @@ -92,6 +100,10 @@ export default class ProposalDiscussionDetailsPage { } } + async voteOnPoll(vote: string) { + await this.page.getByRole("button", { name: `${vote}` }).click(); + } + async deleteProposal() { await this.page.waitForTimeout(2_000); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts index 3132264a3..ffae7f919 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -9,7 +9,11 @@ import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; import { Page, expect } from "@playwright/test"; test.describe("Proposal created logged in state", () => { - test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); + test.use({ + storageState: ".auth/user01.json", + wallet: user01Wallet, + pollEnabled: true, + }); let proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; @@ -49,6 +53,43 @@ test.describe("Proposal created logged in state", () => { await proposalDiscussionDetailsPage.replyComment(randReply); await expect(page.getByText(randReply)).toBeVisible(); }); + + test("8Q. Should vote on poll.", async ({ page }) => { + const pollVotes = ["Yes", "No"]; + const choice = Math.floor(Math.random() * pollVotes.length); + const vote = pollVotes[choice]; + + await proposalDiscussionDetailsPage.voteOnPoll(vote); + + await expect(proposalDiscussionDetailsPage.pollYesBtn).not.toBeVisible(); + await expect(proposalDiscussionDetailsPage.pollNoBtn).not.toBeVisible(); + await expect(page.getByText(`${vote}: (100%)`)).toBeVisible(); + // opposite of random choice vote + const oppositeVote = pollVotes[pollVotes.length - 1 - choice]; + await expect(page.getByText(`${oppositeVote}: (0%)`)).toBeVisible(); + }); + + test("8R. Should change vote on poll.", async ({ page }) => { + const pollVotes = ["Yes", "No"]; + const choice = Math.floor(Math.random() * pollVotes.length); + const vote = pollVotes[choice]; + + await proposalDiscussionDetailsPage.voteOnPoll(vote); + + await proposalDiscussionDetailsPage.changeVoteBtn.click(); + await page + .getByRole("button", { name: "Yes, change my Poll Vote" }) + .click(); + + await expect(proposalDiscussionDetailsPage.pollYesBtn).not.toBeVisible(); + await expect(proposalDiscussionDetailsPage.pollNoBtn).not.toBeVisible(); + + // vote must be changed + await expect(page.getByText(`${vote}: (0%)`)).toBeVisible(); + // opposite of random choice vote + const oppositeVote = pollVotes[pollVotes.length - 1 - choice]; + await expect(page.getByText(`${oppositeVote}: (100%)`)).toBeVisible(); + }); }); test.describe("Proposal created logged out state", () => { From 815d712272a7b168d1f095b36485658b41c986b0 Mon Sep 17 00:00:00 2001 From: niraj Date: Fri, 14 Jun 2024 18:21:07 +0545 Subject: [PATCH 41/49] test: Add and cancel poll --- .../pages/proposalDiscussionDetailsPage.ts | 5 +- .../lib/pages/proposalDiscussionPage.ts | 8 +++ .../proposalDiscussion.loggedin.spec.ts | 63 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts index f040ef663..cedee9c55 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts @@ -48,6 +48,9 @@ export default class ProposalDiscussionDetailsPage { readonly commentCard = this.proposeGovernanceAction.getByTestId("comment-card"); + //inputs + readonly commentInput = this.page.getByRole("textbox"); + constructor(private readonly page: Page) {} async goto(proposalId: number) { @@ -65,7 +68,7 @@ export default class ProposalDiscussionDetailsPage { } async addComment(comment: string) { - await this.page.getByRole("textbox").fill(comment); + await this.commentInput.fill(comment); await this.page .getByRole("button", { name: "Comment", exact: true }) .click(); diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts index 6192fa123..5c1b5dd5f 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts @@ -34,6 +34,14 @@ export default class ProposalDiscussionPage { await this.page.waitForTimeout(2_000); } + async closeUsernamePrompt() { + await this.page + .locator("div") + .filter({ hasText: /^Hey, setup your username$/ }) + .getByRole("button") + .click(); + } + async viewFirstProposal(): Promise { await this.page .locator('[data-testid^="govaction-"][data-testid$="-view-detail"]') diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts index ffae7f919..f1712b8ad 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -35,6 +35,18 @@ test.describe("Proposal created logged in state", () => { await expect(page.getByText("01", { exact: true })).toBeVisible(); }); + test("8J. Should sort the proposed governance action comments.", async ({}) => { + for (let i = 0; i < 4; i++) { + const comment = faker.lorem.paragraph(2); + await proposalDiscussionDetailsPage.addComment(comment); + } + + await proposalDiscussionDetailsPage.sortAndValidate( + "asc", + (date1, date2) => new Date(date1) <= new Date(date2) + ); + }); + test("8M. Should comment anonymously if a username is not set", async ({ page, }) => { @@ -132,3 +144,54 @@ test.describe("Proposal created logged out state", () => { await expect(userPage.getByText(userName)).toBeVisible(); }); }); + +test.describe("Add and cancel poll", () => { + test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); + let proposalId: number; + test.beforeEach(async ({ page }) => { + const proposalDiscussionPage = new ProposalDiscussionPage(page); + await proposalDiscussionPage.goto(); + await proposalDiscussionPage.closeUsernamePrompt(); + + proposalId = await proposalDiscussionPage.createProposal(); + console.log({ proposalId }); + }); + + test.afterEach(async ({ page }) => { + const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( + page + ); + await proposalDiscussionDetailsPage.goto(proposalId); + + await proposalDiscussionDetailsPage.deleteProposal(); + }); + + test("8P. Should add poll on own proposal", async ({ page }) => { + const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( + page + ); + await proposalDiscussionDetailsPage.goto(proposalId); + + await proposalDiscussionDetailsPage.addPollBtn.click(); + + await expect(proposalDiscussionDetailsPage.addPollBtn).not.toBeVisible(); + await expect(proposalDiscussionDetailsPage.closePollBtn).toBeVisible(); + }); + + test("8R. Should disable voting after cancelling the poll with the current poll result.", async ({ + page, + }) => { + const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( + page + ); + await proposalDiscussionDetailsPage.goto(proposalId); + + await proposalDiscussionDetailsPage.addPollBtn.click(); + + await proposalDiscussionDetailsPage.closePollBtn.click(); + + await proposalDiscussionDetailsPage.closePollYesBtn.click(); + + await expect(proposalDiscussionDetailsPage.closePollBtn).not.toBeVisible(); + }); +}); From fc038b29d11ea65a70796b1abc5d9ff50dddcdd3 Mon Sep 17 00:00:00 2001 From: niraj Date: Fri, 14 Jun 2024 18:21:39 +0545 Subject: [PATCH 42/49] chore: Remove proposal teardown and setup --- .../playwright/playwright.config.ts | 5 -- .../playwright/tests/proposal.setup.ts | 60 ------------------- .../playwright/tests/proposal.teardown.ts | 11 ---- 3 files changed, 76 deletions(-) delete mode 100644 tests/govtool-frontend/playwright/tests/proposal.teardown.ts diff --git a/tests/govtool-frontend/playwright/playwright.config.ts b/tests/govtool-frontend/playwright/playwright.config.ts index 0f5f94f09..ada004008 100644 --- a/tests/govtool-frontend/playwright/playwright.config.ts +++ b/tests/govtool-frontend/playwright/playwright.config.ts @@ -79,7 +79,6 @@ export default defineConfig({ dependencies: environments.ci ? ["auth setup", "wallet bootstrap", "proposal setup"] : [], - teardown: environments.ci && "cleanup proposal", }, { name: "loggedin (desktop)", @@ -129,9 +128,5 @@ export default defineConfig({ name: "cleanup delegation", testMatch: "delegation.teardown.ts", }, - { - name: "cleanup proposal", - testMatch: "proposal.teardown.ts", - }, ], }); diff --git a/tests/govtool-frontend/playwright/tests/proposal.setup.ts b/tests/govtool-frontend/playwright/tests/proposal.setup.ts index a72270a0a..9f7111499 100644 --- a/tests/govtool-frontend/playwright/tests/proposal.setup.ts +++ b/tests/govtool-frontend/playwright/tests/proposal.setup.ts @@ -1,22 +1,9 @@ import environments from "@constants/environments"; -import { faker } from "@faker-js/faker"; import { setAllureEpic, setAllureStory } from "@helpers/allure"; -import { generateWalletAddress } from "@helpers/cardano"; import { ShelleyWallet } from "@helpers/crypto"; -import { createFile } from "@helpers/file"; import { pollTransaction } from "@helpers/transaction"; import { test as setup } from "@playwright/test"; import kuberService from "@services/kuberService"; -import { - postAddComment, - postAddPoll, - postCreateProposal, -} from "@services/proposalDiscussion"; -import { - CommentRequest, - ProposalCreateRequest, -} from "@services/proposalDiscussion/types"; -import { StaticProposal } from "@types"; import walletManager from "lib/walletManager"; const PROPOSAL_SUBMISSIONS_WALLETS_COUNT = 1; @@ -60,50 +47,3 @@ setup("Setup temporary proposal wallets", async () => { "proposalSubmission" ); }); - -setup("Create temporary proposal", async () => { - const receivingAddr = generateWalletAddress(); - const proposalRequest: ProposalCreateRequest = { - proposal_links: [ - { - prop_link: faker.internet.url(), - prop_link_text: faker.internet.displayName(), - }, - ], - gov_action_type_id: 1, - prop_name: faker.company.name(), - prop_abstract: faker.lorem.paragraph(2), - prop_motivation: faker.lorem.paragraph(2), - prop_rationale: faker.lorem.paragraph(2), - prop_receiving_address: receivingAddr, - prop_amount: faker.number.int({ min: 100, max: 1000 }).toString(), - is_draft: false, - }; - - const createProposalRes = await postCreateProposal(proposalRequest); - - await postAddPoll({ - proposal_id: createProposalRes.data.attributes.proposal_id.toString(), - poll_start_dt: new Date().toISOString(), - is_poll_active: true, - }); - - const comments: CommentRequest[] = []; - - for (let i = 0; i < 4; i++) { - const comment: CommentRequest = { - proposal_id: createProposalRes.data.attributes.proposal_id.toString(), - comment_text: faker.lorem.paragraph(2), - }; - comments.push(comment); - - await postAddComment(comment); - } - - const staticProposal: StaticProposal = { - id: createProposalRes.data.attributes.proposal_id, - comments, - title: proposalRequest.prop_name, - }; - await createFile("proposals.json", [staticProposal]); -}); diff --git a/tests/govtool-frontend/playwright/tests/proposal.teardown.ts b/tests/govtool-frontend/playwright/tests/proposal.teardown.ts deleted file mode 100644 index 8862cdf49..000000000 --- a/tests/govtool-frontend/playwright/tests/proposal.teardown.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { test as cleanup } from "@playwright/test"; -import { deleteProposal } from "@services/proposalDiscussion"; -import { StaticProposal } from "@types"; - -const staticProposals: StaticProposal[] = require("../lib/_mock/proposals.json"); - -cleanup("Delete Proposal", async () => { - for (let i = 0; i < staticProposals.length; i++) { - await deleteProposal(staticProposals[i].id); - } -}); From 58ab1292b4a1fd779063f65a00088eb8a30a21d0 Mon Sep 17 00:00:00 2001 From: niraj Date: Fri, 14 Jun 2024 18:22:20 +0545 Subject: [PATCH 43/49] fix: Search gov action list --- .../proposalDiscussion.spec.ts | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts index 0c0dcc61b..56bff377b 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts @@ -38,19 +38,16 @@ test("8B. Should filter and sort the list of proposed governance actions.", asyn test("8C. Should search the list of proposed governance actions.", async ({ page, }) => { + const proposalName = "Labadie, Stehr and Rosenbaum"; const proposalDiscussionPage = new ProposalDiscussionPage(page); await proposalDiscussionPage.goto(); - await proposalDiscussionPage.searchInput.fill(proposal01.title); + await proposalDiscussionPage.searchInput.fill(proposalName); const proposalCards = await proposalDiscussionPage.getAllProposals(); for (const proposalCard of proposalCards) { - expect( - (await proposalCard.textContent()) - .toLowerCase() - .includes(`${proposal01.title}`) - ).toBeTruthy(); + await expect(proposalCard.getByText(proposalName)).toBeVisible(); } }); @@ -83,17 +80,6 @@ test("8H. Should disable proposal interaction on a disconnected state.", async ( await expect(proposalDiscussionDetailsPage.commentBtn).toBeDisabled(); }); -test("8I. Should disable poll voting functionality.", async ({ - proposalDiscussionDetailsPage, -}) => { - await expect(proposalDiscussionDetailsPage.pollVoteCard).not.toBeVisible(); - await expect( - proposalDiscussionDetailsPage.pollYesVoteCount - ).not.toBeVisible(); - - await expect(proposalDiscussionDetailsPage.pollNoVoteCount).not.toBeVisible(); -}); - test("8R. Should restrict proposal creation on disconnected state", async ({ page, }) => { From 655770e133d9271d9f3815ab20aa7d22741db059 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 19:04:05 +0545 Subject: [PATCH 44/49] fix: test poll proposal --- .../proposalDiscussion.loggedin.spec.ts | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts index f1712b8ad..9851d247f 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.loggedin.spec.ts @@ -1,4 +1,4 @@ -import { user01Wallet } from "@constants/staticWallets"; +import { proposal01Wallet, user01Wallet } from "@constants/staticWallets"; import { createTempUserAuth } from "@datafactory/createAuth"; import { faker } from "@faker-js/faker"; import { test } from "@fixtures/proposal"; @@ -12,7 +12,6 @@ test.describe("Proposal created logged in state", () => { test.use({ storageState: ".auth/user01.json", wallet: user01Wallet, - pollEnabled: true, }); let proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; @@ -35,10 +34,13 @@ test.describe("Proposal created logged in state", () => { await expect(page.getByText("01", { exact: true })).toBeVisible(); }); - test("8J. Should sort the proposed governance action comments.", async ({}) => { + test("8J. Should sort the proposed governance action comments.", async ({ + page, + }) => { for (let i = 0; i < 4; i++) { const comment = faker.lorem.paragraph(2); await proposalDiscussionDetailsPage.addComment(comment); + await page.waitForTimeout(2_000); } await proposalDiscussionDetailsPage.sortAndValidate( @@ -65,7 +67,27 @@ test.describe("Proposal created logged in state", () => { await proposalDiscussionDetailsPage.replyComment(randReply); await expect(page.getByText(randReply)).toBeVisible(); }); +}); + +test.describe("Proposal created with poll enabled (user auth)", () => { + test.use({ + storageState: ".auth/user01.json", + wallet: user01Wallet, + pollEnabled: true, + }); + + let proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; + + test.beforeEach(async ({ page, proposalId, browser }) => { + const proposalPage = await createNewPageWithWallet(browser, { + storageState: ".auth/proposal01.json", + wallet: proposal01Wallet, + }); + proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(page); + await proposalDiscussionDetailsPage.goto(proposalId); + await proposalDiscussionDetailsPage.closeUsernamePrompt(); + }); test("8Q. Should vote on poll.", async ({ page }) => { const pollVotes = ["Yes", "No"]; const choice = Math.floor(Math.random() * pollVotes.length); @@ -81,7 +103,7 @@ test.describe("Proposal created logged in state", () => { await expect(page.getByText(`${oppositeVote}: (0%)`)).toBeVisible(); }); - test("8R. Should change vote on poll.", async ({ page }) => { + test("8T. Should change vote on poll.", async ({ page }) => { const pollVotes = ["Yes", "No"]; const choice = Math.floor(Math.random() * pollVotes.length); const vote = pollVotes[choice]; @@ -145,53 +167,40 @@ test.describe("Proposal created logged out state", () => { }); }); -test.describe("Add and cancel poll", () => { - test.use({ storageState: ".auth/user01.json", wallet: user01Wallet }); - let proposalId: number; - test.beforeEach(async ({ page }) => { - const proposalDiscussionPage = new ProposalDiscussionPage(page); - await proposalDiscussionPage.goto(); - await proposalDiscussionPage.closeUsernamePrompt(); - - proposalId = await proposalDiscussionPage.createProposal(); - console.log({ proposalId }); +test.describe("Proposal created with poll enabled (proposal auth)", () => { + test.use({ + storageState: ".auth/user01.json", + wallet: user01Wallet, + pollEnabled: true, }); - test.afterEach(async ({ page }) => { - const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( - page - ); - await proposalDiscussionDetailsPage.goto(proposalId); - - await proposalDiscussionDetailsPage.deleteProposal(); - }); + let proposalDiscussionDetailsPage: ProposalDiscussionDetailsPage; - test("8P. Should add poll on own proposal", async ({ page }) => { - const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( - page + test.beforeEach(async ({ browser, proposalId }) => { + const proposalPage = await createNewPageWithWallet(browser, { + storageState: ".auth/proposal01.json", + wallet: proposal01Wallet, + }); + proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( + proposalPage ); - await proposalDiscussionDetailsPage.goto(proposalId); - - await proposalDiscussionDetailsPage.addPollBtn.click(); + proposalDiscussionDetailsPage.goto(proposalId); + }); + test("8P. Should add poll on own proposal", async ({}) => { await expect(proposalDiscussionDetailsPage.addPollBtn).not.toBeVisible(); - await expect(proposalDiscussionDetailsPage.closePollBtn).toBeVisible(); }); test("8R. Should disable voting after cancelling the poll with the current poll result.", async ({ page, }) => { - const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage( - page - ); - await proposalDiscussionDetailsPage.goto(proposalId); - - await proposalDiscussionDetailsPage.addPollBtn.click(); - await proposalDiscussionDetailsPage.closePollBtn.click(); - await proposalDiscussionDetailsPage.closePollYesBtn.click(); - await expect(proposalDiscussionDetailsPage.closePollBtn).not.toBeVisible(); + + // user + const userProposalDetailsPage = new ProposalDiscussionDetailsPage(page); + await expect(userProposalDetailsPage.pollYesBtn).not.toBeVisible(); + await expect(userProposalDetailsPage.pollNoBtn).not.toBeVisible(); }); }); From 2ca0cc45c394d54af390fd874fa47111bc990e4b Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 19:19:52 +0545 Subject: [PATCH 45/49] chore: Refactor test username --- .../miscellaneous.loggedin.spec.ts | 231 +++++++++--------- 1 file changed, 110 insertions(+), 121 deletions(-) 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 index c1a9ecbf3..39ca0c662 100644 --- a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts @@ -1,152 +1,141 @@ import environments from "@constants/environments"; -import { adaHolder01Wallet, user01Wallet } from "@constants/staticWallets"; +import { user01Wallet } from "@constants/staticWallets"; +import { createTempUserAuth } from "@datafactory/createAuth"; import { faker } from "@faker-js/faker"; import { test } from "@fixtures/walletExtension"; import { setAllureEpic } from "@helpers/allure"; +import { ShelleyWallet } from "@helpers/crypto"; +import { createNewPageWithWallet } from "@helpers/page"; import DRepDirectoryPage from "@pages/dRepDirectoryPage"; import EditDRepPage from "@pages/editDRepPage"; +import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; import { expect } from "@playwright/test"; test.beforeEach(async () => { await setAllureEpic("6. Miscellaneous"); }); -test.use({ - storageState: ".auth/user01.json", - wallet: user01Wallet, -}); - -test("6E. Should open Sanchonet docs in a new tab when clicking `Learn More` on dashboards in connected state.", async ({ - page, - context, -}) => { - await page.goto("/"); - - const [delegationLearnMorepage] = await Promise.all([ - context.waitForEvent("page"), - page.getByTestId("delegate-learn-more-button").click(), - ]); - - await expect(delegationLearnMorepage).toHaveURL( - `${environments.docsUrl}/faqs/ways-to-use-your-voting-power` - ); - - const [registerLearnMorepage] = await Promise.all([ - context.waitForEvent("page"), - page.getByTestId("register-learn-more-button").click(), - ]); - - await expect(registerLearnMorepage).toHaveURL( - `${environments.docsUrl}/faqs/what-does-it-mean-to-register-as-a-drep` - ); - - const [directVoterLearnMorepage] = await Promise.all([ - context.waitForEvent("page"), - page.getByTestId("learn-more-button").first().click(), // BUG should be unique test id - ]); - - await expect(directVoterLearnMorepage).toHaveURL( - `${environments.docsUrl}/faqs/what-does-it-mean-to-register-as-a-drep` - ); - - const [GA_LearnMorepage] = await Promise.all([ - context.waitForEvent("page"), - page.getByTestId("learn-more-governance-actions-button").click(), - ]); - - await expect(GA_LearnMorepage).toHaveURL("https://sancho.network/actions/"); - - const [proposed_GA_VoterLearnMorepage] = await Promise.all([ - context.waitForEvent("page"), - page - .locator("div") - .filter({ hasText: /^ProposeLearn more$/ }) - .getByTestId("learn-more-button") - .click(), - ]); // BUG should be unique test id - - await expect(proposed_GA_VoterLearnMorepage).toHaveURL( - `${environments.docsUrl}/faqs/what-is-a-governance-action` - ); -}); - -test("6F. should open sanchonet docs in a new tab when clicking `info` button of abstain and signal-no-confidence card", async ({ - page, - context, -}) => { - const dRepDirectoryPage = new DRepDirectoryPage(page); - await dRepDirectoryPage.goto(); - - await dRepDirectoryPage.automaticDelegationOptionsDropdown.click(); +test.describe("Logged in user", () => { + test.use({ + storageState: ".auth/user01.json", + wallet: user01Wallet, + }); - const [abstain_Info_Page] = await Promise.all([ - context.waitForEvent("page"), - dRepDirectoryPage.abstainInfoButton.click(), - ]); + test("6E. Should open Sanchonet docs in a new tab when clicking `Learn More` on dashboards in connected state.", async ({ + page, + context, + }) => { + await page.goto("/"); - await expect(abstain_Info_Page).toHaveURL(`${environments.docsUrl}`); + const [delegationLearnMorepage] = await Promise.all([ + context.waitForEvent("page"), + page.getByTestId("delegate-learn-more-button").click(), + ]); + + await expect(delegationLearnMorepage).toHaveURL( + `${environments.docsUrl}/faqs/ways-to-use-your-voting-power` + ); + + const [registerLearnMorepage] = await Promise.all([ + context.waitForEvent("page"), + page.getByTestId("register-learn-more-button").click(), + ]); + + await expect(registerLearnMorepage).toHaveURL( + `${environments.docsUrl}/faqs/what-does-it-mean-to-register-as-a-drep` + ); + + const [directVoterLearnMorepage] = await Promise.all([ + context.waitForEvent("page"), + page.getByTestId("learn-more-button").first().click(), // BUG should be unique test id + ]); + + await expect(directVoterLearnMorepage).toHaveURL( + `${environments.docsUrl}/faqs/what-does-it-mean-to-register-as-a-drep` + ); + + const [GA_LearnMorepage] = await Promise.all([ + context.waitForEvent("page"), + page.getByTestId("learn-more-governance-actions-button").click(), + ]); + + await expect(GA_LearnMorepage).toHaveURL("https://sancho.network/actions/"); + + const [proposed_GA_VoterLearnMorepage] = await Promise.all([ + context.waitForEvent("page"), + page + .locator("div") + .filter({ hasText: /^ProposeLearn more$/ }) + .getByTestId("learn-more-button") + .click(), + ]); // BUG should be unique test id + + await expect(proposed_GA_VoterLearnMorepage).toHaveURL( + `${environments.docsUrl}/faqs/what-is-a-governance-action` + ); + }); - const [signal_No_Confidence_Info_Page] = await Promise.all([ - context.waitForEvent("page"), - dRepDirectoryPage.signalNoConfidenceInfoButton.click(), - ]); + test("6F. should open sanchonet docs in a new tab when clicking `info` button of abstain and signal-no-confidence card", async ({ + page, + context, + }) => { + const dRepDirectoryPage = new DRepDirectoryPage(page); + await dRepDirectoryPage.goto(); - await expect(signal_No_Confidence_Info_Page).toHaveURL( - `${environments.docsUrl}` - ); -}); + await dRepDirectoryPage.automaticDelegationOptionsDropdown.click(); -test("6G. Should restrict edit dRep for non dRep", async ({ page }) => { - const editDrepPage = new EditDRepPage(page); - await editDrepPage.goto(); + const [abstain_Info_Page] = await Promise.all([ + context.waitForEvent("page"), + dRepDirectoryPage.abstainInfoButton.click(), + ]); - await page.waitForTimeout(2_000); - await expect(editDrepPage.nameInput).not.toBeVisible(); -}); + await expect(abstain_Info_Page).toHaveURL(`${environments.docsUrl}`); -test("6I. Should prompt for a username after clicking on proposal discussion link if username is not set", async ({ - page, -}) => { - await page.goto("/"); - await page.getByTestId("proposal-discussion-link").click(); + const [signal_No_Confidence_Info_Page] = await Promise.all([ + context.waitForEvent("page"), + dRepDirectoryPage.signalNoConfidenceInfoButton.click(), + ]); - await expect( - page.getByText( - "Hey, setup your usernameUsername cannot be changed in the Future. Some subtext" - ) - ).toBeVisible(); //BUG Add modal testid instead should be username-modal + await expect(signal_No_Confidence_Info_Page).toHaveURL( + `${environments.docsUrl}` + ); + }); - await expect(page.getByLabel("Username *")).toBeVisible(); // BUG use testid instead -}); + test("6G. Should restrict edit dRep for non dRep", async ({ page }) => { + const editDrepPage = new EditDRepPage(page); + await editDrepPage.goto(); -test.describe("Add username", () => { - test.use({ - storageState: ".auth/adaHolder01.json", - wallet: adaHolder01Wallet, + await page.waitForTimeout(2_000); + await expect(editDrepPage.nameInput).not.toBeVisible(); }); - test("6J. Should add a username.", async ({ page }) => { - const username = faker.person.firstName(); - + test("6I. Should prompt for a username after clicking on proposal discussion link if username is not set", async ({ + page, + }) => { await page.goto("/"); await page.getByTestId("proposal-discussion-link").click(); - await page.getByLabel("Username *").fill(username); //BUG used testid instead - - await page - .getByRole("button", { name: "Proceed with this username" }) - .click(); //BUG used testid instead - - await page - .getByRole("button", { name: "Proceed with this username" }) - .click(); - - await page.getByRole("button", { name: "Close" }).click(); - await expect( - page.getByRole("button", { name: "Propose a Governance Action" }) - ).toBeVisible(); + page.getByText( + "Hey, setup your usernameUsername cannot be changed in the Future. Some subtext" + ) + ).toBeVisible(); //BUG Add modal testid instead should be username-modal + + await expect(page.getByLabel("Username *")).toBeVisible(); // BUG use testid instead + }); +}); - await expect(page.getByPlaceholder("Search...")).toBeVisible(); +test.describe("Temporary user", () => { + test("6J. Should add a username.", async ({ page, browser }) => { + const wallet = (await ShelleyWallet.generate()).json(); + const tempUserAuth = await createTempUserAuth(page, wallet); + const userPage = await createNewPageWithWallet(browser, { + storageState: tempUserAuth, + wallet, + }); + + const proposalDiscussionPage = new ProposalDiscussionPage(userPage); + await proposalDiscussionPage.goto(); + await proposalDiscussionPage.setUsername(faker.internet.userName()); }); }); From dd464db02d378ed6a4e6e201147e59a0c558ca22 Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Fri, 14 Jun 2024 19:37:31 +0545 Subject: [PATCH 46/49] test: Comments visibility --- .../playwright/lib/_mock/proposal.json | 2 +- .../lib/_mock/proposalComments.json | 51 +++++++++++++++++++ .../_mock/{poll.json => proposalPoll.json} | 0 .../proposalDiscussion.spec.ts | 19 +++++-- 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 tests/govtool-frontend/playwright/lib/_mock/proposalComments.json rename tests/govtool-frontend/playwright/lib/_mock/{poll.json => proposalPoll.json} (100%) diff --git a/tests/govtool-frontend/playwright/lib/_mock/proposal.json b/tests/govtool-frontend/playwright/lib/_mock/proposal.json index 9b9ac9ad6..be8ef6380 100644 --- a/tests/govtool-frontend/playwright/lib/_mock/proposal.json +++ b/tests/govtool-frontend/playwright/lib/_mock/proposal.json @@ -4,7 +4,7 @@ "attributes": { "prop_likes": 0, "prop_dislikes": 0, - "prop_comments_number": 0, + "prop_comments_number": 3, "prop_submited": false, "prop_status_id": null, "user_id": "52", diff --git a/tests/govtool-frontend/playwright/lib/_mock/proposalComments.json b/tests/govtool-frontend/playwright/lib/_mock/proposalComments.json new file mode 100644 index 000000000..12f0415e2 --- /dev/null +++ b/tests/govtool-frontend/playwright/lib/_mock/proposalComments.json @@ -0,0 +1,51 @@ +{ + "data": [ + { + "id": 139, + "attributes": { + "proposal_id": "128", + "comment_parent_id": null, + "user_id": "20", + "comment_text": "Hello", + "createdAt": "2024-06-14T13:38:35.830Z", + "updatedAt": "2024-06-14T13:38:35.830Z", + "user_govtool_username": "Anonymous", + "subcommens_number": 0 + } + }, + { + "id": 138, + "attributes": { + "proposal_id": "128", + "comment_parent_id": null, + "user_id": "20", + "comment_text": "Nice proposal", + "createdAt": "2024-06-14T13:38:31.279Z", + "updatedAt": "2024-06-14T13:38:31.279Z", + "user_govtool_username": "Anonymous", + "subcommens_number": 0 + } + }, + { + "id": 137, + "attributes": { + "proposal_id": "128", + "comment_parent_id": null, + "user_id": "20", + "comment_text": "Go Ahead", + "createdAt": "2024-06-14T13:38:27.286Z", + "updatedAt": "2024-06-14T13:38:27.286Z", + "user_govtool_username": "Anonymous", + "subcommens_number": 0 + } + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 3 + } + } +} diff --git a/tests/govtool-frontend/playwright/lib/_mock/poll.json b/tests/govtool-frontend/playwright/lib/_mock/proposalPoll.json similarity index 100% rename from tests/govtool-frontend/playwright/lib/_mock/poll.json rename to tests/govtool-frontend/playwright/lib/_mock/proposalPoll.json diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts index 56bff377b..d00bb4f01 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts @@ -6,7 +6,8 @@ import ProposalDiscussionPage from "@pages/proposalDiscussionPage"; import { expect } from "@playwright/test"; const mockProposal = require("../../lib/_mock/proposal.json"); -const mockPoll = require("../../lib/_mock/poll.json"); +const mockPoll = require("../../lib/_mock/proposalPoll.json"); +const mockComments = require("../../lib/_mock/proposalComments.json"); test.beforeEach(() => { setAllureEpic("Proposal Discussion Forum"); @@ -80,7 +81,7 @@ test("8H. Should disable proposal interaction on a disconnected state.", async ( await expect(proposalDiscussionDetailsPage.commentBtn).toBeDisabled(); }); -test("8R. Should restrict proposal creation on disconnected state", async ({ +test("8S. Should restrict proposal creation on disconnected state", async ({ page, }) => { const proposalDiscussionPage = new ProposalDiscussionPage(page); @@ -99,12 +100,18 @@ test.describe("Mocked proposal", () => { }) ); - await page.route("**/api/polls/**", async (route) => + await page.route("**/api/polls**", async (route) => route.fulfill({ body: JSON.stringify(mockPoll), }) ); + await page.route("**/api/comments**", async (route) => + route.fulfill({ + body: JSON.stringify(mockComments), + }) + ); + proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(page); await proposalDiscussionDetailsPage.goto(10); }); @@ -129,4 +136,10 @@ test.describe("Mocked proposal", () => { await expect(proposalDiscussionDetailsPage.pollNoBtn).not.toBeVisible(); }); + + test("8F. Should display all comments with count indication.", async () => { + await expect(proposalDiscussionDetailsPage.commentsCount).toHaveText( + mockProposal.data.attributes.prop_comments_number + ); + }); }); From 4255fa30ff267e30d20a4fef3d593c6502446a4c Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Wed, 19 Jun 2024 16:54:48 +0545 Subject: [PATCH 47/49] chore: Remove unused proposal discussion services --- .../lib/services/proposalDiscussion/index.ts | 84 ------------------- .../lib/services/proposalDiscussion/types.ts | 27 ------ .../lib/services/proposalDiscussionService.ts | 0 3 files changed, 111 deletions(-) delete mode 100644 tests/govtool-frontend/playwright/lib/services/proposalDiscussion/index.ts delete mode 100644 tests/govtool-frontend/playwright/lib/services/proposalDiscussion/types.ts delete mode 100644 tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts diff --git a/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/index.ts b/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/index.ts deleted file mode 100644 index d8f7d1c76..000000000 --- a/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/index.ts +++ /dev/null @@ -1,84 +0,0 @@ -import environments from "@constants/environments"; -import { Logger } from "@helpers/logger"; -import { RequestInit } from "node-fetch"; -import { CommentRequest, PollRequest, ProposalCreateRequest } from "./types"; - -import fetch = require("node-fetch"); - -export const postCreateProposal = async (data: ProposalCreateRequest) => { - const endpoint = "/proposals"; - const options: RequestInit = { - method: "POST", - headers: { - "content-type": "application/json", - authorization: - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", - }, - body: JSON.stringify({ data: { data } }), - }; - - return fetchClient(endpoint, options); -}; - -export const deleteProposal = async (proposalId: number) => { - const endpoint = `/proposals/${proposalId}`; - const options: RequestInit = { - method: "DELETE", - headers: { - "content-type": "application/json", - authorization: - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", - }, - }; - - return fetchClient(endpoint, options); -}; - -export const postAddPoll = async (data: PollRequest) => { - const endpoint = `/api/polls`; - const options: RequestInit = { - method: "DELETE", - headers: { - "content-type": "application/json", - authorization: - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", - }, - body: JSON.stringify({ data: { data } }), - }; - - return fetchClient(endpoint, options); -}; - -export const postAddComment = async (data: CommentRequest) => { - const endpoint = `/comments`; - const options = { - method: "POST", - headers: { - "content-type": "application/json", - authorization: - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzMsImlhdCI6MTcxODE2NjA5NiwiZXhwIjoxNzIwNzU4MDk2fQ.oWJefxnDGosktBlPQTvJ01Xqxa8YVAuhYs9MQPJE9po", - }, - body: JSON.stringify({ data: { data } }), - }; - - return fetchClient(endpoint, options); -}; - -async function fetchClient( - endpoint: string, - options: RequestInit -): Promise { - try { - const res = await fetch(`${environments.pdfUrl}/api` + endpoint, options); - const response = await res.json(); - - if (res.ok) { - return response; - } else { - throw new Error(response.error?.message || "Unknown error occurred"); - } - } catch (error) { - Logger.fail(`API request failed: ${error.message}`); - throw error; - } -} diff --git a/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/types.ts b/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/types.ts deleted file mode 100644 index 36f1d5979..000000000 --- a/tests/govtool-frontend/playwright/lib/services/proposalDiscussion/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -type ProposalLinksType = { - prop_link: string; - prop_link_text: string; -}; - -export type ProposalCreateRequest = { - proposal_links: Array; - gov_action_type_id: number; - prop_name: string; - prop_abstract: string; - prop_motivation: string; - prop_rationale: string; - prop_receiving_address: string; - prop_amount: string; - is_draft: boolean; -}; - -export type CommentRequest = { - proposal_id: string; - comment_text: string; -}; - -export type PollRequest = { - proposal_id: string; - poll_start_dt: string; - is_poll_active: boolean; -}; diff --git a/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts b/tests/govtool-frontend/playwright/lib/services/proposalDiscussionService.ts deleted file mode 100644 index e69de29bb..000000000 From 1b147e0797b16c6d2d74397468a2a73c9e8f5fff Mon Sep 17 00:00:00 2001 From: Nabin Kawan Date: Wed, 19 Jun 2024 17:02:27 +0545 Subject: [PATCH 48/49] chore: Fix typo in test title (6F) --- .../tests/6-miscellaneous/miscellaneous.loggedin.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 39ca0c662..2d3d1c46c 100644 --- a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts @@ -75,7 +75,7 @@ test.describe("Logged in user", () => { ); }); - test("6F. should open sanchonet docs in a new tab when clicking `info` button of abstain and signal-no-confidence card", async ({ + test("6F. Should open sanchonet docs in a new tab when clicking `info` button of abstain and signal-no-confidence card", async ({ page, context, }) => { From 4b95ff31fc18744c21d7aef4ac8a13e784bb6dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Wed, 19 Jun 2024 13:38:51 +0200 Subject: [PATCH 49/49] chore: add force rebuild images toggle to github actions --- .github/workflows/build-and-deploy-beta.yml | 9 +++++++++ .github/workflows/build-and-deploy-dev.yml | 9 +++++++++ .github/workflows/build-and-deploy-staging.yml | 9 +++++++++ .github/workflows/build-and-deploy-test.yml | 9 +++++++++ scripts/govtool/common.mk | 9 +++++++-- 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-deploy-beta.yml b/.github/workflows/build-and-deploy-beta.yml index cb2090349..6af69324b 100644 --- a/.github/workflows/build-and-deploy-beta.yml +++ b/.github/workflows/build-and-deploy-beta.yml @@ -13,11 +13,20 @@ on: options: - "enabled" - "disabled" + forceRebuildDockerImages: + description: "Force rebuild the docker images" + required: false + type: choice + default: "false" + options: + - "true" + - "false" env: ENVIRONMENT: "beta" CARDANO_NETWORK: "sanchonet" DOMAIN: "sanchogov.tools" + FORCE_REBUILD: ${{inputs.forceRebuildDockerImages == 'true'}} jobs: deploy: diff --git a/.github/workflows/build-and-deploy-dev.yml b/.github/workflows/build-and-deploy-dev.yml index c07d88741..7755dc10f 100644 --- a/.github/workflows/build-and-deploy-dev.yml +++ b/.github/workflows/build-and-deploy-dev.yml @@ -12,11 +12,20 @@ on: options: - "enabled" - "disabled" + forceRebuildDockerImages: + description: "Force rebuild the docker images" + required: false + type: choice + default: "false" + options: + - "true" + - "false" env: ENVIRONMENT: "dev" CARDANO_NETWORK: "sanchonet" DOMAIN: "dev-sanchonet.govtool.byron.network" + FORCE_REBUILD: ${{inputs.forceRebuildDockerImages == 'true'}} jobs: deploy: diff --git a/.github/workflows/build-and-deploy-staging.yml b/.github/workflows/build-and-deploy-staging.yml index c841a1a15..7534040d3 100644 --- a/.github/workflows/build-and-deploy-staging.yml +++ b/.github/workflows/build-and-deploy-staging.yml @@ -15,11 +15,20 @@ on: options: - "enabled" - "disabled" + forceRebuildDockerImages: + description: "Force rebuild the docker images" + required: false + type: choice + default: "false" + options: + - "true" + - "false" env: ENVIRONMENT: "staging" CARDANO_NETWORK: "sanchonet" DOMAIN: "staging.govtool.byron.network" + FORCE_REBUILD: ${{inputs.forceRebuildDockerImages == 'true'}} jobs: deploy: diff --git a/.github/workflows/build-and-deploy-test.yml b/.github/workflows/build-and-deploy-test.yml index 45fd36015..b2c99fd83 100644 --- a/.github/workflows/build-and-deploy-test.yml +++ b/.github/workflows/build-and-deploy-test.yml @@ -15,11 +15,20 @@ on: options: - "enabled" - "disabled" + forceRebuildDockerImages: + description: "Force rebuild the docker images" + required: false + type: choice + default: "false" + options: + - "true" + - "false" env: ENVIRONMENT: "test" CARDANO_NETWORK: "sanchonet" DOMAIN: "test-sanchonet.govtool.byron.network" + FORCE_REBUILD: ${{inputs.forceRebuildDockerImages == 'true'}} jobs: deploy: diff --git a/scripts/govtool/common.mk b/scripts/govtool/common.mk index 7e801fed4..77cd61747 100644 --- a/scripts/govtool/common.mk +++ b/scripts/govtool/common.mk @@ -34,10 +34,15 @@ check_defined = \ __check_defined = \ $(if $(value $1),, \ $(error Undefined $1$(if $2, ($2)))) - + +force_rebuild := $(shell echo $${FORCE_REBUILD:-false}) # helper function for checking if image exists on ECR check_image_on_ecr = \ - $(docker) manifest inspect "$(repo_url)/$1:$2" > /dev/null 2>&1 + if [ "$(force_rebuild)" = "true" ]; then \ + false; \ + else \ + $(docker) manifest inspect "$(repo_url)/$1:$2" > /dev/null 2>&1; \ + fi .PHONY: check-env-defined check-env-defined: