mirror of
https://github.com/frappe/frappe.git
synced 2024-06-12 21:32:26 +00:00
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
|
@ -137,6 +137,12 @@
|
|||
"JsBarcode": true,
|
||||
"L": 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
|
||||
|
||||
# cypress
|
||||
cypress/screenshots
|
||||
cypress/videos
|
|
@ -9,6 +9,7 @@ python:
|
|||
env:
|
||||
- DB=mariadb
|
||||
- DB=postgres
|
||||
- TEST_TYPE=ui
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
@ -18,11 +19,14 @@ addons:
|
|||
hosts:
|
||||
- test_site
|
||||
- test_site_postgres
|
||||
- test_site_ui
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- python: 2.7
|
||||
env: DB=postgres
|
||||
- python: 3.6
|
||||
env: TEST_TYPE=ui
|
||||
|
||||
install:
|
||||
- $TRAVIS_BUILD_DIR/.travis/install.sh
|
||||
|
|
|
@ -19,4 +19,5 @@ sudo pip install -e ~/bench
|
|||
rm $TRAVIS_BUILD_DIR/.git/shallow
|
||||
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_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
|
||||
|
||||
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
|
||||
mysql -u root -ptravis -e 'create database 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';"
|
||||
setup_mariadb_env 'test_frappe'
|
||||
bench --site test_site reinstall --yes
|
||||
bench --site test_site setup-help
|
||||
bench setup-global-help --root_password travis
|
||||
bench --site test_site scheduler disable
|
||||
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
|
||||
psql -c "CREATE DATABASE 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'] = {
|
||||
onload: function(me) {
|
||||
frappe.route_options = {
|
||||
"owner": frappe.session.user,
|
||||
"status": "Open"
|
||||
};
|
||||
if (!frappe.route_options) {
|
||||
frappe.route_options = {
|
||||
"owner": frappe.session.user,
|
||||
"status": "Open"
|
||||
};
|
||||
}
|
||||
me.page.set_title(__("To Do"));
|
||||
|
||||
},
|
||||
hide_name_column: true,
|
||||
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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -266,23 +280,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
before_refresh() {
|
||||
if (frappe.route_options) {
|
||||
// Priority 1: route filters
|
||||
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)
|
||||
.then(() => this.filter_area.set(this.filters));
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
"scripts": {
|
||||
"build": "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": {
|
||||
"type": "git",
|
||||
|
@ -38,6 +40,7 @@
|
|||
"devDependencies": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"chalk": "^2.3.2",
|
||||
"cypress": "^3.1.1",
|
||||
"less": "^3.0.4",
|
||||
"node-sass": "^4.9.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…
Reference in New Issue
Block a user