E2E - Web3 interaction & more tests (#202)

* account switching & setting gas for e2e web3

* e2e web3 interaction + new specs

* refund after payout spec

* use send instead of sendAsync + update ganache script cmd

* sign intercept + deterministic accounts

* add authentication to creation tests

* adjust cy.request call

* refactor + WIP fiddle with xhr

* xhook to modify incoming api requests
This commit is contained in:
AMStrix 2018-11-21 21:20:09 -06:00 committed by Daniel Ternyak
parent d367e6e474
commit 968974d8d7
22 changed files with 1310 additions and 209 deletions

View File

@ -1 +1,3 @@
{}
{
"baseUrl": "http://localhost:3000"
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
syncTimeWithEvm,
randomString
} from "../helpers";
import { createDemoProposal, authenticateUser } from "../parts";
describe("authenticate", () => {
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
});

View File

@ -3,13 +3,13 @@ import { loadWeb3, randomString, randomHex } from "../helpers";
describe("browse", () => {
it("should load and be able to browse pages", () => {
cy.visit("http://localhost:3000", { onBeforeLoad: loadWeb3 });
cy.visit("http://localhost:3000", { onBeforeLoad: loadWeb3(0) });
cy.title().should("include", "Grant.io - Home");
// test hero create link
cy.get('.Home-hero-buttons a[href="/create"]')
// {force: true} here overcomes a strange issue where the button moves up under the header
// this is likely a cypress-related problem
// this is likely a cypress scroll related problem
.click({ force: true });
cy.title().should("include", "Grant.io - Create a Proposal");

View File

@ -0,0 +1,47 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
syncTimeWithEvm,
randomString
} from "../helpers";
import { createDemoProposal, authenticateUser } from "../parts";
describe("proposal", () => {
const id = randomString();
const title = `[${id}] e2e create cancel`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
(Cypress as any).runner.stop();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("cancels the proposal", () => {
cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click();
cy.contains(".ant-dropdown-menu-item", "Cancel proposal").click();
cy.contains(".ant-modal-footer > div button", "Confirm").click();
cy.contains("body", "Proposal didnt get funded", { timeout: 20000 });
cy.get(".ant-modal-wrap").should("not.be.visible");
cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click();
cy.contains(".ant-dropdown-menu-item", "Cancel proposal").should(
"have.attr",
"aria-disabled",
"true"
);
});
it("should appear unfunded to outsiders (account 9)", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(9) }));
cy.contains("body", "Proposal didnt get funded", { timeout: 20000 });
});
});

View File

