Add UI tests using Cypress (#6562)
* test(UI): Add UI tests using cypress * test: Add test configuration for travis * fix: Lock redis version * fix: Refactor fill_field command * fix: Rename setup_wizard test to run first * test: Add setup for dashboard service * test: Add build matrix for ui test * test: Add name to each build matrix * test: Only include ui test for an extra build stage * fix: Exclude UI test with python 3.6 * test: Test order * test: Enable developer_mode * test(login): Check session user and not hash * test: Refactor assert * test: Refactor setup wizard test * test: Remove setup wizard test * test: Add blank seed database * test(form): Scroll to top before save * test: Fix form test * test: timeout * test: more wait * test: Remove specific selector * test: Remove wait, delay typing * test: Blur input after typing * test: Wait for form to get dirty * test: Add credentials for frappe org * test: Remove node install step * style: Fix linting issues * fix: List view filters - ToDo: Dont override frappe.route_options if it is already set * test: Dont reload page before test
This commit is contained in:
parent
8ec0bdc402
commit
83d6659e12
19 changed files with 913 additions and 57 deletions
|
|
@ -137,6 +137,12 @@
|
||||||
"JsBarcode": true,
|
"JsBarcode": true,
|
||||||
"L": true,
|
"L": true,
|
||||||
"Chart": true,
|
"Chart": true,
|
||||||
"DataTable": true
|
"DataTable": true,
|
||||||
|
"Cypress": true,
|
||||||
|
"cy": true,
|
||||||
|
"it": true,
|
||||||
|
"context": true,
|
||||||
|
"before": true,
|
||||||
|
"beforeEach": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -185,3 +185,7 @@ typings/
|
||||||
|
|
||||||
# next.js build output
|
# next.js build output
|
||||||
.next
|
.next
|
||||||
|
|
||||||
|
# cypress
|
||||||
|
cypress/screenshots
|
||||||
|
cypress/videos
|
||||||
|
|
@ -9,6 +9,7 @@ python:
|
||||||
env:
|
env:
|
||||||
- DB=mariadb
|
- DB=mariadb
|
||||||
- DB=postgres
|
- DB=postgres
|
||||||
|
- TEST_TYPE=ui
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- mysql
|
- mysql
|
||||||
|
|
@ -18,11 +19,14 @@ addons:
|
||||||
hosts:
|
hosts:
|
||||||
- test_site
|
- test_site
|
||||||
- test_site_postgres
|
- test_site_postgres
|
||||||
|
- test_site_ui
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
exclude:
|
exclude:
|
||||||
- python: 2.7
|
- python: 2.7
|
||||||
env: DB=postgres
|
env: DB=postgres
|
||||||
|
- python: 3.6
|
||||||
|
env: TEST_TYPE=ui
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- $TRAVIS_BUILD_DIR/.travis/install.sh
|
- $TRAVIS_BUILD_DIR/.travis/install.sh
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,5 @@ sudo pip install -e ~/bench
|
||||||
rm $TRAVIS_BUILD_DIR/.git/shallow
|
rm $TRAVIS_BUILD_DIR/.git/shallow
|
||||||
cd ~/ && bench init frappe-bench --python $(which python) --frappe-path $TRAVIS_BUILD_DIR
|
cd ~/ && bench init frappe-bench --python $(which python) --frappe-path $TRAVIS_BUILD_DIR
|
||||||
cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
|
cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
|
||||||
cp -r $TRAVIS_BUILD_DIR/test_sites/test_site_postgres ~/frappe-bench/sites/
|
cp -r $TRAVIS_BUILD_DIR/test_sites/test_site_postgres ~/frappe-bench/sites/
|
||||||
|
cp -r $TRAVIS_BUILD_DIR/test_sites/test_site_ui ~/frappe-bench/sites/
|
||||||
|
|
@ -2,15 +2,29 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
setup_mariadb_env() {
|
||||||
|
mysql -u root -ptravis -e "create database $1"
|
||||||
|
mysql -u root -ptravis -e "USE mysql; CREATE USER '$1'@'localhost' IDENTIFIED BY '$1'; FLUSH PRIVILEGES; "
|
||||||
|
mysql -u root -ptravis -e "USE mysql; GRANT ALL PRIVILEGES ON \`$1\`.* TO '$1'@'localhost';"
|
||||||
|
}
|
||||||
|
|
||||||
if [[ $DB == 'mariadb' ]]; then
|
if [[ $DB == 'mariadb' ]]; then
|
||||||
mysql -u root -ptravis -e 'create database test_frappe'
|
setup_mariadb_env 'test_frappe'
|
||||||
mysql -u root -ptravis -e "USE mysql; CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'; FLUSH PRIVILEGES; "
|
|
||||||
mysql -u root -ptravis -e "USE mysql; GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';"
|
|
||||||
bench --site test_site reinstall --yes
|
bench --site test_site reinstall --yes
|
||||||
bench --site test_site setup-help
|
bench --site test_site setup-help
|
||||||
bench setup-global-help --root_password travis
|
bench setup-global-help --root_password travis
|
||||||
bench --site test_site scheduler disable
|
bench --site test_site scheduler disable
|
||||||
bench --site test_site run-tests --coverage
|
bench --site test_site run-tests --coverage
|
||||||
|
|
||||||
|
elif [[ $TEST_TYPE == 'ui' ]]; then
|
||||||
|
setup_mariadb_env 'test_site_ui'
|
||||||
|
bench --site test_site_ui --force restore ./apps/frappe/test_sites/test_site_ui/20181116_225029-test_site_ui-database.sql.gz
|
||||||
|
bench --site test_site_ui migrate
|
||||||
|
bench --site test_site_ui setup-help
|
||||||
|
bench setup-global-help --root_password travis
|
||||||
|
bench --site test_site_ui scheduler disable
|
||||||
|
cd apps/frappe && yarn && yarn cypress:run
|
||||||
|
|
||||||
elif [[ $DB == 'postgres' ]]; then
|
elif [[ $DB == 'postgres' ]]; then
|
||||||
psql -c "CREATE DATABASE test_frappe;" -U postgres
|
psql -c "CREATE DATABASE test_frappe;" -U postgres
|
||||||
psql -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe';" -U postgres
|
psql -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe';" -U postgres
|
||||||
|
|
|
||||||
4
cypress.json
Normal file
4
cypress.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"baseUrl": "http://test_site_ui:8000",
|
||||||
|
"projectId": "92odwv"
|
||||||
|
}
|
||||||
52
cypress/integration/awesome_bar.js
Normal file
52
cypress/integration/awesome_bar.js
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
context('Awesome Bar', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.login('Administrator', 'qwe');
|
||||||
|
cy.visit('/desk');
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.navbar-home').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('navigates to modules', () => {
|
||||||
|
cy.get('#navbar-search')
|
||||||
|
.type('modules{downarrow}{enter}', { delay: 100 });
|
||||||
|
|
||||||
|
cy.location('hash').should('eq', '#modules');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('navigates to doctype list', () => {
|
||||||
|
cy.get('#navbar-search')
|
||||||
|
.type('todo{downarrow}{enter}', { delay: 100 });
|
||||||
|
|
||||||
|
cy.get('h1').should('contain', 'To Do');
|
||||||
|
|
||||||
|
cy.location('hash').should('eq', '#List/ToDo/List');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('find text in doctype list', () => {
|
||||||
|
cy.get('#navbar-search')
|
||||||
|
.type('test in todo{downarrow}{enter}', { delay: 100 });
|
||||||
|
|
||||||
|
cy.get('h1').should('contain', 'To Do');
|
||||||
|
|
||||||
|
cy.get('.toggle-filter')
|
||||||
|
.should('have.length', 1)
|
||||||
|
.should('contain', 'ID like %test%');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('navigates to new form', () => {
|
||||||
|
cy.get('#navbar-search')
|
||||||
|
.type('new blog post{downarrow}{enter}', { delay: 100 });
|
||||||
|
|
||||||
|
cy.get('.title-text:visible').should('have.text', 'New Blog Post 1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calculates math expressions', () => {
|
||||||
|
cy.get('#navbar-search')
|
||||||
|
.type('55 + 32{downarrow}{enter}', { delay: 100 });
|
||||||
|
|
||||||
|
cy.get('.modal-title').should('contain', 'Result');
|
||||||
|
cy.get('.msgprint').should('contain', '55 + 32 = 87');
|
||||||
|
});
|
||||||
|
});
|
||||||
16
cypress/integration/form.js
Normal file
16
cypress/integration/form.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
context('Form', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.login('Administrator', 'qwe');
|
||||||
|
cy.visit('/desk');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create a new form', () => {
|
||||||
|
cy.visit('/desk#Form/ToDo/New ToDo 1');
|
||||||
|
cy.fill_field('description', 'this is a test todo', 'Text Editor').blur();
|
||||||
|
cy.get('.page-title').should('contain', 'Not Saved');
|
||||||
|
cy.get('.primary-action').click();
|
||||||
|
cy.visit('/desk#List/ToDo');
|
||||||
|
cy.location('hash').should('eq', '#List/ToDo/List');
|
||||||
|
cy.get('.list-row').should('contain', 'this is a test todo');
|
||||||
|
});
|
||||||
|
});
|
||||||
39
cypress/integration/login.js
Normal file
39
cypress/integration/login.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
context('Login', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/login');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('greets with login screen', () => {
|
||||||
|
cy.get('.page-card-head').contains('Sign In');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates password', () => {
|
||||||
|
cy.get('#login_email').type('Administrator');
|
||||||
|
cy.get('.btn-login').click();
|
||||||
|
cy.location('pathname').should('eq', '/login');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates email', () => {
|
||||||
|
cy.get('#login_password').type('qwe');
|
||||||
|
cy.get('.btn-login').click();
|
||||||
|
cy.location('pathname').should('eq', '/login');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs in using correct credentials', () => {
|
||||||
|
cy.get('#login_email').type('Administrator');
|
||||||
|
cy.get('#login_password').type('qwe');
|
||||||
|
|
||||||
|
cy.get('.btn-login').click();
|
||||||
|
cy.location('pathname').should('eq', '/desk');
|
||||||
|
cy.window().its('frappe.session.user').should('eq', 'Administrator');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows invalid login if incorrect credentials', () => {
|
||||||
|
cy.get('#login_email').type('Administrator');
|
||||||
|
cy.get('#login_password').type('qwer');
|
||||||
|
|
||||||
|
cy.get('.btn-login').click();
|
||||||
|
cy.get('.page-card-head').contains('Invalid Login. Try again.');
|
||||||
|
cy.location('pathname').should('eq', '/login');
|
||||||
|
});
|
||||||
|
});
|
||||||
17
cypress/plugins/index.js
Normal file
17
cypress/plugins/index.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// ***********************************************************
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
// `on` is used to hook into various events Cypress emits
|
||||||
|
// `config` is the resolved Cypress config
|
||||||
|
};
|
||||||
52
cypress/support/commands.js
Normal file
52
cypress/support/commands.js
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// ***********************************************
|
||||||
|
// 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) => { ... });
|
||||||
|
Cypress.Commands.add('login', (email, password) => {
|
||||||
|
cy.request({
|
||||||
|
url: '/',
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
cmd: 'login',
|
||||||
|
usr: email,
|
||||||
|
pwd: password
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => {
|
||||||
|
let selector = `.form-control[data-fieldname="${fieldname}"]`;
|
||||||
|
|
||||||
|
if (fieldtype === 'Text Editor') {
|
||||||
|
selector = `[data-fieldname="${fieldname}"] .ql-editor`;
|
||||||
|
}
|
||||||
|
|
||||||
|
cy.get(selector).as('input');
|
||||||
|
|
||||||
|
if (fieldtype === 'Select') {
|
||||||
|
return cy.get('@input').select(value);
|
||||||
|
} else {
|
||||||
|
return cy.get('@input').type(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
25
cypress/support/index.js
Normal file
25
cypress/support/index.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// ***********************************************************
|
||||||
|
// 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')
|
||||||
|
|
||||||
|
Cypress.Cookies.defaults({
|
||||||
|
whitelist: 'sid'
|
||||||
|
});
|
||||||
12
cypress/tsconfig.json
Normal file
12
cypress/tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"baseUrl": "../node_modules",
|
||||||
|
"types": [
|
||||||
|
"cypress"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
frappe.listview_settings['ToDo'] = {
|
frappe.listview_settings['ToDo'] = {
|
||||||
onload: function(me) {
|
onload: function(me) {
|
||||||
frappe.route_options = {
|
if (!frappe.route_options) {
|
||||||
"owner": frappe.session.user,
|
frappe.route_options = {
|
||||||
"status": "Open"
|
"owner": frappe.session.user,
|
||||||
};
|
"status": "Open"
|
||||||
|
};
|
||||||
|
}
|
||||||
me.page.set_title(__("To Do"));
|
me.page.set_title(__("To Do"));
|
||||||
|
|
||||||
},
|
},
|
||||||
hide_name_column: true,
|
hide_name_column: true,
|
||||||
refresh: function(me) {
|
refresh: function(me) {
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,20 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
||||||
|
|
||||||
this.actions_menu_items = this.get_actions_menu_items();
|
this.actions_menu_items = this.get_actions_menu_items();
|
||||||
|
|
||||||
|
if (this.view_user_settings.filters && this.view_user_settings.filters.length) {
|
||||||
|
// Priority 1: saved filters
|
||||||
|
const saved_filters = this.view_user_settings.filters;
|
||||||
|
this.filters = this.validate_filters(saved_filters);
|
||||||
|
} else {
|
||||||
|
// Priority 2: filters in listview_settings
|
||||||
|
this.filters = (this.settings.filters || []).map(f => {
|
||||||
|
if (f.length === 3) {
|
||||||
|
f = [this.doctype, f[0], f[1], f[2]];
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.patch_refresh_and_load_lib();
|
this.patch_refresh_and_load_lib();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,23 +280,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
||||||
|
|
||||||
before_refresh() {
|
before_refresh() {
|
||||||
if (frappe.route_options) {
|
if (frappe.route_options) {
|
||||||
// Priority 1: route filters
|
|
||||||
this.filters = this.parse_filters_from_route_options();
|
this.filters = this.parse_filters_from_route_options();
|
||||||
} else if (this.view_user_settings.filters && this.view_user_settings.filters.length) {
|
|
||||||
// Priority 2: saved filters
|
|
||||||
const saved_filters = this.view_user_settings.filters;
|
|
||||||
this.filters = this.validate_filters(saved_filters);
|
|
||||||
} else {
|
|
||||||
// Priority 3: filters in listview_settings
|
|
||||||
this.filters = (this.settings.filters || []).map(f => {
|
|
||||||
if (f.length === 3) {
|
|
||||||
f = [this.doctype, f[0], f[1], f[2]];
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.filters.length) {
|
|
||||||
return this.filter_area.clear(false)
|
return this.filter_area.clear(false)
|
||||||
.then(() => this.filter_area.set(this.filters));
|
.then(() => this.filter_area.set(this.filters));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node rollup/build.js",
|
"build": "node rollup/build.js",
|
||||||
"production": "FRAPPE_ENV=production node rollup/build.js",
|
"production": "FRAPPE_ENV=production node rollup/build.js",
|
||||||
"watch": "node rollup/watch.js"
|
"watch": "node rollup/watch.js",
|
||||||
|
"cypress:run": "cypress run --record --key 14ddd919-b01f-4d5f-b9d1-5af54d34c7f3",
|
||||||
|
"cypress:open": "cypress open"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -38,6 +40,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"chalk": "^2.3.2",
|
"chalk": "^2.3.2",
|
||||||
|
"cypress": "^3.1.1",
|
||||||
"less": "^3.0.4",
|
"less": "^3.0.4",
|
||||||
"node-sass": "^4.9.0",
|
"node-sass": "^4.9.0",
|
||||||
"rollup": "^0.65.0",
|
"rollup": "^0.65.0",
|
||||||
|
|
|
||||||
Binary file not shown.
14
test_sites/test_site_ui/site_config.json
Normal file
14
test_sites/test_site_ui/site_config.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"developer_mode": 1,
|
||||||
|
"db_name": "test_site_ui",
|
||||||
|
"db_password": "test_site_ui",
|
||||||
|
"db_type": "mariadb",
|
||||||
|
"auto_email_id": "test@example.com",
|
||||||
|
"mail_server": "smtp.example.com",
|
||||||
|
"mail_login": "test@example.com",
|
||||||
|
"mail_password": "test",
|
||||||
|
"admin_password": "qwe",
|
||||||
|
"root_password": "travis",
|
||||||
|
"run_selenium_tests": 1,
|
||||||
|
"host_name": "http://test_site_ui:8000"
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue