Cypress e2e basics (#186)
* cypress e2e with a couple tests * add createCrowdFund helper function for future use
This commit is contained in:
parent
0670eaa054
commit
4891a83450
|
@ -0,0 +1,12 @@
|
||||||
|
.next
|
||||||
|
node_modules
|
||||||
|
.idea
|
||||||
|
build
|
||||||
|
out
|
||||||
|
src/build
|
||||||
|
dist
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
*.pid
|
||||||
|
client/lib/contracts
|
||||||
|
.vscode
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Grant.io end to end testing
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
### Writing tests
|
||||||
|
|
||||||
|
Tests can be found in `cypress/integration`. Cypress will hot-reload open tests as you develop. Documentation on cypress testing can be found [here](https://docs.cypress.io/guides/getting-started/writing-your-first-test.html#Add-a-test-file).
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
1. Make sure you have a working development environment running (backend, ganache & frontend).
|
||||||
|
1. Run the cypress client:
|
||||||
|
```bash
|
||||||
|
yarn cypress:open
|
||||||
|
```
|
||||||
|
1. The cypress UI will open, select a test to run it.
|
||||||
|
|
||||||
|
### CI
|
||||||
|
|
||||||
|
TODO
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import Web3 = require("web3");
|
||||||
|
import * as CrowdFundFactory from "../../contract/build/contracts/CrowdFundFactory.json";
|
||||||
|
|
||||||
|
export const loadWeb3 = (window: any) => {
|
||||||
|
window.web3 = new Web3(`ws://localhost:8545`);
|
||||||
|
return window.web3.eth.net.getId().then((id: string) => {
|
||||||
|
console.log("loadWeb3: connected to networkId: " + id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createCrowdFund = (web3: Web3) => {
|
||||||
|
const HOUR = 3600;
|
||||||
|
const DAY = HOUR * 24;
|
||||||
|
const ETHER = 10 ** 18;
|
||||||
|
const NOW = Math.round(new Date().getTime() / 1000);
|
||||||
|
|
||||||
|
return web3.eth.net.getId().then((id: number) => {
|
||||||
|
const factoryAddy = (CrowdFundFactory as any).networks[id].address;
|
||||||
|
const factory = new web3.eth.Contract(
|
||||||
|
(CrowdFundFactory as any).abi,
|
||||||
|
factoryAddy
|
||||||
|
);
|
||||||
|
|
||||||
|
return web3.eth.getAccounts().then((accounts: string[]) => {
|
||||||
|
const raiseGoal = 1 * ETHER;
|
||||||
|
const beneficiary = accounts[1];
|
||||||
|
const trustees = [accounts[1]];
|
||||||
|
const milestones = [raiseGoal + ""];
|
||||||
|
const deadline = NOW + DAY * 100;
|
||||||
|
const milestoneVotingPeriod = HOUR;
|
||||||
|
const immediateFirstMilestonePayout = false;
|
||||||
|
|
||||||
|
return factory.methods
|
||||||
|
.createCrowdFund(
|
||||||
|
raiseGoal + "",
|
||||||
|
beneficiary,
|
||||||
|
trustees,
|
||||||
|
milestones,
|
||||||
|
deadline,
|
||||||
|
milestoneVotingPeriod,
|
||||||
|
immediateFirstMilestonePayout
|
||||||
|
)
|
||||||
|
.send({
|
||||||
|
from: accounts[4],
|
||||||
|
// important
|
||||||
|
gas: 3695268
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(receipt: any) =>
|
||||||
|
receipt.events.ContractCreated.returnValues.newAddress
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const randomString = () => {
|
||||||
|
return Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substring(7);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const randomHex = function(len: number) {
|
||||||
|
const maxlen = 8;
|
||||||
|
const min = Math.pow(16, Math.min(len, maxlen) - 1);
|
||||||
|
const max = Math.pow(16, Math.min(len, maxlen)) - 1;
|
||||||
|
const n = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
let r = n.toString(16);
|
||||||
|
while (r.length < len) {
|
||||||
|
r = r + randomHex(len - maxlen);
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
};
|
|
@ -0,0 +1,47 @@
|
||||||
|
/// <reference types="cypress"/>
|
||||||
|
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.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
|
||||||
|
.click({ force: true });
|
||||||
|
cy.title().should("include", "Grant.io - Create a Proposal");
|
||||||
|
|
||||||
|
// browse back home
|
||||||
|
cy.get('.Header a[href="/"]').click();
|
||||||
|
|
||||||
|
// test hero explore link
|
||||||
|
cy.get('.Home-hero-buttons a[href="/proposals"]')
|
||||||
|
// {force: true} here overcomes a strange issue where the button moves up under the header
|
||||||
|
// this is likely a cypress-related problem
|
||||||
|
.click({ force: true });
|
||||||
|
cy.title().should("include", "Grant.io - Browse proposals");
|
||||||
|
|
||||||
|
// browse back home
|
||||||
|
cy.get('.Header a[href="/"]').click();
|
||||||
|
|
||||||
|
// browse to create via header link
|
||||||
|
cy.get('.Header a[href="/create"]').click();
|
||||||
|
cy.title().should("include", "Grant.io - Create a Proposal");
|
||||||
|
|
||||||
|
// browse to explore via header link
|
||||||
|
cy.get('.Header a[href="/proposals"]').click();
|
||||||
|
cy.title().should("include", "Grant.io - Browse proposals");
|
||||||
|
|
||||||
|
// browse footer links
|
||||||
|
cy.get('.Footer a[href="/about"]').click();
|
||||||
|
cy.title().should("include", "Grant.io - About");
|
||||||
|
cy.get('.Footer a[href="/contact"]').click();
|
||||||
|
cy.title().should("include", "Grant.io - Contact");
|
||||||
|
cy.get('.Footer a[href="/tos"]').click();
|
||||||
|
cy.title().should("include", "Grant.io - Terms of Service");
|
||||||
|
cy.get('.Footer a[href="/privacy"]').click();
|
||||||
|
cy.title().should("include", "Grant.io - Privacy Policy");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,192 @@
|
||||||
|
/// <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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
const wp = require("@cypress/webpack-preprocessor");
|
||||||
|
module.exports = on => {
|
||||||
|
const options = {
|
||||||
|
webpackOptions: {
|
||||||
|
resolve: {
|
||||||
|
extensions: [".ts", ".tsx", ".js", ".json"]
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
loader: "ts-loader",
|
||||||
|
options: { transpileOnly: true }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
on("file:preprocessor", wp(options));
|
||||||
|
};
|
|
@ -0,0 +1,25 @@
|
||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
|
@ -0,0 +1,20 @@
|
||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
|
@ -0,0 +1,4 @@
|
||||||
|
declare module "*.json" {
|
||||||
|
const value: any;
|
||||||
|
export default value;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "e2e",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"cypress:open": "cypress open",
|
||||||
|
"cypress:run": "cypress run"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@cypress/webpack-preprocessor": "^2.0.1",
|
||||||
|
"@types/web3": "^1.0.3",
|
||||||
|
"cypress": "^3.1.0",
|
||||||
|
"ts-loader": "^5.0.0",
|
||||||
|
"typescript": "^3.0.3",
|
||||||
|
"web3": "^1.0.0-beta.36",
|
||||||
|
"webpack": "^4.17.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "es6"],
|
||||||
|
"jsx": "react",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"compileOnSave": false
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue