Cypress e2e basics (#186)

* cypress e2e with a couple tests

* add createCrowdFund helper function for future use
This commit is contained in:
AMStrix 2018-11-06 12:52:52 -06:00 committed by William O'Beirne
parent 0670eaa054
commit 4891a83450
13 changed files with 5530 additions and 0 deletions

12
e2e/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.next
node_modules
.idea
build
out
src/build
dist
*.log
.env
*.pid
client/lib/contracts
.vscode

24
e2e/README.md Normal file
View File

@ -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

1
e2e/cypress.json Normal file
View File

@ -0,0 +1 @@
{}

72
e2e/cypress/helpers.ts Normal file
View File

@ -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;
};

View File

@ -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");
});
});

View File

@ -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);
});
});

View File

@ -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));
};

View File

@ -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) => { ... })

View File

@ -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')

4
e2e/cypress/typings.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module "*.json" {
const value: any;
export default value;
}

19
e2e/package.json Normal file
View File

@ -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"
}
}

13
e2e/tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"strict": true,
"sourceMap": true,
"module": "commonjs",
"target": "es5",
"lib": ["dom", "es6"],
"jsx": "react",
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true
},
"compileOnSave": false
}

5081
e2e/yarn.lock Normal file

File diff suppressed because it is too large Load Diff