@ -0,0 +1,173 @@
/// <reference types="cypress"/>
import { loadWeb3, randomString, randomHex, syncTimeWithEvm } from "../helpers";
import { authenticateUser } from "../parts";
describe("create.flow", () => {
const time = new Date().toLocaleString();
const id = randomString();
const randomEthHex = randomHex(32);
const nextYear = new Date().getUTCFullYear() + 1;
const proposal = {
title: `[${id}] e2e create flow`,
brief: "e2e brief",
category: "Community", // .anticon-team
targetAmount: 5,
body: `#### e2e Proposal ${id} {enter} **created** ${time} `,
team: [
{
name: "Alisha Endtoend",
title: "QA Robot0",
ethAddress: `0x0000${randomEthHex}0000`,
emailAddress: `qa.alisha.${id}@grant.io`
},
{
name: "Billy Endtoend",
title: "QA Robot1",
ethAddress: `0x1111${randomEthHex}1111`,
emailAddress: `qa.billy.${id}@grant.io`
}
],
milestones: [
{
title: `e2e Milestone ${id} 0`,
body: `e2e Milestone ${id} {enter} body 0`,
date: {
y: nextYear,
m: "Jan",
expect: "January " + nextYear
}
},
{
title: `e2e Milestone ${id} 1`,
body: `e2e Milestone ${id} {enter} body 1`,
date: {
y: nextYear,
m: "Feb",
expect: "February " + nextYear
}
}
]
};
afterEach(function() {
if (this.currentTest.state === "failed") {
(Cypress as any).runner.stop();
}
});
context("create flow wizard", () => {
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("create flow step 1", () => {
cy.get('[href="/create"]').click();
syncTimeWithEvm(cy);
cy.get('.CreateFlow input[name="title"]').type(proposal.title);
cy.get('.CreateFlow textarea[name="brief"]').type(proposal.brief);
cy.contains("Select a category").click();
cy.get(".ant-select-dropdown li .anticon-team").click();
cy.get('.CreateFlow input[name="amountToRaise"]').type(
"" + proposal.targetAmount
);
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("create flow step 2", () => {
cy.get("button.TeamForm-add").click();
cy.get('.TeamMember-info input[name="name"]').type(proposal.team[1].name);
cy.get('.TeamMember-info input[name="title"]').type(
proposal.team[1].title
);
cy.get('.TeamMember-info input[name="ethAddress"]').type(
proposal.team[1].ethAddress
);
cy.get('.TeamMember-info input[name="emailAddress"]').type(
proposal.team[1].emailAddress
);
cy.get("button")
.contains("Save changes")
.click({ force: true });
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("create flow step 3", () => {
cy.get(".DraftEditor-editorContainer > div").type(proposal.body);
cy.get(".mde-tabs > :nth-child(2)").click();
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("create flow step 4", () => {
cy.get('input[name="title"]').type(proposal.milestones[0].title);
cy.get('textarea[name="body"]').type(proposal.milestones[0].body);
cy.get('input[placeholder="Expected completion date"]').click();
cy.get(".ant-calendar-month-panel-next-year-btn").click();
cy.get(".ant-calendar-month-panel-month")
.contains(proposal.milestones[0].date.m)
.click();
cy.get(".ant-calendar-picker-input").should(
"have.value",
proposal.milestones[0].date.expect
);
cy.get("button")
.contains("Add another milestone")
.click({ force: true });
cy.get('input[name="title"]')
.eq(1)
.type(proposal.milestones[1].title);
cy.get('textarea[name="body"]')
.eq(1)
.type(proposal.milestones[1].body);
cy.get('input[placeholder="Expected completion date"]')
.eq(1)
.click();
cy.get(".ant-calendar-month-panel-next-year-btn").click();
cy.get(".ant-calendar-month-panel-month")
.contains(proposal.milestones[1].date.m)
.click();
cy.get(".ant-calendar-picker-input")
.eq(1)
.should("have.value", proposal.milestones[1].date.expect);
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("create flow step 5", () => {
cy.window()
.then(w => (w as any).web3.eth.getAccounts())
.then(accts => {
cy.get('input[name="payOutAddress"]').type(accts[0]);
cy.get("button")
.contains("Add another trustee")
.click({ force: true });
cy.get(
'input[placeholder="0x8B0B72F8bDE212991135668922fD5acE557DE6aB"]'
)
.eq(1)
.type(accts[1]);
cy.get('input[name="deadline"][value="2592000"]').click({
force: true
});
cy.get('input[name="milestoneDeadline"][value="259200"]').click({
force: true
});
});
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("publishes the proposal", () => {
cy.get("button")
.contains("Publish")
.click();
cy.get(".CreateFinal-loader-text").contains("Deploying contract...");
cy.get(".CreateFinal-message-text a", { timeout: 20000 })
.contains("Click here")
.click();
cy.get(".Proposal-top-main-title").contains(proposal.title);
});
});
});

View File

@ -0,0 +1,61 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
syncTimeWithEvm,
randomString
} from "../helpers";
import { createDemoProposal, authenticateUser } from "../parts";
describe("create.fund.cancel", () => {
const id = randomString();
const title = `[${id}] e2e create fund cancel`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
(Cypress as any).runner.stop();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("create demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("funds the proposal with account 5", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.get(".ant-input", { timeout: 20000 }).type(amount);
cy.get(".ant-form > .ant-btn").click();
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("cancels the proposal (refund contributors)", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click();
cy.contains(".ant-dropdown-menu-item", "Refund contributors").click();
cy.contains(".ant-modal-footer > div button", "Confirm").click();
cy.get(".ant-modal-wrap", { timeout: 20000 }).should("not.be.visible");
cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click();
cy.contains(".ant-dropdown-menu-item", "Refund contributors").should(
"have.attr",
"aria-disabled",
"true"
);
});
it("refunds the contributor (account 5)", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-tabs-nav > :nth-child(1) > :nth-child(4)", "Refund", {
timeout: 20000
}).click();
// force disables cypress' auto scrolling which messes up UI in this case
cy.contains(".ant-btn", "Get your refund").click({ force: true });
cy.contains("body", "Your refund has been processed", { timeout: 20000 });
});
});

View File

@ -0,0 +1,126 @@
/// <reference types="cypress"/>
import {
loadWeb3,
randomString,
syncTimeWithEvm,
increaseTime
} from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.complete.minority-no-votes", () => {
const id = randomString();
const title = `[${id}] e2e minority no-votes complete`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("funds the proposal from accounts 5, 6 & 7", () => {
fundProposal(cy, 5, 0.1);
fundProposal(cy, 6, 0.2);
fundProposal(cy, 7, 0.7);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
});
it("requests milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("minority funder (acct 5) votes no", () => {
// VOTE NO
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 });
});
it("expires milestone 2 voting period & receives payout", () => {
// EXPIRE
increaseTime(cy, 70000);
// RECEIVE PAYOUT
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).click();
});
it("requests milestone 3 payout", () => {
// MILESTONE 3
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("minority funder (acct 5) votes no", () => {
// VOTE NO
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 });
});
it("expires milestone 3 voting period & receives payout", () => {
// EXPIRE
increaseTime(cy, 70000);
// RECEIVE PAYOUT
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).click();
});
it("should not have receive button", () => {
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).should("not.exist");
});
});

View File

@ -0,0 +1,103 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
syncTimeWithEvm,
randomString
} from "../helpers";
import { createDemoProposal, authenticateUser } from "../parts";
describe("create.fund.complete", () => {
const id = randomString();
const title = `[${id}] e2e create fund complete`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("create demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("funds the proposal with account 5", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.get(".ant-input", { timeout: 20000 }).type(amount);
cy.get(".ant-form > .ant-btn").click();
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout", () => {
// MILESTONE 1
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request initial payout",
{ timeout: 20000 }
)
.as("RequestPayout")
.click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
});
it("requests and receives milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
// EXPIRE
increaseTime(cy, 70000);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).click();
});
it("requests and receives milestone 3 payout", () => {
// MILESTONE 3
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
// EXPIRE
increaseTime(cy, 70000);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).click();
});
it("should not have receive button", () => {
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).should("not.exist");
});
});

View File

@ -0,0 +1,60 @@
/// <reference types="cypress"/>
import { loadWeb3, randomString, syncTimeWithEvm } from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.ms2.majority-no-vote", () => {
const id = randomString();
const title = `[${id}] e2e ms2 majority no-vote`;
const amount = "1";
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("fund the proposal with 5th account", () => {
fundProposal(cy, 5, 1);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout for milestone 1", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
);
});
it("requests milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("vote against milestone 2 payout as account 5", () => {
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 }).click();
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 });
});
});

View File

@ -0,0 +1,87 @@
/// <reference types="cypress"/>
import {
loadWeb3,
randomString,
syncTimeWithEvm,
increaseTime
} from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.ms2.no-vote.re-vote", () => {
const id = randomString();
const title = `[${id}] e2e ms2 no-vote expire re-vote`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("fund the proposal with 5th account", () => {
fundProposal(cy, 5, 1);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout for milestone 1", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
});
it("request milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("vote against milestone 2 payout as 5th account", () => {
// reload page with 5th account
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 });
});
it("milestone 2 vote expires and payout is requested again", () => {
// EXPIRE
increaseTime(cy, 70000);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains("Payout was voted against");
// RE-REQUEST PAYOUT
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
// TODO: fix this bug (the following fails)
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
});

View File

@ -0,0 +1,69 @@
/// <reference types="cypress"/>
import {
loadWeb3,
randomString,
syncTimeWithEvm,
increaseTime
} from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.ms2.refund-after-payout", () => {
const id = randomString();
const title = `[${id}] e2e ms2 refund after payout`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("fund the proposal with account 5", () => {
fundProposal(cy, 5, 1);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout for milestone 1", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
);
});
it("majority refund vote and get refund (account 5)", () => {
// REFUND
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-tabs-nav > :nth-child(1) > :nth-child(4)", "Refund", {
timeout: 20000
}).click();
// INCREASE TIME
increaseTime(cy, 70000);
// force disables cypress' auto scrolling which messes up UI in this case
cy.contains(".ant-btn", "Vote for refund").click({ force: true });
cy.contains(".ant-btn", "Get your refund", { timeout: 20000 }).click({
force: true
});
cy.contains("body", "Your refund has been processed", { timeout: 20000 });
});
});

View File

@ -0,0 +1,93 @@
/// <reference types="cypress"/>
import {
loadWeb3,
randomString,
syncTimeWithEvm,
increaseTime
} from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.ms2.revert-no-vote", () => {
const id = randomString();
const title = `[${id}] e2e ms2 revert no-vote`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("funds the proposal with 5th account", () => {
fundProposal(cy, 5, 1);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout for milestone 1", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
});
it("requests milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("votes against milestone 2 payout as account 5 and then reverts the vote", () => {
// NO VOTE... REVERT
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 });
});
it("milestone 2 vote expires and payout is received", () => {
// EXPIRE
increaseTime(cy, 70000);
// PAYOUT
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout"
).click();
});
it("milestone 3 becomes active", () => {
// MILESTONE 3
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
);
});
});

View File

@ -1,192 +0,0 @@
/// <reference types="cypress"/>
import { loadWeb3, randomString, randomHex } from "../helpers";
describe("create proposal", () => {
it("should load and be able to browse pages", () => {
cy.visit("http://localhost:3000/create", { onBeforeLoad: loadWeb3 });
// populate ethAccounts
cy.wait(1000);
cy.window()
.then(w => (w as any).web3.eth.getAccounts())
.as("EthAccounts");
// demo proposal
// cy.get("button.CreateFlow-footer-example").click();
const time = new Date().toLocaleString();
const id = randomString();
const randomEthHex = randomHex(32);
const nextYear = new Date().getUTCFullYear() + 1;
const proposal = {
title: "e2e - smoke - create " + time,
brief: "e2e brief",
category: "Community", // .anticon-team
targetAmount: 5,
body: `#### e2e Proposal ${id} {enter} **created** ${time} `,
team: [
{
name: "Alisha Endtoend",
title: "QA Robot0",
ethAddress: `0x0000${randomEthHex}0000`,
emailAddress: `qa.alisha.${id}@grant.io`
},
{
name: "Billy Endtoend",
title: "QA Robot1",
ethAddress: `0x1111${randomEthHex}1111`,
emailAddress: `qa.billy.${id}@grant.io`
}
],
milestones: [
{
title: `e2e Milestone ${id} 0`,
body: `e2e Milestone ${id} {enter} body 0`,
date: {
y: nextYear,
m: "Jan",
expect: "January " + nextYear
}
},
{
title: `e2e Milestone ${id} 1`,
body: `e2e Milestone ${id} {enter} body 1`,
date: {
y: nextYear,
m: "Feb",
expect: "February " + nextYear
}
}
]
};
// step 1
cy.get('.CreateFlow input[name="title"]', { timeout: 20000 }).type(
proposal.title
);
cy.get('.CreateFlow textarea[name="brief"]').type(proposal.brief);
cy.contains("Select a category").click();
cy.get(".ant-select-dropdown li .anticon-team").click();
cy.get('.CreateFlow input[name="amountToRaise"]').type(
"" + proposal.targetAmount
);
cy.get(".CreateFlow-footer-button")
.contains("Continue")
.as("Continue")
.click();
// step 2
cy.get('.TeamMember-info input[name="name"]').type(proposal.team[0].name);
cy.get('.TeamMember-info input[name="title"]').type(proposal.team[0].title);
cy.get("@EthAccounts").then(accts => {
cy.get('.TeamMember-info input[name="ethAddress"]').type(
accts[0].toString()
);
});
cy.get('.TeamMember-info input[name="emailAddress"]').type(
proposal.team[0].emailAddress
);
cy.get("button")
.contains("Save changes")
.click({ force: true });
cy.get("button.TeamForm-add").click();
cy.get('.TeamMember-info input[name="name"]').type(proposal.team[1].name);
cy.get('.TeamMember-info input[name="title"]').type(proposal.team[1].title);
cy.get("@EthAccounts").then(accts => {
cy.get('.TeamMember-info input[name="ethAddress"]').type(
accts[1].toString()
);
});
cy.get('.TeamMember-info input[name="emailAddress"]').type(
proposal.team[1].emailAddress
);
cy.get("button")
.contains("Save changes")
.click({ force: true });
cy.wait(1000);
cy.get("@Continue").click();
// step 3
cy.get(".DraftEditor-editorContainer > div").type(proposal.body);
cy.get(".mde-tabs > :nth-child(2)").click();
cy.wait(1000);
cy.get("@Continue").click();
// step 4
cy.get('input[name="title"]').type(proposal.milestones[0].title);
cy.get('textarea[name="body"]').type(proposal.milestones[0].body);
cy.get('input[placeholder="Expected completion date"]').click();
cy.get(".ant-calendar-month-panel-next-year-btn").click();
cy.get(".ant-calendar-month-panel-month")
.contains(proposal.milestones[0].date.m)
.click();
cy.get(".ant-calendar-picker-input").should(
"have.value",
proposal.milestones[0].date.expect
);
cy.get("button")
.contains("Add another milestone")
.click({ force: true });
cy.get('input[name="title"]')
.eq(1)
.type(proposal.milestones[1].title);
cy.get('textarea[name="body"]')
.eq(1)
.type(proposal.milestones[1].body);
cy.get('input[placeholder="Expected completion date"]')
.eq(1)
.click();
cy.get(".ant-calendar-month-panel-next-year-btn").click();
cy.get(".ant-calendar-month-panel-month")
.contains(proposal.milestones[1].date.m)
.click();
cy.get(".ant-calendar-picker-input")
.eq(1)
.should("have.value", proposal.milestones[1].date.expect);
cy.wait(1000);
cy.get("@Continue").click();
// step 5
cy.window()
.then(w => (w as any).web3.eth.getAccounts())
.then(accts => {
cy.get('input[name="payOutAddress"]').type(accts[0]);
cy.get("button")
.contains("Add another trustee")
.click({ force: true });
cy.get(
'input[placeholder="0x8B0B72F8bDE212991135668922fD5acE557DE6aB"]'
)
.eq(1)
.type(accts[1]);
cy.get('input[name="deadline"][value="2592000"]').click({
force: true
});
cy.get('input[name="milestoneDeadline"][value="259200"]').click({
force: true
});
});
cy.wait(1000);
cy.get("@Continue").click();
// final
cy.get("button")
.contains("Publish")
.click();
cy.get(".CreateFinal-loader-text").contains("Deploying contract...");
cy.get(".CreateFinal-message-text a", { timeout: 20000 })
.contains("Click here")
.click();
// done
cy.get(".Proposal-top-main-title").contains(proposal.title);
});
});

View File

@ -0,0 +1,15 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
randomString,
syncTimeWithEvm
} from "../helpers";
// describe("sandbox", () => {
// it("how to increase time", () => {
// cy.visit("http://localhost:3000", { onBeforeLoad: loadWeb3(0) });
// // increase time on browser and ganache
// increaseTime(cy, 60000);
// });
// });

107
e2e/cypress/parts.ts Normal file
View File

@ -0,0 +1,107 @@
/// <reference types="cypress"/>
import { syncTimeWithEvm, loadWeb3, testAccounts } from "./helpers";
export const authenticateUser = (
cy: Cypress.Chainable,
accountIndex: number
) => {
const name = `Qual Itty ${accountIndex}`;
const ethAccount = testAccounts[accountIndex][0];
const title = `QA Robot ${accountIndex}`;
const email = `qa.robot.${accountIndex}@grant.io`;
cy.visit("http://localhost:3000", { onBeforeLoad: loadWeb3(accountIndex) });
syncTimeWithEvm(cy);
cy.get(".AuthButton").click();
cy.request({
url: `http://localhost:5000/api/v1/users/${ethAccount}`,
method: "GET",
failOnStatusCode: false
})
.its("status")
.then(status => {
if (status === 200) {
cy.contains("button", "Prove identity").click();
} else {
cy.get("input[name='name']").type(name);
cy.get("input[name='title']").type(title);
cy.get("input[name='email']").type(email);
cy.contains("button", "Claim Identity").click();
}
cy.contains(".ProfileUser", email);
});
};
export const createDemoProposal = (
cy: Cypress.Chainable,
title: string,
amount: string
) => {
cy.get('[href="/create"]').click();
// expects to be @ /create
cy.url().should("contain", "/create");
cy.log("CREATE DEMO PROPOSAL", title, amount);
// demo proposal
cy.get("button.CreateFlow-footer-example").click();
// change name
cy.get(".ant-steps > :nth-child(1)").click();
cy.get('.CreateFlow input[name="title"]', { timeout: 20000 })
.clear()
.type(title)
.blur();
cy.get('.CreateFlow input[name="amountToRaise"]')
.clear()
.type(amount)
.blur();
cy.wait(1000);
// remove extra trustees
cy.get(".ant-steps > :nth-child(5)").click();
cy.get(
":nth-child(11) > .ant-form-item-control-wrapper div > button"
).click();
cy.get(
":nth-child(10) > .ant-form-item-control-wrapper div > button"
).click();
cy.get(":nth-child(9) > .ant-form-item-control-wrapper div > button").click();
cy.get(":nth-child(8) > .ant-form-item-control-wrapper div > button").click();
cy.get(":nth-child(7) > .ant-form-item-control-wrapper div > button").click();
cy.wait(1000);
cy.get(".CreateFlow-footer-button")
.contains("Continue")
.click();
// final
cy.get("button")
.contains("Publish")
.click();
cy.get(".CreateFinal-loader-text").contains("Deploying contract...");
cy.get(".CreateFinal-message-text a", { timeout: 30000 })
.contains("Click here")
.click();
// created
cy.get(".Proposal-top-main-title").contains(title);
};
export const fundProposal = (
cy: Cypress.Chainable,
accountIndex: number,
amount: number
) => {
// expects to be @ /proposals/<proposal>
cy.url().should("contain", "/proposals/");
// reload page with accountIndex account
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(accountIndex) }));
// fund proposal
cy.get(".ant-input", { timeout: 20000 }).type(amount + "");
cy.contains(".ant-form > .ant-btn", "Fund this project", { timeout: 20000 })
.click()
.should("not.have.attr", "loading");
};

