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