Merge pull request #348 from Budibase/feature/add-e2e-tests
Adds integration tests using Cypress **NOTE** When adding to CI, we need to add a `yarn build` step. Think that the client library is not being built.
This commit is contained in:
commit
a21fe80da3
|
@ -166,6 +166,18 @@ rm -rf ~/.budibase
|
||||||
```
|
```
|
||||||
Follow from **Step 3. Install and Build** in the setup guide above. You should have a fresh Budibase installation.
|
Follow from **Step 3. Install and Build** in the setup guide above. You should have a fresh Budibase installation.
|
||||||
|
|
||||||
|
### Running tests
|
||||||
|
|
||||||
|
#### End-to-end Tests
|
||||||
|
|
||||||
|
Budibase uses Cypress to run a number of E2E tests. To run the tests execute the following command in the root folder:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you are in the builder you can run `yarn cy:test`.
|
||||||
|
|
||||||
|
|
||||||
### Other Useful Information
|
### Other Useful Information
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"@rollup/plugin-json": "^4.0.2",
|
"@rollup/plugin-json": "^4.0.2",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-plugin-cypress": "^2.11.1",
|
||||||
"eslint-plugin-prettier": "^3.1.2",
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
"eslint-plugin-svelte3": "^2.7.3",
|
"eslint-plugin-svelte3": "^2.7.3",
|
||||||
"lerna": "3.14.1",
|
"lerna": "3.14.1",
|
||||||
|
@ -24,7 +25,8 @@
|
||||||
"test": "lerna run test",
|
"test": "lerna run test",
|
||||||
"lint": "eslint packages",
|
"lint": "eslint packages",
|
||||||
"lint:fix": "eslint --fix packages",
|
"lint:fix": "eslint --fix packages",
|
||||||
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\""
|
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\"",
|
||||||
|
"test:e2e": "lerna run cy:test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material/icon-button": "4.0.0",
|
"@material/icon-button": "4.0.0",
|
||||||
|
|
|
@ -5,4 +5,5 @@ package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
release/
|
release/
|
||||||
dist/
|
dist/
|
||||||
|
cypress/screenshots
|
||||||
routify
|
routify
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"baseUrl": "http://localhost:4001/_builder/",
|
||||||
|
"video": false
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io",
|
||||||
|
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"id": 8739,
|
||||||
|
"name": "Jane",
|
||||||
|
"email": "jane@example.com"
|
||||||
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Leanne Graham",
|
||||||
|
"username": "Bret",
|
||||||
|
"email": "Sincere@april.biz",
|
||||||
|
"address": {
|
||||||
|
"street": "Kulas Light",
|
||||||
|
"suite": "Apt. 556",
|
||||||
|
"city": "Gwenborough",
|
||||||
|
"zipcode": "92998-3874",
|
||||||
|
"geo": {
|
||||||
|
"lat": "-37.3159",
|
||||||
|
"lng": "81.1496"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "1-770-736-8031 x56442",
|
||||||
|
"website": "hildegard.org",
|
||||||
|
"company": {
|
||||||
|
"name": "Romaguera-Crona",
|
||||||
|
"catchPhrase": "Multi-layered client-server neural-net",
|
||||||
|
"bs": "harness real-time e-markets"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Ervin Howell",
|
||||||
|
"username": "Antonette",
|
||||||
|
"email": "Shanna@melissa.tv",
|
||||||
|
"address": {
|
||||||
|
"street": "Victor Plains",
|
||||||
|
"suite": "Suite 879",
|
||||||
|
"city": "Wisokyburgh",
|
||||||
|
"zipcode": "90566-7771",
|
||||||
|
"geo": {
|
||||||
|
"lat": "-43.9509",
|
||||||
|
"lng": "-34.4618"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "010-692-6593 x09125",
|
||||||
|
"website": "anastasia.net",
|
||||||
|
"company": {
|
||||||
|
"name": "Deckow-Crist",
|
||||||
|
"catchPhrase": "Proactive didactic contingency",
|
||||||
|
"bs": "synergize scalable supply-chains"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Clementine Bauch",
|
||||||
|
"username": "Samantha",
|
||||||
|
"email": "Nathan@yesenia.net",
|
||||||
|
"address": {
|
||||||
|
"street": "Douglas Extension",
|
||||||
|
"suite": "Suite 847",
|
||||||
|
"city": "McKenziehaven",
|
||||||
|
"zipcode": "59590-4157",
|
||||||
|
"geo": {
|
||||||
|
"lat": "-68.6102",
|
||||||
|
"lng": "-47.0653"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "1-463-123-4447",
|
||||||
|
"website": "ramiro.info",
|
||||||
|
"company": {
|
||||||
|
"name": "Romaguera-Jacobson",
|
||||||
|
"catchPhrase": "Face to face bifurcated interface",
|
||||||
|
"bs": "e-enable strategic applications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Patricia Lebsack",
|
||||||
|
"username": "Karianne",
|
||||||
|
"email": "Julianne.OConner@kory.org",
|
||||||
|
"address": {
|
||||||
|
"street": "Hoeger Mall",
|
||||||
|
"suite": "Apt. 692",
|
||||||
|
"city": "South Elvis",
|
||||||
|
"zipcode": "53919-4257",
|
||||||
|
"geo": {
|
||||||
|
"lat": "29.4572",
|
||||||
|
"lng": "-164.2990"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "493-170-9623 x156",
|
||||||
|
"website": "kale.biz",
|
||||||
|
"company": {
|
||||||
|
"name": "Robel-Corkery",
|
||||||
|
"catchPhrase": "Multi-tiered zero tolerance productivity",
|
||||||
|
"bs": "transition cutting-edge web services"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Chelsey Dietrich",
|
||||||
|
"username": "Kamren",
|
||||||
|
"email": "Lucio_Hettinger@annie.ca",
|
||||||
|
"address": {
|
||||||
|
"street": "Skiles Walks",
|
||||||
|
"suite": "Suite 351",
|
||||||
|
"city": "Roscoeview",
|
||||||
|
"zipcode": "33263",
|
||||||
|
"geo": {
|
||||||
|
"lat": "-31.8129",
|
||||||
|
"lng": "62.5342"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "(254)954-1289",
|
||||||
|
"website": "demarco.info",
|
||||||
|
"company": {
|
||||||
|
"name": "Keebler LLC",
|
||||||
|
"catchPhrase": "User-centric fault-tolerant solution",
|
||||||
|
"bs": "revolutionize end-to-end systems"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Mrs. Dennis Schulist",
|
||||||
|
"username": "Leopoldo_Corkery",
|
||||||
|
"email": "Karley_Dach@jasper.info",
|
||||||
|
"address": {
|
||||||
|
"street": "Norberto Crossing",
|
||||||
|
"suite": "Apt. 950",
|
||||||
|
"city": "South Christy",
|
||||||
|
"zipcode": "23505-1337",
|
||||||
|
"geo": {
|
||||||
|
"lat": "-71.4197",
|
||||||
|
"lng": "71.7478"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "1-477-935-8478 x6430",
|
||||||
|
"website": "ola.org",
|
||||||
|
"company": {
|
||||||
|
"name": "Considine-Lockman",
|
||||||
|
"catchPhrase": "Synchronised bottom-line interface",
|
||||||
|
"bs": "e-enable innovative applications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "Kurtis Weissnat",
|
||||||
|
"username": "Elwyn.Skiles",
|
||||||
|
"email": "Telly.Hoeger@billy.biz",
|
||||||
|
"address": {
|
||||||
|
"street": "Rex Trail",
|
||||||
|
"suite": "Suite 280",
|
||||||
|
"city": "Howemouth",
|
||||||
|
"zipcode": "58804-1099",
|
||||||
|
"geo": {
|
||||||
|
"lat": "24.8918",
|
||||||
|
"lng": "21.8984"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "210.067.6132",
|
||||||
|
"website": "elvis.io",
|
||||||
|
"company": {
|
||||||
|
"name": "Johns Group",
|
||||||
|
"catchPhrase": "Configurable multimedia task-force",
|
||||||
|
"bs": "generate enterprise e-tailers"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "Nicholas Runolfsdottir V",
|
||||||
|
"username": "Maxime_Nienow",
|
||||||
|
"email": "Sherwood@rosamond.me",
|
||||||
|
"address": {
|
||||||
|
"street": "Ellsworth Summit",
|
||||||
|
"suite": "Suite 729",
|
||||||
|
"city": "Aliyaview",
|
||||||
|
"zipcode": "45169",
|
||||||
|
"geo": {
|
||||||
|
"lat": "-14.3990",
|
||||||
|
"lng": "-120.7677"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "586.493.6943 x140",
|
||||||
|
"website": "jacynthe.com",
|
||||||
|
"company": {
|
||||||
|
"name": "Abernathy Group",
|
||||||
|
"catchPhrase": "Implemented secondary concept",
|
||||||
|
"bs": "e-enable extensible e-tailers"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "Glenna Reichert",
|
||||||
|
"username": "Delphine",
|
||||||
|
"email": "Chaim_McDermott@dana.io",
|
||||||
|
"address": {
|
||||||
|
"street": "Dayna Park",
|
||||||
|
"suite": "Suite 449",
|
||||||
|
"city": "Bartholomebury",
|
||||||
|
"zipcode": "76495-3109",
|
||||||
|
"geo": {
|
||||||
|
"lat": "24.6463",
|
||||||
|
"lng": "-168.8889"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "(775)976-6794 x41206",
|
||||||
|
"website": "conrad.com",
|
||||||
|
"company": {
|
||||||
|
"name": "Yost and Sons",
|
||||||
|
"catchPhrase": "Switchable contextually-based project",
|
||||||
|
"bs": "aggregate real-time technologies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "Clementina DuBuque",
|
||||||
|
"username": "Moriah.Stanton",
|
||||||
|
"email": "Rey.Padberg@karina.biz",
|
||||||
|
"address": {
|
||||||
|
"street": "Kattie Turnpike",
|
||||||
|
"suite": "Suite 198",
|
||||||
|
"city": "Lebsackbury",
|
||||||
|
"zipcode": "31428-2261",
|
||||||
|
"geo": {
|
||||||
|
"lat": "-38.2386",
|
||||||
|
"lng": "57.2232"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": "024-648-3804",
|
||||||
|
"website": "ambrose.net",
|
||||||
|
"company": {
|
||||||
|
"name": "Hoeger LLC",
|
||||||
|
"catchPhrase": "Centralized empowering task-force",
|
||||||
|
"bs": "target end-to-end models"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
context('Create an Application', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('localhost:4001/_builder')
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://on.cypress.io/interacting-with-elements
|
||||||
|
|
||||||
|
it('should create a new application', () => {
|
||||||
|
// https://on.cypress.io/type
|
||||||
|
cy.createApp('My Cool App', 'This is a description')
|
||||||
|
|
||||||
|
cy.visit('localhost:4001/_builder')
|
||||||
|
|
||||||
|
cy.contains('My Cool App').should('exist')
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,53 @@
|
||||||
|
context('Create Components', () => {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.visit('localhost:4001/_builder')
|
||||||
|
// https://on.cypress.io/type
|
||||||
|
cy.createApp('Model App', 'Model App Description')
|
||||||
|
cy.createModel('dog', 'name', 'age')
|
||||||
|
cy.addRecord('bob', '15')
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://on.cypress.io/interacting-with-elements
|
||||||
|
it('should add a container', () => {
|
||||||
|
cy.contains('frontend').click()
|
||||||
|
cy.get('.switcher > :nth-child(2)').click()
|
||||||
|
|
||||||
|
cy.contains('Container').click()
|
||||||
|
})
|
||||||
|
it('should add a headline', () => {
|
||||||
|
cy.addHeadlineComponent('An Amazing headline!')
|
||||||
|
|
||||||
|
getIframeBody().contains('An Amazing headline!')
|
||||||
|
})
|
||||||
|
it('change the font size of the headline', () => {
|
||||||
|
cy.contains('Typography').click()
|
||||||
|
cy.get('input[name="font-size"]')
|
||||||
|
.type('60px')
|
||||||
|
cy.contains('Design').click()
|
||||||
|
|
||||||
|
getIframeBody().contains('An Amazing headline!').should('have.css', 'font-size', '60px')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const getIframeDocument = () => {
|
||||||
|
return cy
|
||||||
|
.get('iframe')
|
||||||
|
// Cypress yields jQuery element, which has the real
|
||||||
|
// DOM element under property "0".
|
||||||
|
// From the real DOM iframe element we can get
|
||||||
|
// the "document" element, it is stored in "contentDocument" property
|
||||||
|
// Cypress "its" command can access deep properties using dot notation
|
||||||
|
// https://on.cypress.io/its
|
||||||
|
.its('0.contentDocument').should('exist')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIframeBody = () => {
|
||||||
|
// get the document
|
||||||
|
return getIframeDocument()
|
||||||
|
// automatically retries until body is loaded
|
||||||
|
.its('body').should('not.be.undefined')
|
||||||
|
// wraps "body" DOM element to allow
|
||||||
|
// chaining more Cypress commands, like ".find(...)"
|
||||||
|
.then(cy.wrap)
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
context('Create a Model', () => {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.visit('localhost:4001/_builder')
|
||||||
|
// https://on.cypress.io/type
|
||||||
|
cy.createApp('Model App', 'Model App Description')
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://on.cypress.io/interacting-with-elements
|
||||||
|
it('should create a new model', () => {
|
||||||
|
|
||||||
|
cy.createModel('dog', 'name', 'age')
|
||||||
|
|
||||||
|
// Check if model exists
|
||||||
|
cy.get('.title').should('have.text', 'dog')
|
||||||
|
})
|
||||||
|
it('should add a record', () => {
|
||||||
|
cy.addRecord('bob', '15')
|
||||||
|
|
||||||
|
cy.contains('bob').should('have.text', 'bob')
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,19 @@
|
||||||
|
context('Create a User', () => {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.visit('localhost:4001/_builder')
|
||||||
|
// https://on.cypress.io/type
|
||||||
|
cy.createApp('User App', 'This app is used to test user creation')
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://on.cypress.io/interacting-with-elements
|
||||||
|
it('should create a user', () => {
|
||||||
|
// Close Model modal that shows up after creating an app
|
||||||
|
cy.get('.close').click()
|
||||||
|
|
||||||
|
cy.createUser('bbuser', 'test', 'ADMIN')
|
||||||
|
|
||||||
|
// Check to make sure user was created!
|
||||||
|
cy.contains('bbuser').should('have.text', 'bbuser')
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,46 @@
|
||||||
|
context('Create a workflow', () => {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.visit('localhost:4001/_builder')
|
||||||
|
|
||||||
|
cy.createApp('Workflow Test App', 'This app is used to test that workflows do in fact work!')
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://on.cypress.io/interacting-with-elements
|
||||||
|
it('should create a workflow', () => {
|
||||||
|
cy.createModel('dog', 'name', 'age')
|
||||||
|
cy.createUser('bbuser', 'test', 'ADMIN')
|
||||||
|
|
||||||
|
|
||||||
|
cy.contains('workflow').click()
|
||||||
|
cy.get('.new-workflow-button').click()
|
||||||
|
cy.get('input').type('Add Record')
|
||||||
|
cy.contains('Save').click()
|
||||||
|
|
||||||
|
// Add trigger
|
||||||
|
cy.get('[data-cy=add-workflow-component]').click()
|
||||||
|
cy.get('[data-cy=RECORD_SAVED]').click()
|
||||||
|
cy.get('.budibase__input').select('dog')
|
||||||
|
|
||||||
|
// Create action
|
||||||
|
cy.get('[data-cy=SAVE_RECORD]').click()
|
||||||
|
cy.get(':nth-child(2) > .budibase__input').type('goodboy')
|
||||||
|
cy.get(':nth-child(3) > .budibase__input').type('11')
|
||||||
|
|
||||||
|
// Save
|
||||||
|
cy.get('[data-cy=save-workflow-setup]').click()
|
||||||
|
cy.get('.workflow-button').click()
|
||||||
|
|
||||||
|
// Activate Workflow
|
||||||
|
cy.get('[data-cy=activate-workflow]').click()
|
||||||
|
|
||||||
|
})
|
||||||
|
it('should add record when a new record is added', () => {
|
||||||
|
cy.contains('backend').click()
|
||||||
|
|
||||||
|
cy.addRecord('bob', '15')
|
||||||
|
|
||||||
|
cy.contains('goodboy').should('have.text', 'goodboy')
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,22 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
// ***********************************************************
|
||||||
|
// This example plugins/index.js can be used to load plugins
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off loading
|
||||||
|
// the plugins file with the 'pluginsFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/plugins-guide
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// This function is called when a project is opened or re-opened (e.g. due to
|
||||||
|
// the project's config changing)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Cypress.PluginConfig}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
module.exports = (on, config) => {
|
||||||
|
// `on` is used to hook into various events Cypress emits
|
||||||
|
// `config` is the resolved Cypress config
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// What this script does:
|
||||||
|
// 1. Removes the old test folder if it exists (.budibase-cypress)
|
||||||
|
// 2. Initialises using `.budibase-cypress`
|
||||||
|
// 3. Runs the server using said folder
|
||||||
|
|
||||||
|
const rimraf = require("rimraf")
|
||||||
|
const { join } = require("path")
|
||||||
|
const homedir = join(require("os").homedir(), ".budibase-cypress")
|
||||||
|
const init = require("../../cli/src/commands/init/initHandler")
|
||||||
|
const run = require("../../cli/src/commands/run/runHandler")
|
||||||
|
|
||||||
|
rimraf.sync(homedir)
|
||||||
|
|
||||||
|
init({ dir: homedir, clientId: "cypress-test" }).then(() => {
|
||||||
|
delete require.cache[require.resolve("../../server/src/environment")]
|
||||||
|
run({ dir: homedir })
|
||||||
|
})
|
|
@ -0,0 +1,105 @@
|
||||||
|
// ***********************************************
|
||||||
|
// 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 will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||||
|
|
||||||
|
Cypress.Commands.add("createApp", (name, description) => {
|
||||||
|
cy.get(".banner-button")
|
||||||
|
.click()
|
||||||
|
.get('input[name="name"]')
|
||||||
|
.type(name)
|
||||||
|
.should("have.value", name)
|
||||||
|
|
||||||
|
cy.get('textarea[name="description"]')
|
||||||
|
.type(description)
|
||||||
|
.should("have.value", description)
|
||||||
|
|
||||||
|
cy.contains("Save").click()
|
||||||
|
})
|
||||||
|
Cypress.Commands.add("createModel", (modelName, firstField, secondField) => {
|
||||||
|
// Enter model name
|
||||||
|
cy.get("[data-cy=Name]")
|
||||||
|
.click()
|
||||||
|
.type(modelName)
|
||||||
|
|
||||||
|
// Add 'name' field
|
||||||
|
cy.get("[data-cy=add-new-model-field]").click()
|
||||||
|
cy.get("[data-cy=Name]")
|
||||||
|
.click()
|
||||||
|
.type(firstField)
|
||||||
|
cy.contains("Save").click()
|
||||||
|
|
||||||
|
// Add 'age' field
|
||||||
|
cy.get("[data-cy=add-new-model-field]").click()
|
||||||
|
|
||||||
|
cy.get("[data-cy=Name]")
|
||||||
|
.click()
|
||||||
|
.type(secondField)
|
||||||
|
cy.get("select").select("number")
|
||||||
|
cy.contains("Save").click()
|
||||||
|
cy.contains(secondField).should("exist")
|
||||||
|
|
||||||
|
// Save model
|
||||||
|
cy.contains("Save").click()
|
||||||
|
})
|
||||||
|
Cypress.Commands.add("addRecord", (firstField, secondField) => {
|
||||||
|
cy.contains("Create new record").click()
|
||||||
|
|
||||||
|
cy.get("[data-cy=name-input]")
|
||||||
|
.click()
|
||||||
|
.type(firstField)
|
||||||
|
cy.get("[data-cy=age-input]")
|
||||||
|
.click()
|
||||||
|
.type(secondField)
|
||||||
|
|
||||||
|
// Save
|
||||||
|
cy.contains("Save").click()
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("createUser", (username, password, level) => {
|
||||||
|
// Create User
|
||||||
|
cy.get(".nav-group-header > .ri-add-line").click()
|
||||||
|
|
||||||
|
cy.get("[data-cy=username]").type(username)
|
||||||
|
cy.get("[data-cy=password]").type(password)
|
||||||
|
cy.get("[data-cy=accessLevel]").select(level)
|
||||||
|
|
||||||
|
// Save
|
||||||
|
cy.contains("Save").click()
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("addHeadlineComponent", text => {
|
||||||
|
cy.get(".switcher > :nth-child(2)").click()
|
||||||
|
|
||||||
|
cy.get("[data-cy=Text]").click()
|
||||||
|
cy.get("[data-cy=Headline]").click()
|
||||||
|
cy.get(".tabs > :nth-child(2)").click()
|
||||||
|
cy.get('input[type="text"]').type(text)
|
||||||
|
cy.contains("Design").click()
|
||||||
|
})
|
||||||
|
Cypress.Commands.add("addButtonComponent", () => {
|
||||||
|
cy.get(".switcher > :nth-child(2)").click()
|
||||||
|
|
||||||
|
cy.get("[data-cy=Button]").click()
|
||||||
|
})
|
|
@ -0,0 +1,3 @@
|
||||||
|
Cypress.Cookies.defaults({
|
||||||
|
whitelist: "builder:token",
|
||||||
|
})
|
|
@ -0,0 +1,21 @@
|
||||||
|
// ***********************************************************
|
||||||
|
// 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 "./cookies"
|
||||||
|
import "./commands"
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
|
@ -9,7 +9,11 @@
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watchAll",
|
"test:watch": "jest --watchAll",
|
||||||
"dev:builder": "routify --routify-dir routify -c rollup",
|
"dev:builder": "routify --routify-dir routify -c rollup",
|
||||||
"rollup": "rollup -c -w"
|
"rollup": "rollup -c -w",
|
||||||
|
"cy:setup": "node ./cypress/setup.js",
|
||||||
|
"cy:run": "cypress run",
|
||||||
|
"cy:open": "cypress open",
|
||||||
|
"cy:test": "start-server-and-test cy:setup http://localhost:4001/_builder cy:run"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"globals": {
|
"globals": {
|
||||||
|
@ -35,6 +39,14 @@
|
||||||
},
|
},
|
||||||
"transformIgnorePatterns": [
|
"transformIgnorePatterns": [
|
||||||
"/node_modules/(?!svelte).+\\.js$"
|
"/node_modules/(?!svelte).+\\.js$"
|
||||||
|
],
|
||||||
|
"modulePathIgnorePatterns": [
|
||||||
|
"<rootDir>/cypress/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"plugin:cypress/recommended"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -67,10 +79,12 @@
|
||||||
"@sveltech/routify": "1.7.11",
|
"@sveltech/routify": "1.7.11",
|
||||||
"babel-jest": "^24.8.0",
|
"babel-jest": "^24.8.0",
|
||||||
"browser-sync": "^2.26.7",
|
"browser-sync": "^2.26.7",
|
||||||
|
"cypress": "^4.8.0",
|
||||||
"http-proxy-middleware": "^0.19.1",
|
"http-proxy-middleware": "^0.19.1",
|
||||||
"jest": "^24.8.0",
|
"jest": "^24.8.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^1.12.0",
|
"rollup": "^1.12.0",
|
||||||
"rollup-plugin-alias": "^1.5.2",
|
"rollup-plugin-alias": "^1.5.2",
|
||||||
"rollup-plugin-browsersync": "^1.0.0",
|
"rollup-plugin-browsersync": "^1.0.0",
|
||||||
|
@ -83,6 +97,7 @@
|
||||||
"rollup-plugin-svelte": "^5.0.3",
|
"rollup-plugin-svelte": "^5.0.3",
|
||||||
"rollup-plugin-terser": "^4.0.4",
|
"rollup-plugin-terser": "^4.0.4",
|
||||||
"rollup-plugin-url": "^2.2.2",
|
"rollup-plugin-url": "^2.2.2",
|
||||||
|
"start-server-and-test": "^1.11.0",
|
||||||
"svelte": "3.23.x"
|
"svelte": "3.23.x"
|
||||||
},
|
},
|
||||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { buildStyle } from "../../helpers.js"
|
import { buildStyle } from "../../helpers.js"
|
||||||
export let value = ""
|
export let value = ""
|
||||||
|
export let name = ""
|
||||||
export let textAlign = "left"
|
export let textAlign = "left"
|
||||||
export let width = "160px"
|
export let width = "160px"
|
||||||
export let placeholder = ""
|
export let placeholder = ""
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
{name}
|
||||||
class:centerPlaceholder
|
class:centerPlaceholder
|
||||||
type="text"
|
type="text"
|
||||||
value={displayValue}
|
value={displayValue}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
<label class="uk-form-label">{label}</label>
|
<label class="uk-form-label">{label}</label>
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
<input
|
<input
|
||||||
|
data-cy={label}
|
||||||
class="budibase__input"
|
class="budibase__input"
|
||||||
class:uk-form-danger={hasError}
|
class:uk-form-danger={hasError}
|
||||||
on:change
|
on:change
|
||||||
|
|
|
@ -53,7 +53,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="table-controls">
|
<div class="table-controls">
|
||||||
<span class="label">Fields</span>
|
<span class="label">Fields</span>
|
||||||
<div class="hoverable new-field" on:click={() => (showFieldView = true)}>
|
<div
|
||||||
|
data-cy="add-new-model-field"
|
||||||
|
class="hoverable new-field"
|
||||||
|
on:click={() => (showFieldView = true)}>
|
||||||
Add new field
|
Add new field
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,15 +28,26 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<label class="uk-form-label" for="form-stacked-text">Username</label>
|
<label class="uk-form-label" for="form-stacked-text">Username</label>
|
||||||
<input class="uk-input" type="text" bind:value={username} />
|
<input
|
||||||
|
data-cy="username"
|
||||||
|
class="uk-input"
|
||||||
|
type="text"
|
||||||
|
bind:value={username} />
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<label class="uk-form-label" for="form-stacked-text">Password</label>
|
<label class="uk-form-label" for="form-stacked-text">Password</label>
|
||||||
<input class="uk-input" type="password" bind:value={password} />
|
<input
|
||||||
|
data-cy="password"
|
||||||
|
class="uk-input"
|
||||||
|
type="password"
|
||||||
|
bind:value={password} />
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<label class="uk-form-label" for="form-stacked-text">Access Level</label>
|
<label class="uk-form-label" for="form-stacked-text">Access Level</label>
|
||||||
<select class="uk-select" bind:value={accessLevelId}>
|
<select
|
||||||
|
data-cy="accessLevel"
|
||||||
|
class="uk-select"
|
||||||
|
bind:value={accessLevelId}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
<option value="POWER_USER">Power User</option>
|
<option value="POWER_USER">Power User</option>
|
||||||
<option value="ADMIN">Admin</option>
|
<option value="ADMIN">Admin</option>
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
{#if type === 'select'}
|
{#if type === 'select'}
|
||||||
<select
|
<select
|
||||||
|
data-cy="{label}-select"
|
||||||
class={determineClassName(type)}
|
class={determineClassName(type)}
|
||||||
bind:value
|
bind:value
|
||||||
class:uk-form-danger={errors.length > 0}>
|
class:uk-form-danger={errors.length > 0}>
|
||||||
|
@ -41,6 +42,7 @@
|
||||||
</select>
|
</select>
|
||||||
{:else}
|
{:else}
|
||||||
<input
|
<input
|
||||||
|
data-cy="{label}-input"
|
||||||
class={determineClassName(type)}
|
class={determineClassName(type)}
|
||||||
class:uk-form-danger={errors.length > 0}
|
class:uk-form-danger={errors.length > 0}
|
||||||
{checked}
|
{checked}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
{#each categories as category}
|
{#each categories as category}
|
||||||
<li
|
<li
|
||||||
|
data-cy={category.name}
|
||||||
on:click={() => onClick(category)}
|
on:click={() => onClick(category)}
|
||||||
class:active={selectedCategory === category}>
|
class:active={selectedCategory === category}>
|
||||||
{category.name}
|
{category.name}
|
||||||
|
|
|
@ -3,7 +3,11 @@
|
||||||
export let item
|
export let item
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="item-item" in:fly={{ y: 100, duration: 1000 }} on:click>
|
<div
|
||||||
|
data-cy={item.name}
|
||||||
|
class="item-item"
|
||||||
|
in:fly={{ y: 100, duration: 1000 }}
|
||||||
|
on:click>
|
||||||
<div class="item-icon">
|
<div class="item-icon">
|
||||||
<i class={item.icon} />
|
<i class={item.icon} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
{...handlevalueKey(value)}
|
{...handlevalueKey(value)}
|
||||||
on:change={val => handleChange(key, val)}
|
on:change={val => handleChange(key, val)}
|
||||||
onChange={val => handleChange(key, val)}
|
onChange={val => handleChange(key, val)}
|
||||||
{...props} />
|
{...props}
|
||||||
|
name={key} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,10 @@
|
||||||
{#if workflowBlock}
|
{#if workflowBlock}
|
||||||
<WorkflowBlockSetup {workflowBlock} />
|
<WorkflowBlockSetup {workflowBlock} />
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button class="workflow-button hoverable" on:click={saveWorkflow}>
|
<button
|
||||||
|
data-cy="save-workflow-setup"
|
||||||
|
class="workflow-button hoverable"
|
||||||
|
on:click={saveWorkflow}>
|
||||||
Save Workflow
|
Save Workflow
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
class:highlighted={!workflowLive}
|
class:highlighted={!workflowLive}
|
||||||
class:hoverable={!workflowLive}
|
class:hoverable={!workflowLive}
|
||||||
class="play-button hoverable"
|
class="play-button hoverable"
|
||||||
|
data-cy="activate-workflow"
|
||||||
on:click={() => setWorkflowLive(true)}>
|
on:click={() => setWorkflowLive(true)}>
|
||||||
<i class="ri-play-fill" />
|
<i class="ri-play-fill" />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -15,7 +15,10 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="workflow-block hoverable" on:click={addBlockToWorkflow}>
|
<div
|
||||||
|
class="workflow-block hoverable"
|
||||||
|
on:click={addBlockToWorkflow}
|
||||||
|
data-cy={actionId}>
|
||||||
<div>
|
<div>
|
||||||
<i class={blockDefinition.icon} />
|
<i class={blockDefinition.icon} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<span
|
<span
|
||||||
|
data-cy="workflow-list"
|
||||||
class="hoverable workflow-header"
|
class="hoverable workflow-header"
|
||||||
class:selected={selectedTab === 'WORKFLOWS'}
|
class:selected={selectedTab === 'WORKFLOWS'}
|
||||||
on:click={() => (selectedTab = 'WORKFLOWS')}>
|
on:click={() => (selectedTab = 'WORKFLOWS')}>
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
</span>
|
</span>
|
||||||
{#if $workflowStore.currentWorkflow}
|
{#if $workflowStore.currentWorkflow}
|
||||||
<span
|
<span
|
||||||
|
data-cy="add-workflow-component"
|
||||||
class="hoverable"
|
class="hoverable"
|
||||||
class:selected={selectedTab === 'ADD'}
|
class:selected={selectedTab === 'ADD'}
|
||||||
on:click={() => (selectedTab = 'ADD')}>
|
on:click={() => (selectedTab = 'ADD')}>
|
||||||
|
|
|
@ -7,7 +7,7 @@ const Sqrl = require("squirrelly")
|
||||||
const uuid = require("uuid")
|
const uuid = require("uuid")
|
||||||
|
|
||||||
module.exports = opts => {
|
module.exports = opts => {
|
||||||
run(opts)
|
return run(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
const run = async opts => {
|
const run = async opts => {
|
||||||
|
|
|
@ -5,7 +5,6 @@ module.exports = ({ dir }) => {
|
||||||
dir = xPlatHomeDir(dir)
|
dir = xPlatHomeDir(dir)
|
||||||
process.env.BUDIBASE_DIR = resolve(dir)
|
process.env.BUDIBASE_DIR = resolve(dir)
|
||||||
require("dotenv").config({ path: resolve(dir, ".env") })
|
require("dotenv").config({ path: resolve(dir, ".env") })
|
||||||
console.log("dotenv loaded")
|
|
||||||
|
|
||||||
// dont make this a variable or top level require
|
// dont make this a variable or top level require
|
||||||
// ti will cause environment module to be loaded prematurely
|
// ti will cause environment module to be loaded prematurely
|
||||||
|
|
|
@ -5,9 +5,6 @@ COUCH_DB_URL={{couchDbUrl}}
|
||||||
# identifies a client database - i.e. group of apps
|
# identifies a client database - i.e. group of apps
|
||||||
CLIENT_ID={{clientId}}
|
CLIENT_ID={{clientId}}
|
||||||
|
|
||||||
# Full access API key for server
|
|
||||||
ADMIN_SECRET={{adminSecret}}
|
|
||||||
|
|
||||||
# used to create cookie hashes
|
# used to create cookie hashes
|
||||||
JWT_SECRET={{cookieKey1}}
|
JWT_SECRET={{cookieKey1}}
|
||||||
|
|
||||||
|
|
|
@ -4,5 +4,4 @@ process.env.NODE_ENV = "jest"
|
||||||
process.env.JWT_SECRET = "test-jwtsecret"
|
process.env.JWT_SECRET = "test-jwtsecret"
|
||||||
process.env.CLIENT_ID = "test-client-id"
|
process.env.CLIENT_ID = "test-client-id"
|
||||||
process.env.BUDIBASE_DIR = tmpdir("budibase-unittests")
|
process.env.BUDIBASE_DIR = tmpdir("budibase-unittests")
|
||||||
process.env.ADMIN_SECRET = "test-admin-secret"
|
|
||||||
process.env.LOG_LEVEL = "silent"
|
process.env.LOG_LEVEL = "silent"
|
||||||
|
|
|
@ -3,7 +3,6 @@ module.exports = {
|
||||||
NODE_ENV: process.env.NODE_ENV,
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
JWT_SECRET: process.env.JWT_SECRET,
|
JWT_SECRET: process.env.JWT_SECRET,
|
||||||
BUDIBASE_DIR: process.env.BUDIBASE_DIR,
|
BUDIBASE_DIR: process.env.BUDIBASE_DIR,
|
||||||
ADMIN_SECRET: process.env.ADMIN_SECRET,
|
|
||||||
PORT: process.env.PORT,
|
PORT: process.env.PORT,
|
||||||
COUCH_DB_URL: process.env.COUCH_DB_URL,
|
COUCH_DB_URL: process.env.COUCH_DB_URL,
|
||||||
SALT_ROUNDS: process.env.SALT_ROUNDS,
|
SALT_ROUNDS: process.env.SALT_ROUNDS,
|
||||||
|
|
|
@ -1980,6 +1980,13 @@ escape-string-regexp@^1.0.5:
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||||
|
|
||||||
|
eslint-plugin-cypress@^2.11.1:
|
||||||
|
version "2.11.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.1.tgz#a945e2774b88211e2c706a059d431e262b5c2862"
|
||||||
|
integrity sha512-MxMYoReSO5+IZMGgpBZHHSx64zYPSPTpXDwsgW7ChlJTF/sA+obqRbHplxD6sBStE+g4Mi0LCLkG4t9liu//mQ==
|
||||||
|
dependencies:
|
||||||
|
globals "^11.12.0"
|
||||||
|
|
||||||
eslint-plugin-prettier@^3.1.2:
|
eslint-plugin-prettier@^3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba"
|
||||||
|
@ -2526,7 +2533,7 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
|
||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
path-is-absolute "^1.0.0"
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
globals@^11.1.0:
|
globals@^11.1.0, globals@^11.12.0:
|
||||||
version "11.12.0"
|
version "11.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||||
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
|
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
|
||||||
|
|
Loading…
Reference in New Issue