View File

@ -2,3 +2,7 @@ declare module "*.json" {
const value: any;
export default value;
}
declare module "eth-sig-util" {
export function signTypedData(k: any, d: any): string;
}

View File

@ -11,6 +11,7 @@
"@cypress/webpack-preprocessor": "^2.0.1",
"@types/web3": "^1.0.3",
"cypress": "^3.1.0",
"eth-sig-util": "^2.1.0",
"ts-loader": "^5.0.0",
"typescript": "^3.0.3",
"web3": "^1.0.0-beta.36",

View File

@ -1011,6 +1011,16 @@ binary-extensions@^1.0.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
bindings@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
bip66@^1.1.3:
version "1.1.5"
resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22"
dependencies:
safe-buffer "^5.0.1"
bl@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
@ -1040,7 +1050,7 @@ bn.js@4.11.6:
version "4.11.6"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215"
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.6, bn.js@^4.4.0:
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.3, bn.js@^4.11.6, bn.js@^4.4.0, bn.js@^4.8.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
@ -1100,7 +1110,7 @@ brorand@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
browserify-aes@^1.0.0, browserify-aes@^1.0.4:
browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6:
version "1.2.0"
resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
dependencies:
@ -1213,7 +1223,7 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.0.5:
buffer@^5.0.5, buffer@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6"
dependencies:
@ -1779,6 +1789,14 @@ domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
drbg.js@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b"
dependencies:
browserify-aes "^1.0.6"
create-hash "^1.1.2"
create-hmac "^1.1.4"
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@ -1820,7 +1838,7 @@ elliptic@6.3.3:
hash.js "^1.0.0"
inherits "^2.0.1"
elliptic@^6.0.0, elliptic@^6.4.0:
elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0:
version "6.4.1"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
dependencies:
@ -1920,6 +1938,46 @@ eth-lib@0.2.7:
elliptic "^6.4.0"
xhr-request-promise "^0.1.2"
eth-sig-util@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-2.1.0.tgz#33e60e5486897a2ddeb4bf5a0993b2c6d5cc9e19"
dependencies:
buffer "^5.2.1"
elliptic "^6.4.0"
ethereumjs-abi "0.6.5"
ethereumjs-util "^5.1.1"
tweetnacl "^1.0.0"
tweetnacl-util "^0.15.0"
ethereumjs-abi@0.6.5:
version "0.6.5"
resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241"
dependencies:
bn.js "^4.10.0"
ethereumjs-util "^4.3.0"
ethereumjs-util@^4.3.0:
version "4.5.0"
resolved "http://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz#3e9428b317eebda3d7260d854fddda954b1f1bc6"
dependencies:
bn.js "^4.8.0"
create-hash "^1.1.2"
keccakjs "^0.2.0"
rlp "^2.0.0"
secp256k1 "^3.0.1"
ethereumjs-util@^5.1.1:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz#3e0c0d1741471acf1036052d048623dee54ad642"
dependencies:
bn.js "^4.11.0"
create-hash "^1.1.2"
ethjs-util "^0.1.3"
keccak "^1.0.2"
rlp "^2.0.0"
safe-buffer "^5.1.1"
secp256k1 "^3.0.1"
ethers@4.0.0-beta.1:
version "4.0.0-beta.1"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.0-beta.1.tgz#0648268b83e0e91a961b1af971c662cdf8cbab6d"
@ -1942,6 +2000,13 @@ ethjs-unit@0.1.6:
bn.js "4.11.6"
number-to-bn "1.7.0"
ethjs-util@^0.1.3:
version "0.1.6"
resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536"
dependencies:
is-hex-prefixed "1.0.0"
strip-hex-prefix "1.0.0"
eventemitter3@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.1.1.tgz#47786bdaa087caf7b1b75e73abc5c7d540158cd0"
@ -2872,7 +2937,16 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
keccakjs@^0.2.1:
keccak@^1.0.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/keccak/-/keccak-1.4.0.tgz#572f8a6dbee8e7b3aa421550f9e6408ca2186f80"
dependencies:
bindings "^1.2.1"
inherits "^2.0.3"
nan "^2.2.1"
safe-buffer "^5.1.0"
keccakjs@^0.2.0, keccakjs@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/keccakjs/-/keccakjs-0.2.1.tgz#1d633af907ef305bbf9f2fa616d56c44561dfa4d"
dependencies:
@ -3238,6 +3312,10 @@ nan@^2.0.8, nan@^2.3.3, nan@^2.9.2:
version "2.11.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099"
nan@^2.2.1:
version "2.11.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766"
nano-json-stream-parser@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f"
@ -3926,6 +4004,12 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
rlp@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.1.0.tgz#e4f9886d5a982174f314543831e36e1a658460f9"
dependencies:
safe-buffer "^5.1.1"
run-queue@^1.0.0, run-queue@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
@ -3990,6 +4074,19 @@ scryptsy@^1.2.1:
dependencies:
pbkdf2 "^3.0.3"
secp256k1@^3.0.1:
version "3.5.2"
resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.5.2.tgz#f95f952057310722184fe9c914e6b71281f2f2ae"
dependencies:
bindings "^1.2.1"
bip66 "^1.1.3"
bn.js "^4.11.3"
create-hash "^1.1.2"
drbg.js "^1.0.1"
elliptic "^6.2.3"
nan "^2.2.1"
safe-buffer "^5.1.0"
seek-bzip@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
@ -4526,10 +4623,18 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tweetnacl-util@^0.15.0:
version "0.15.0"
resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.0.tgz#4576c1cee5e2d63d207fee52f1ba02819480bc75"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
tweetnacl@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.0.tgz#713d8b818da42068740bf68386d0479e66fc8a7b"
type-is@~1.6.15, type-is@~1.6.16:
version "1.6.16"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"

View File

@ -16,7 +16,13 @@ const getContractInstance = async (
deployedAddress = deployedAddress || contractDefinition.networks[networkId].address;
// create the instance
return new web3.eth.Contract(contractDefinition.abi, deployedAddress);
const contract = new web3.eth.Contract(contractDefinition.abi, deployedAddress);
// use gas from e2e injected window.web3.provider
if ((web3.currentProvider as any)._e2eContractGas) {
contract.options.gas = (web3.currentProvider as any)._e2eContractGas;
}
return contract;
};
export default getContractInstance;

View File

@ -508,7 +508,7 @@ export function signData(data: object, dataTypes: object, primaryType: string) {
primaryType,
};
(web3.currentProvider as any).sendAsync(
(web3.currentProvider as any).send(
{
method: 'eth_signTypedData_v3',
params: [accounts[0], JSON.stringify(rawTypedData)],

View File

@ -13,7 +13,7 @@
"heroku-postbuild": "yarn build",
"tsc": "tsc",
"link-contracts": "cd client/lib && ln -s ../../build/contracts contracts",
"ganache": "ganache-cli -b 5",
"ganache": "ganache-cli -b 5 -s testGrantIo -e 1000",
"truffle": "truffle exec ./bin/init-truffle.js && cd client/lib/contracts && truffle console",
"storybook": "start-storybook -p 9001 -c .storybook"
},