Merge branch 'develop' into map-read-only

This commit is contained in:
Raffael Meyer 2023-05-22 14:52:08 +02:00 committed by GitHub
commit daf936a79a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1699 changed files with 71992 additions and 72710 deletions

View file

@ -12,3 +12,10 @@ charset = utf-8
[{*.py,*.js,*.vue,*.css,*.scss,*.html}]
indent_style = tab
indent_size = 4
max_line_length = 99
# JSON files - mostly doctype schema files
[{*.json}]
insert_final_newline = false
indent_style = space
indent_size = 2

56
.flake8
View file

@ -1,37 +1,75 @@
[flake8]
ignore =
B001,
B007,
B009,
B010,
B950,
E101,
E111,
E114,
E116,
E117,
E121,
E122,
E123,
E124,
E125,
E126,
E127,
E128,
E131,
E201,
E202,
E203,
E211,
E221,
E222,
E223,
E224,
E225,
E226,
E228,
E231,
E241,
E242,
E251,
E261,
E262,
E265,
E266,
E271,
E272,
E273,
E274,
E301,
E302,
E303,
E305,
E306,
E402,
E501,
E502,
E701,
E702,
E703,
E741,
F401,
F403,
F405,
W191,
W291,
W292,
W293,
W391,
W503,
W504,
F403,
B007,
B950,
W191,
E124, # closing bracket, irritating while writing QB code
E131, # continuation line unaligned for hanging indent
E123, # closing bracket does not match indentation of opening bracket's line
E101, # ensured by use of black
E711,
E129,
F841,
E713,
E712,
B028,
max-line-length = 200
exclude=.github/helper/semgrep_rules
exclude=,test_*.py

View file

@ -25,3 +25,12 @@ c0c5b2ebdddbe8898ce2d5e5365f4931ff73b6bf
# update python code to use 3.10 supported features
81b37cb7d2160866afa2496873656afe53f0c145
# mass minified JSON schema
85e3ee940353d7b0b517b33815148672e9a8b15b
# format JS files with pretter
40f27f908a3890c9a90d2d96794fc31fcea63c59
# db.get_all -> get_all
2eec621e95564c359ad22da79501a855c1f32b03

View file

@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Community Forum
url: https://discuss.erpnext.com/
url: https://discuss.frappe.io/c/framework/5
about: For general QnA, discussions and community help.

109
.github/helper/ci.py vendored Normal file
View file

@ -0,0 +1,109 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See LICENSE
import os
from pathlib import Path
STANDARD_INCLUSIONS = ["*.py"]
STANDARD_EXCLUSIONS = [
"*.js",
"*.xml",
"*.pyc",
"*.css",
"*.less",
"*.scss",
"*.vue",
"*.html",
"*/test_*",
"*/node_modules/*",
"*/doctype/*/*_dashboard.py",
"*/patches/*",
".github/*",
]
# tested via commands' test suite
TESTED_VIA_CLI = [
"*/frappe/installer.py",
"*/frappe/utils/install.py",
"*/frappe/utils/scheduler.py",
"*/frappe/utils/doctor.py",
"*/frappe/build.py",
"*/frappe/database/__init__.py",
"*/frappe/database/db_manager.py",
"*/frappe/database/**/setup_db.py",
]
FRAPPE_EXCLUSIONS = [
"*/tests/*",
"*/commands/*",
"*/frappe/change_log/*",
"*/frappe/exceptions*",
"*/frappe/desk/page/setup_wizard/setup_wizard.py",
"*/frappe/coverage.py",
"*frappe/setup.py",
"*/frappe/hooks.py",
"*/doctype/*/*_dashboard.py",
"*/patches/*",
"*/.github/helper/ci.py",
] + TESTED_VIA_CLI
def get_bench_path():
return Path(__file__).resolve().parents[4]
class CodeCoverage:
def __init__(self, with_coverage, app):
self.with_coverage = with_coverage
self.app = app or "frappe"
def __enter__(self):
if self.with_coverage:
import os
from coverage import Coverage
# Generate coverage report only for app that is being tested
source_path = os.path.join(get_bench_path(), "apps", self.app)
print(f"Source path: {source_path}")
omit = STANDARD_EXCLUSIONS[:]
if self.app == "frappe":
omit.extend(FRAPPE_EXCLUSIONS)
self.coverage = Coverage(source=[source_path], omit=omit, include=STANDARD_INCLUSIONS)
self.coverage.start()
def __exit__(self, exc_type, exc_value, traceback):
if self.with_coverage:
self.coverage.stop()
self.coverage.save()
self.coverage.xml_report()
if __name__ == "__main__":
app = "frappe"
site = os.environ.get("SITE") or "test_site"
use_orchestrator = bool(os.environ.get("ORCHESTRATOR_URL"))
build_number = 1
total_builds = 1
try:
build_number = int(os.environ.get("BUILD_NUMBER"))
except Exception:
pass
try:
total_builds = int(os.environ.get("TOTAL_BUILDS"))
except Exception:
pass
with CodeCoverage(with_coverage=True, app=app):
if use_orchestrator:
from frappe.parallel_test_runner import ParallelTestWithOrchestrator
ParallelTestWithOrchestrator(app, site=site)
else:
from frappe.parallel_test_runner import ParallelTestRunner
ParallelTestRunner(app, site=site, build_number=build_number, total_builds=total_builds)

View file

@ -1,7 +1,7 @@
{
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "test_frappe_consumer",
"db_name": "test_frappe",
"db_password": "test_frappe",
"allow_tests": true,
"db_type": "mariadb",
@ -13,5 +13,6 @@
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site:8000",
"monitor": 1,
"server_script_enabled": true
}

View file

@ -1,7 +1,7 @@
{
"db_host": "127.0.0.1",
"db_port": 5432,
"db_name": "test_frappe_consumer",
"db_name": "test_frappe",
"db_password": "test_frappe",
"db_type": "postgres",
"allow_tests": true,

View file

@ -3,48 +3,71 @@ import requests
from urllib.parse import urlparse
docs_repos = [
"frappe_docs",
"erpnext_documentation",
WEBSITE_REPOS = [
"erpnext_com",
"frappe_io",
]
DOCUMENTATION_DOMAINS = [
"docs.erpnext.com",
"frappeframework.com",
]
def uri_validator(x):
result = urlparse(x)
return all([result.scheme, result.netloc, result.path])
def docs_link_exists(body):
for line in body.splitlines():
for word in line.split():
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split('/')
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True
if parsed_url.netloc in ["docs.erpnext.com", "frappeframework.com"]:
return True
def is_valid_url(url: str) -> bool:
parts = urlparse(url)
return all((parts.scheme, parts.netloc, parts.path))
def is_documentation_link(word: str) -> bool:
if not word.startswith("http") or not is_valid_url(word):
return False
parsed_url = urlparse(word)
if parsed_url.netloc in DOCUMENTATION_DOMAINS:
return True
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split("/")
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in WEBSITE_REPOS:
return True
return False
def contains_documentation_link(body: str) -> bool:
return any(
is_documentation_link(word)
for line in body.splitlines()
for word in line.split()
)
def check_pull_request(number: str) -> "tuple[int, str]":
response = requests.get(f"https://api.github.com/repos/frappe/frappe/pulls/{number}")
if not response.ok:
return 1, "Pull Request Not Found! ⚠️"
payload = response.json()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (
not title.startswith("feat")
or not head_sha
or "no-docs" in body
or "backport" in body
):
return 0, "Skipping documentation checks... 🏃"
if contains_documentation_link(body):
return 0, "Documentation Link Found. You're Awesome! 🎉"
return 1, "Documentation Link Not Found! ⚠️"
if __name__ == "__main__":
pr = sys.argv[1]
response = requests.get("https://api.github.com/repos/frappe/frappe/pulls/{}".format(pr))
if response.ok:
payload = response.json()
title = (payload.get("title") or "").lower()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if title.startswith("feat") and head_sha and "no-docs" not in body:
if docs_link_exists(body):
print("Documentation Link Found. You're Awesome! 🎉")
else:
print("Documentation Link Not Found! ⚠️")
sys.exit(1)
else:
print("Skipping documentation checks... 🏃")
exit_code, message = check_pull_request(sys.argv[1])
print(message)
sys.exit(exit_code)

View file

@ -1,75 +0,0 @@
[flake8]
ignore =
B001,
B007,
B009,
B010,
B950,
E101,
E111,
E114,
E116,
E117,
E121,
E122,
E123,
E124,
E125,
E126,
E127,
E128,
E131,
E201,
E202,
E203,
E211,
E221,
E222,
E223,
E224,
E225,
E226,
E228,
E231,
E241,
E242,
E251,
E261,
E262,
E265,
E266,
E271,
E272,
E273,
E274,
E301,
E302,
E303,
E305,
E306,
E402,
E501,
E502,
E701,
E702,
E703,
E741,
F401,
F403,
F405,
W191,
W291,
W292,
W293,
W391,
W503,
W504,
E711,
E129,
F841,
E713,
E712,
max-line-length = 200
exclude=.github/helper/semgrep_rules,test_*.py

View file

@ -1,67 +1,71 @@
#!/bin/bash
set -e
cd ~ || exit
pip install frappe-bench
echo "Setting Up Bench..."
pip install frappe-bench
bench -v init frappe-bench --skip-assets --python "$(which python)" --frappe-path "${GITHUB_WORKSPACE}"
cd ./frappe-bench || exit
bench -v setup requirements --dev
if [ "$TYPE" == "ui" ]
then
bench -v setup requirements --node;
fi
echo "Setting Up Sites & Database..."
mkdir ~/frappe-bench/sites/test_site
cp "${GITHUB_WORKSPACE}/.github/helper/consumer_db/$DB.json" ~/frappe-bench/sites/test_site/site_config.json
cp "${GITHUB_WORKSPACE}/.github/helper/db/$DB.json" ~/frappe-bench/sites/test_site/site_config.json
if [ "$TYPE" == "server" ]; then
mkdir ~/frappe-bench/sites/test_site_producer;
cp "${GITHUB_WORKSPACE}/.github/helper/producer_db/$DB.json" ~/frappe-bench/sites/test_site_producer/site_config.json;
if [ "$DB" == "mariadb" ]
then
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL character_set_server = 'utf8mb4'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "FLUSH PRIVILEGES";
fi
if [ "$DB" == "postgres" ]
then
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe'" -U postgres;
fi
if [ "$DB" == "mariadb" ];then
curl -LsS -O https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
sudo bash mariadb_repo_setup --mariadb-server-version=10.6
sudo apt install mariadb-client
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL character_set_server = 'utf8mb4'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_consumer";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_consumer'@'localhost' IDENTIFIED BY 'test_frappe_consumer'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_consumer\`.* TO 'test_frappe_consumer'@'localhost'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_producer";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_producer'@'localhost' IDENTIFIED BY 'test_frappe_producer'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_producer\`.* TO 'test_frappe_producer'@'localhost'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "FLUSH PRIVILEGES";
fi
if [ "$DB" == "postgres" ];then
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_consumer" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_consumer WITH PASSWORD 'test_frappe'" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_producer" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_producer WITH PASSWORD 'test_frappe'" -U postgres;
fi
cd ./frappe-bench || exit
echo "Setting Up Procfile..."
sed -i 's/^watch:/# watch:/g' Procfile
sed -i 's/^schedule:/# schedule:/g' Procfile
if [ "$TYPE" == "server" ]; then sed -i 's/^socketio:/# socketio:/g' Procfile; fi
if [ "$TYPE" == "server" ]; then sed -i 's/^redis_socketio:/# redis_socketio:/g' Procfile; fi
if [ "$TYPE" == "server" ]
then
sed -i 's/^socketio:/# socketio:/g' Procfile
sed -i 's/^redis_socketio:/# redis_socketio:/g' Procfile
fi
if [ "$TYPE" == "ui" ]; then bench -v setup requirements --node; fi
bench -v setup requirements --dev
if [ "$TYPE" == "ui" ]
then
sed -i 's/^web: bench serve/web: bench serve --with-coverage/g' Procfile
fi
if [ "$TYPE" == "ui" ]; then sed -i 's/^web: bench serve/web: bench serve --with-coverage/g' Procfile; fi
echo "Starting Bench..."
# install node-sass which is required for website theme test
cd ./apps/frappe || exit
yarn add node-sass@4.13.1
cd ../..
bench start &> bench_start.log &
if [ "$TYPE" == "server" ]
then
CI=Yes bench build --app frappe &
build_pid=$!
fi
bench start &
bench --site test_site reinstall --yes
if [ "$TYPE" == "server" ]; then bench --site test_site_producer reinstall --yes; fi
if [ "$TYPE" == "server" ]; then CI=Yes bench build --app frappe; fi
if [ "$TYPE" == "server" ]
then
# wait till assets are built succesfully
wait $build_pid
fi

View file

@ -1,19 +1,13 @@
#!/bin/bash
set -e
# Check for merge conflicts before proceeding
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
echo "Setting Up System Dependencies..."
# install wkhtmltopdf
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
sudo chmod o+x /usr/local/bin/wkhtmltopdf
sudo apt update
sudo apt install libcups2-dev redis-server mariadb-client-10.6
# install cups
sudo apt update && sudo apt install libcups2-dev redis-server
install_wkhtmltopdf() {
wget -q https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
}
install_wkhtmltopdf &

View file

@ -1,16 +0,0 @@
{
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "test_frappe_producer",
"db_password": "test_frappe",
"allow_tests": true,
"db_type": "mariadb",
"auto_email_id": "test@example.com",
"mail_server": "smtp.example.com",
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site_producer:8000"
}

View file

@ -1,16 +0,0 @@
{
"db_host": "127.0.0.1",
"db_port": 5432,
"db_name": "test_frappe_producer",
"db_password": "test_frappe",
"db_type": "postgres",
"allow_tests": true,
"auto_email_id": "test@example.com",
"mail_server": "smtp.example.com",
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"root_login": "postgres",
"root_password": "travis",
"host_name": "http://test_site_producer:8000"
}

View file

@ -4,8 +4,10 @@ import re
import shlex
import subprocess
import sys
import time
import urllib.request
from functools import lru_cache
from urllib.error import HTTPError
@lru_cache(maxsize=None)
@ -15,41 +17,78 @@ def fetch_pr_data(pr_number, repo, endpoint=""):
if endpoint:
api_url += f"/{endpoint}"
req = urllib.request.Request(api_url)
res = urllib.request.urlopen(req)
return json.loads(res.read().decode('utf8'))
res = req(api_url)
return json.loads(res.read().decode("utf8"))
def req(url):
"Simple resilient request call to handle rate limits."
headers = None
token = os.environ.get("GITHUB_TOKEN")
if token:
headers = {"authorization": f"Bearer {token}"}
retries = 0
while True:
try:
req = urllib.request.Request(url, headers=headers)
return urllib.request.urlopen(req)
except HTTPError as exc:
if exc.code == 403 and retries < 5:
retries += 1
time.sleep(retries)
continue
raise
def get_files_list(pr_number, repo="frappe/frappe"):
return [change["filename"] for change in fetch_pr_data(pr_number, repo, "files")]
def get_output(command, shell=True):
print(command)
command = shlex.split(command)
return subprocess.check_output(command, shell=shell, encoding="utf8").strip()
def has_skip_ci_label(pr_number, repo="frappe/frappe"):
return has_label(pr_number, "Skip CI", repo)
def has_run_server_tests_label(pr_number, repo="frappe/frappe"):
return has_label(pr_number, "Run Server Tests", repo)
def has_run_ui_tests_label(pr_number, repo="frappe/frappe"):
return has_label(pr_number, "Run UI Tests", repo)
def has_label(pr_number, label, repo="frappe/frappe"):
return any([fetched_label["name"] for fetched_label in fetch_pr_data(pr_number, repo)["labels"] if fetched_label["name"] == label])
return any(
[
fetched_label["name"]
for fetched_label in fetch_pr_data(pr_number, repo)["labels"]
if fetched_label["name"] == label
]
)
def is_py(file):
return file.endswith("py")
def is_ci(file):
return ".github" in file
def is_frontend_code(file):
return file.lower().endswith((".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html"))
return file.lower().endswith(
(".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html")
)
def is_docs(file):
regex = re.compile(r'\.(md|png|jpg|jpeg|csv|svg)$|^.github|LICENSE')
regex = re.compile(r"\.(md|png|jpg|jpeg|csv|svg)$|^.github|LICENSE")
return bool(regex.search(file))
@ -61,8 +100,7 @@ if __name__ == "__main__":
# this is a push build, run all builds
if not pr_number:
os.system('echo "::set-output name=build::strawberry"')
os.system('echo "::set-output name=build-server::strawberry"')
os.system('echo "build=strawberry" >> $GITHUB_OUTPUT')
sys.exit(0)
files_list = files_list or get_files_list(pr_number=pr_number, repo=repo)
@ -78,8 +116,13 @@ if __name__ == "__main__":
only_py_changed = updated_py_file_count == len(files_list)
if has_skip_ci_label(pr_number, repo):
print("Found `Skip CI` label on pr, stopping build process.")
sys.exit(0)
if build_type == "ui" and has_run_ui_tests_label(pr_number, repo):
print("Running UI tests only.")
elif build_type == "server" and has_run_server_tests_label(pr_number, repo):
print("Running server tests only.")
else:
print("Found `Skip CI` label on pr, stopping build process.")
sys.exit(0)
elif ci_files_changed:
print("CI related files were updated, running all build processes.")
@ -88,7 +131,11 @@ if __name__ == "__main__":
print("Only docs were updated, stopping build process.")
sys.exit(0)
elif only_frontend_code_changed and build_type == "server" and not has_run_server_tests_label(pr_number, repo):
elif (
only_frontend_code_changed
and build_type == "server"
and not has_run_server_tests_label(pr_number, repo)
):
print("Only Frontend code was updated; Stopping Python build process.")
sys.exit(0)
@ -96,4 +143,4 @@ if __name__ == "__main__":
print("Only Python code was updated, stopping Cypress build process.")
sys.exit(0)
os.system('echo "::set-output name=build::strawberry"')
os.system('echo "build=strawberry" >> $GITHUB_OUTPUT')

View file

@ -20,19 +20,12 @@ for _file in files_to_scan:
if 'frappe-lint: disable-translate' in line:
continue
start_matches = start_pattern.search(line)
if start_matches:
starts_with_f = starts_with_f_pattern.search(line)
if starts_with_f:
has_f_string = f_string_pattern.search(line)
if has_f_string:
if start_matches := start_pattern.search(line):
if starts_with_f := starts_with_f_pattern.search(line):
if has_f_string := f_string_pattern.search(line):
errors_encounter += 1
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
continue
else:
continue
continue
match = pattern.search(line)
error_found = False

4
.github/labeler.yml vendored Normal file
View file

@ -0,0 +1,4 @@
# Any python files modifed but no test files modified
add-test-cases:
- any: ['frappe/**/*.py']
all: ['!frappe/**/test*.py']

30
.github/semantic.yml vendored
View file

@ -1,30 +0,0 @@
# Always validate the PR title AND all the commits
titleAndCommits: true
# Allow use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns")
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
allowMergeCommits: true
# Allow use of Revert commits (eg on github: "Revert "feat: ride unicorns"")
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
allowRevertCommits: true
# For allowed PR types: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json
# Tool Reference: https://github.com/zeke/semantic-pull-requests
# By default types specified in commitizen/conventional-commit-types is used.
# See: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json
# You can override the valid types
types:
- BREAKING CHANGE
- feat
- fix
- docs
- style
- refactor
- perf
- test
- build
- ci
- chore
- revert

26
.github/workflows/backport.yml vendored Normal file
View file

@ -0,0 +1,26 @@
name: Backport
on:
pull_request_target:
types:
- closed
- labeled
jobs:
main:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Actions
uses: actions/checkout@v3
with:
repository: "frappe/backport"
path: ./actions
ref: develop
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run backport
uses: ./actions/backport
with:
token: ${{secrets.RELEASE_TOKEN}}
labelsToAdd: "backport"
title: "{{originalTitle}}"

View file

@ -16,10 +16,10 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js v14
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 14
node-version: 18
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
@ -31,4 +31,4 @@ jobs:
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
run: npx semantic-release
run: npx semantic-release

View file

@ -1,22 +0,0 @@
name: 'Python Dependency Check'
on:
pull_request:
workflow_dispatch:
push:
branches: [ develop ]
permissions:
contents: read
jobs:
deps-vulnerable-check:
name: 'Vulnerable Dependency'
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: actions/checkout@v3
- run: pip install pip-audit
- run: pip-audit ${GITHUB_WORKSPACE}

View file

@ -1,20 +0,0 @@
name: 'Trigger Docker build on release'
on:
release:
types: [released]
permissions:
contents: read
jobs:
curl:
permissions:
contents: none
name: 'Trigger Docker build on release'
runs-on: ubuntu-latest
container:
image: alpine:latest
steps:
- name: curl
run: |
apk add curl bash
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'

View file

@ -1,28 +0,0 @@
name: 'Documentation Check'
on:
pull_request:
types: [ opened, synchronize, reopened, edited ]
permissions:
contents: read
jobs:
docs-required:
name: 'Documentation Required'
runs-on: ubuntu-latest
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: 'Clone repo'
uses: actions/checkout@v3
- name: Validate Docs
env:
PR_NUMBER: ${{ github.event.number }}
run: |
pip install requests --quiet
python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER

32
.github/workflows/initiate_release.yml vendored Normal file
View file

@ -0,0 +1,32 @@
# This workflow is agnostic to branches. Only maintain on develop branch.
# To add/remove versions just modify the matrix.
name: Create weekly release pull requests
on:
schedule:
# 9:30 UTC => 3 PM IST Tuesday
- cron: "30 9 * * 2"
workflow_dispatch:
jobs:
release:
name: Release
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
version: ["13", "14"]
steps:
- uses: octokit/request-action@v2.x
with:
route: POST /repos/{owner}/{repo}/pulls
owner: frappe
repo: frappe
title: |-
"chore: release v${{ matrix.version }}"
body: "Automated weekly release."
base: version-${{ matrix.version }}
head: version-${{ matrix.version }}-hotfix
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}

12
.github/workflows/labeller.yml vendored Normal file
View file

@ -0,0 +1,12 @@
name: "Pull Request Labeler"
on:
pull_request_target:
types: [opened, reopened]
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View file

@ -1,29 +1,100 @@
name: Linters
on:
pull_request: { }
pull_request:
workflow_dispatch:
push:
branches: [ develop ]
permissions:
contents: read
concurrency:
group: commitcheck-frappe-${{ github.event_name }}-${{ github.event.number }}
cancel-in-progress: true
jobs:
linters:
name: Frappe Linter
commit-lint:
name: 'Semantic Commits'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 200
- uses: actions/setup-node@v3
with:
node-version: 16
check-latest: true
- name: Set up Python
- name: Check commit titles
run: |
npm install @commitlint/cli @commitlint/config-conventional
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
docs-required:
name: 'Documentation Required'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
- uses: actions/checkout@v3
- name: Install and Run Pre-commit
uses: pre-commit/action@v3.0.0
- name: Validate Docs
env:
PR_NUMBER: ${{ github.event.number }}
run: |
pip install requests --quiet
python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER
linter:
name: 'Frappe Linter'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: pre-commit/action@v3.0.0
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep
run: pip install semgrep==0.97.0
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
run: |
pip install semgrep==0.97.0
semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
deps-vulnerable-check:
name: 'Vulnerable Dependency Check'
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: actions/checkout@v3
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Install and run pip-audit
run: |
pip install pip-audit
cd ${GITHUB_WORKSPACE}
sed -i '/dropbox/d' pyproject.toml # Remove dropbox temporarily https://github.com/dropbox/dropbox-sdk-python/pull/456
pip-audit --desc on .

21
.github/workflows/lock.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: 'Lock threads'
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4
with:
github-token: ${{ github.token }}
issue-inactive-days: 14
pr-inactive-days: 14

View file

@ -1,8 +1,11 @@
name: 'Frappe Assets'
name: 'Release'
on:
release:
types: [ created ]
types: [released]
permissions:
contents: read
env:
GITHUB_TOKEN: ${{ github.token }}
@ -16,9 +19,11 @@ jobs:
- uses: actions/checkout@v3
with:
path: 'frappe'
- uses: actions/setup-node@v3
with:
python-version: '12.x'
node-version: 16
- uses: actions/setup-python@v4
with:
python-version: '3.10'
@ -36,7 +41,7 @@ jobs:
- name: Get release
id: get_release
uses: bruceadams/get-release@v1.2.3
uses: bruceadams/get-release@v1.3.1
- name: Upload built Assets to Release
uses: actions/upload-release-asset@v1.0.2
@ -45,3 +50,16 @@ jobs:
asset_path: build/assets.tar.gz
asset_name: assets.tar.gz
asset_content_type: application/octet-stream
docker-release:
name: 'Trigger Docker build on release'
runs-on: ubuntu-latest
permissions:
contents: none
container:
image: alpine:latest
steps:
- name: curl
run: |
apk add curl bash
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'

View file

@ -1,21 +1,45 @@
name: Patch
on: [pull_request, workflow_dispatch]
name: Patch (MariaDB)
on:
pull_request:
workflow_dispatch:
concurrency:
group: patch-mariadb-develop-${{ github.event.number }}
group: patch-mariadb-develop-${{ github.event_name }}-${{ github.event.number }}
cancel-in-progress: true
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
contents: read
jobs:
test:
checkrun:
name: Build Check
runs-on: ubuntu-latest
timeout-minutes: 60
name: Patch Test
outputs:
build: ${{ steps.check-build.outputs.build }}
steps:
- name: Clone
uses: actions/checkout@v3
- name: Check if build should be run
id: check-build
run: |
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py"
env:
TYPE: "server"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test:
name: Patch
runs-on: ubuntu-latest
needs: checkrun
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
timeout-minutes: 60
services:
mariadb:
@ -30,6 +54,13 @@ jobs:
- name: Clone
uses: actions/checkout@v3
- name: Check for Merge Conflicts
run: |
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
- name: Setup Python
uses: "gabrielfalcao/pyenv-action@v10"
with:
@ -38,24 +69,13 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
check-latest: true
- name: Check if build should be run
id: check-build
run: |
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py"
env:
TYPE: "server"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
- name: Add to Hosts
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v3
with:
path: ~/.cache/pip
@ -64,26 +84,11 @@ jobs:
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@ -92,25 +97,18 @@ jobs:
${{ runner.os }}-yarn-
- name: Install Dependencies
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
env:
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
TYPE: server
- name: Install
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: |
bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
pip install frappe-bench
pyenv global $(pyenv versions | grep '3.10')
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: mariadb
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
TYPE: server
DB: mariadb
- name: Run Patch Tests
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: |
cd ~/frappe-bench/
wget https://frappeframework.com/files/v10-frappe.sql.gz
@ -120,23 +118,28 @@ jobs:
cd apps/frappe/
git remote set-url upstream https://github.com/frappe/frappe.git
pyenv global $(pyenv versions | grep '3.7')
for version in $(seq 12 13)
do
echo "Updating to v$version"
branch_name="version-$version-hotfix"
git fetch --depth 1 upstream $branch_name:$branch_name
git checkout -q -f $branch_name
pip install -U frappe-bench
function update_to_version() {
version=$1
branch_name="version-$version-hotfix"
echo "Updating to v$version"
git fetch --depth 1 upstream $branch_name:$branch_name
git checkout -q -f $branch_name
pip install -U frappe-bench
rm -rf ~/frappe-bench/env
bench -v setup env
bench --site test_site migrate
done
rm -rf ~/frappe-bench/env
bench -v setup env
bench --site test_site migrate
}
pyenv global $(pyenv versions | grep '3.7')
update_to_version 12
update_to_version 13
pyenv global $(pyenv versions | grep '3.10')
update_to_version 14
echo "Updating to last commit"
git checkout -q -f "$GITHUB_SHA"
pyenv global $(pyenv versions | grep '3.10')
rm -rf ~/frappe-bench/env
bench -v setup env
bench --site test_site migrate

View file

@ -1,6 +1,7 @@
name: 'Frappe Assets'
on:
workflow_dispatch:
push:
branches: [ develop ]
@ -15,10 +16,10 @@ jobs:
path: 'frappe'
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
- uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
- name: Set up bench and build assets
run: |
npm install -g yarn

38
.github/workflows/release_notes.yml vendored Normal file
View file

@ -0,0 +1,38 @@
# This action:
#
# 1. Generates release notes using github API.
# 2. Strips unnecessary info like chore/style etc from notes.
# 3. Updates release info.
# This action needs to be maintained on all branches that do releases.
name: 'Release Notes'
on:
workflow_dispatch:
inputs:
tag_name:
description: 'Tag of release like v13.0.0'
required: true
type: string
release:
types: [released]
permissions:
contents: read
jobs:
regen-notes:
name: 'Regenerate release notes'
runs-on: ubuntu-latest
steps:
- name: Update notes
run: |
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/frappe/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' )
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/frappe/releases/tags/$RELEASE_TAG | jq -r '.id')
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/frappe/releases/$RELEASE_ID -f body=$NEW_NOTES
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}

View file

@ -1,135 +0,0 @@
name: Server
on:
pull_request:
workflow_dispatch:
push:
branches: [ develop ]
concurrency:
group: server-mariadb-develop-${{ github.event.number }}
cancel-in-progress: true
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
container: [1, 2]
name: Python Unit Tests (MariaDB)
services:
mariadb:
image: mariadb:10.6
env:
MARIADB_ROOT_PASSWORD: travis
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Check if build should be run
id: check-build
run: |
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py"
env:
TYPE: "server"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
- uses: actions/setup-node@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
with:
node-version: 14
check-latest: true
- name: Add to Hosts
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: |
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts
- name: Cache pip
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install Dependencies
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
env:
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
TYPE: server
- name: Install
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: mariadb
TYPE: server
- name: Run Tests
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator --with-coverage
env:
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
- name: Upload coverage data
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: codecov/codecov-action@v3
with:
name: MariaDB
fail_ci_if_error: true
files: /home/runner/frappe-bench/sites/coverage.xml
verbose: true
flags: server

View file

@ -7,25 +7,58 @@ on:
branches: [ develop ]
concurrency:
group: server-postgres-develop-${{ github.event.number }}
group: server-develop-${{ github.event_name }}-${{ github.event.number }}
cancel-in-progress: true
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
contents: read
jobs:
test:
checkrun:
name: Build Check
runs-on: ubuntu-latest
outputs:
build: ${{ steps.check-build.outputs.build }}
steps:
- name: Clone
uses: actions/checkout@v3
- name: Check if build should be run
id: check-build
run: |
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py"
env:
TYPE: "server"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test:
name: Unit Tests
runs-on: ubuntu-latest
needs: checkrun
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
db: ["mariadb", "postgres"]
container: [1, 2]
name: Python Unit Tests (Postgres)
services:
mariadb:
image: mariadb:10.6
env:
MARIADB_ROOT_PASSWORD: travis
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
postgres:
image: postgres:12.4
env:
@ -45,31 +78,26 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
- name: Check if build should be run
id: check-build
- name: Check for valid Python & Merge Conflicts
run: |
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py"
env:
TYPE: "server"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
python -m compileall -q -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
- uses: actions/setup-node@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
with:
node-version: '14'
node-version: 16
check-latest: true
- name: Add to Hosts
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: |
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts
- name: Cache pip
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v3
with:
path: ~/.cache/pip
@ -78,26 +106,11 @@ jobs:
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@ -106,33 +119,45 @@ jobs:
${{ runner.os }}-yarn-
- name: Install Dependencies
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
run: |
bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
TYPE: server
- name: Install
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: postgres
TYPE: server
DB: ${{ matrix.db }}
- name: Run Tests
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator --with-coverage
run: cd ~/frappe-bench/sites && ../env/bin/python3 ../apps/frappe/.github/helper/ci.py
env:
SITE: test_site
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
BUILD_NUMBER: ${{ matrix.container }}
TOTAL_BUILDS: 2
- name: Upload coverage data
uses: actions/upload-artifact@v3
with:
name: coverage-${{ matrix.db }}-${{ matrix.container }}
path: /home/runner/frappe-bench/sites/coverage.xml
coverage:
name: Coverage Wrap Up
needs: [test, checkrun]
runs-on: ubuntu-latest
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
steps:
- name: Clone
uses: actions/checkout@v3
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Upload coverage data
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: codecov/codecov-action@v3
with:
name: Postgres
name: Server
fail_ci_if_error: true
files: /home/runner/frappe-bench/sites/coverage.xml
verbose: true
flags: server

View file

@ -7,21 +7,46 @@ on:
branches: [ develop ]
concurrency:
group: ui-develop-${{ github.event.number }}
group: ui-develop-${{ github.event_name }}-${{ github.event.number }}
cancel-in-progress: true
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
contents: read
jobs:
checkrun:
name: Build Check
runs-on: ubuntu-latest
outputs:
build: ${{ steps.check-build.outputs.build }}
steps:
- name: Clone
uses: actions/checkout@v3
- name: Check if build should be run
id: check-build
run: |
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py"
env:
TYPE: "ui"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test:
runs-on: ubuntu-latest
needs: checkrun
if: ${{ needs.checkrun.outputs.build == 'strawberry' && github.repository_owner == 'frappe' }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
containers: [1, 2, 3]
# Make sure you modify coverage submission file list if changing this
container: [1, 2, 3]
name: UI Tests (Cypress)
@ -41,31 +66,26 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
- name: Check if build should be run
id: check-build
- name: Check for valid Python & Merge Conflicts
run: |
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py"
env:
TYPE: "ui"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
python -m compileall -q -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
- uses: actions/setup-node@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
with:
node-version: 14
node-version: 16
check-latest: true
- name: Add to Hosts
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: |
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts
- name: Cache pip
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v3
with:
path: ~/.cache/pip
@ -74,104 +94,105 @@ jobs:
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-yarn-ui-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
${{ runner.os }}-yarn-ui-
- name: Cache cypress binary
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v3
with:
path: ~/.cache
key: ${{ runner.os }}-cypress-
restore-keys: |
${{ runner.os }}-cypress-
${{ runner.os }}-
path: ~/.cache/Cypress
key: ${{ runner.os }}-cypress
- name: Install Dependencies
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
run: |
bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
TYPE: ui
- name: Install
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: mariadb
TYPE: ui
- name: Verify yarn.lock
run: |
cd ~/frappe-bench/apps/frappe
git diff --exit-code yarn.lock
- name: Instrument Source Code
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: cd ~/frappe-bench/apps/frappe/ && npx nyc instrument -x 'frappe/public/dist/**' -x 'frappe/public/js/lib/**' -x '**/*.bundle.js' --compact=false --in-place frappe
- name: Build
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: cd ~/frappe-bench/ && bench build --apps frappe
- name: Site Setup
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: cd ~/frappe-bench/ && bench --site test_site execute frappe.utils.install.complete_setup_wizard
run: |
cd ~/frappe-bench/
bench --site test_site execute frappe.utils.install.complete_setup_wizard
bench --site test_site execute frappe.tests.ui_test_helpers.create_test_user
- name: UI Tests
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests frappe --with-coverage --headless --parallel --ci-build-id $GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT
env:
CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb
- name: Stop server
if: ${{ steps.check-build.outputs.build-server == 'strawberry' }}
- name: Stop server and wait for coverage file
run: |
ps -ef | grep "frappe serve" | awk '{print $2}' | xargs kill -s SIGINT 2> /dev/null || true
ps -ef | grep "[f]rappe serve" | awk '{print $2}' | xargs kill -s SIGINT
sleep 5
( tail -f /home/runner/frappe-bench/sites/coverage.xml & ) | grep -q "\/coverage"
- name: Check If Coverage Report Exists
id: check_coverage
uses: andstor/file-existence-action@v1
- name: Upload JS coverage data
uses: actions/upload-artifact@v3
with:
files: "/home/runner/frappe-bench/apps/frappe/.cypress-coverage/clover.xml"
name: coverage-js-${{ matrix.container }}
path: /home/runner/frappe-bench/apps/frappe/.cypress-coverage/clover.xml
- name: Upload Coverage Data
if: ${{ steps.check-build.outputs.build == 'strawberry' && steps.check_coverage.outputs.files_exists == 'true' }}
- name: Upload python coverage data
uses: actions/upload-artifact@v3
with:
name: coverage-py-${{ matrix.container }}
path: /home/runner/frappe-bench/sites/coverage.xml
- name: Show bench output
if: ${{ always() }}
run: cat ~/frappe-bench/bench_start.log || true
coverage:
name: Coverage Wrap Up
needs: [test, checkrun]
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v3
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Upload python coverage data
uses: codecov/codecov-action@v3
with:
name: UIBackend
fail_ci_if_error: true
verbose: true
files: ./coverage-py-1/coverage.xml,./coverage-py-2/coverage.xml,./coverage-py-3/coverage.xml
flags: server-ui
- name: Upload JS coverage data
uses: codecov/codecov-action@v3
with:
name: Cypress
fail_ci_if_error: true
directory: /home/runner/frappe-bench/apps/frappe/.cypress-coverage/
files: ./coverage-js-1/clover.xml,./coverage-js-2/clover.xml,./coverage-js-3/clover.xml
verbose: true
flags: ui-tests
- name: Upload Server Coverage Data
if: ${{ steps.check-build.outputs.build-server == 'strawberry' }}
uses: codecov/codecov-action@v3
with:
name: MariaDB
fail_ci_if_error: true
files: /home/runner/frappe-bench/sites/coverage.xml
verbose: true
flags: server

View file

@ -4,11 +4,13 @@ pull_request_rules:
- and:
- and:
- author!=surajshetty3416
- author!=gavindsouza
- author!=deepeshgarg007
- author!=ankush
- author!=frappe-pr-bot
- author!=mergify[bot]
- or:
- base=version-15
- base=version-14
- base=version-13
- base=version-12
actions:
@ -20,15 +22,6 @@ pull_request_rules:
- name: Automatic merge on CI success and review
conditions:
- status-success=Sider
- status-success=Python Unit Tests (MariaDB) (1)
- status-success=Python Unit Tests (MariaDB) (2)
- status-success=Python Unit Tests (Postgres) (1)
- status-success=Python Unit Tests (Postgres) (2)
- status-success=UI Tests (Cypress) (1)
- status-success=UI Tests (Cypress) (2)
- status-success=UI Tests (Cypress) (3)
- status-success=security/snyk (frappe)
- label!=dont-merge
- label!=squash
- "#approved-reviews-by>=1"
@ -37,15 +30,6 @@ pull_request_rules:
method: merge
- name: Automatic squash on CI success and review
conditions:
- status-success=Sider
- status-success=Python Unit Tests (MariaDB) (1)
- status-success=Python Unit Tests (MariaDB) (2)
- status-success=Python Unit Tests (Postgres) (1)
- status-success=Python Unit Tests (Postgres) (2)
- status-success=UI Tests (Cypress) (1)
- status-success=UI Tests (Cypress) (2)
- status-success=UI Tests (Cypress) (3)
- status-success=security/snyk (frappe)
- label!=dont-merge
- label=squash
- "#approved-reviews-by>=1"
@ -77,22 +61,13 @@ pull_request_rules:
assignees:
- "{{ author }}"
- name: backport to version-13-pre-release
- name: backport to version-14-hotfix
conditions:
- label="backport version-13-pre-release"
- label="backport version-14-hotfix"
actions:
backport:
branches:
- version-13-pre-release
- version-14-hotfix
assignees:
- "{{ author }}"
- name: backport to version-12-hotfix
conditions:
- label="backport version-12-hotfix"
actions:
backport:
branches:
- version-12-hotfix
assignees:
- "{{ author }}"

View file

@ -26,23 +26,38 @@ repos:
- id: pyupgrade
args: ['--py310-plus']
- repo: https://github.com/adityahase/black
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
- repo: https://github.com/frappe/black
rev: 951ccf4d5bb0d692b457a5ebc4215d755618eb68
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/timothycrosley/isort
rev: 5.9.1
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
- id: prettier
types_or: [javascript]
# Ignore any files that might contain jinja / bundles
exclude: |
(?x)^(
frappe/public/dist/.*|
.*node_modules.*|
.*boilerplate.*|
frappe/www/website_script.js|
frappe/templates/includes/.*|
frappe/public/js/lib/.*
)$
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
additional_dependencies: ['flake8-bugbear',]
args: ['--config', '.github/helper/flake8.conf']
ci:
autoupdate_schedule: weekly

View file

@ -13,9 +13,9 @@
[
"@semantic-release/git", {
"assets": ["frappe/__init__.py"],
"message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}"
"message": "chore(release): Bumped to Version ${nextRelease.version}"
}
],
"@semantic-release/github"
]
}
}

101
.snyk
View file

@ -1,101 +0,0 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.19.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
SNYK-JS-AWESOMPLETE-174474:
- awesomplete:
reason: No patch available
expires: '2019-06-11T14:12:04.995Z'
'npm:mem:20180117':
- showdown > yargs > os-locale > mem:
reason: No patch available
expires: '2019-06-11T14:12:04.995Z'
SNYK-PYTHON-PYYAML-550022:
- '*':
reason: Project is not directly dependant on the package
expires: 2021-04-01T18:02:21.256Z
# patches apply the minimum changes required to fix a vulnerability
patch:
'npm:extend:20180424':
- superagent > extend:
patched: '2019-05-09T10:14:19.246Z'
SNYK-JS-LODASH-450202:
- frappe-datatable > lodash:
patched: '2020-01-31T01:33:09.889Z'
SNYK-JS-LODASH-567746:
- frappe-datatable > lodash:
patched: '2020-04-30T23:02:32.330Z'
- quagga > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > lodash:
patched: '2020-04-30T23:02:32.330Z'
- tailwindcss > lodash:
patched: '2020-04-30T23:02:32.330Z'
- '@tailwindcss/ui > @tailwindcss/custom-forms > lodash':
patched: '2020-04-30T23:02:32.330Z'
- snyk > @snyk/dep-graph > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > inquirer > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > snyk-config > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > snyk-mvn-plugin > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > snyk-nodejs-lockfile-parser > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > snyk-nuget-plugin > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > @snyk/dep-graph > graphlib > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > snyk-go-plugin > graphlib > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > snyk-nodejs-lockfile-parser > graphlib > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/dep-graph > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > snyk-nuget-plugin > dotnet-deps-parser > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > snyk-php-plugin > @snyk/composer-lockfile-parser > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/dep-graph > graphlib > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/ruby-semver > lodash:
patched: '2020-04-30T23:02:32.330Z'
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/dep-graph > graphlib > lodash:
patched: '2020-04-30T23:02:32.330Z'
- quill-image-resize > lodash:
patched: '2020-08-24T23:06:37.710Z'
- node-sass > lodash:
patched: '2020-09-15T23:06:41.931Z'
- node-sass > sass-graph > lodash:
patched: '2020-09-15T23:06:41.931Z'
- node-sass > gaze > globule > lodash:
patched: '2020-09-15T23:06:41.931Z'
- snyk > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/dep-graph > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-cpp-plugin > @snyk/dep-graph > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-go-plugin > @snyk/dep-graph > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-gradle-plugin > @snyk/dep-graph > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-docker-plugin > snyk-nodejs-lockfile-parser > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-mvn-plugin > @snyk/java-call-graph-builder > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/dep-graph > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-php-plugin > @snyk/cli-interface > @snyk/dep-graph > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-gradle-plugin > @snyk/cli-interface > @snyk/dep-graph > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-mvn-plugin > @snyk/cli-interface > @snyk/dep-graph > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > @snyk/dep-graph > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-nodejs-lockfile-parser > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'
- snyk > snyk-go-plugin > graphlib > lodash:
patched: '2020-09-16T23:06:38.881Z'

View file

@ -1,9 +0,0 @@
{
"extends": ["stylelint-config-recommended"],
"plugins": ["stylelint-scss"],
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"no-descending-specificity": null
}
}

View file

@ -6,13 +6,7 @@
* @frappe/frappe-review-team
templates/ @surajshetty3416
www/ @surajshetty3416
patches/ @surajshetty3416 @gavindsouza
event_streaming/ @ruchamahabal
patches/ @surajshetty3416
data_import* @netchampfaris
core/ @surajshetty3416
database @gavindsouza
model @gavindsouza
pyproject.toml @gavindsouza
query_builder/ @gavindsouza
commands/ @gavindsouza
workspace @shariquerik

View file

@ -14,25 +14,28 @@
</div>
<div align="center">
<a href="https://github.com/frappe/frappe/actions/workflows/server-mariadb-tests.yml">
<img src="https://github.com/frappe/frappe/actions/workflows/server-mariadb-tests.yml/badge.svg">
<a target="_blank" href="#LICENSE" title="License: MIT">
<img src="https://img.shields.io/badge/License-MIT-success.svg">
</a>
<a target="_blank" href="https://www.python.org/downloads/" title="Python version">
<img src="https://img.shields.io/badge/python-%3E=_3.10-success.svg">
</a>
<a href="https://frappeframework.com/docs">
<img src="https://img.shields.io/badge/docs-%F0%9F%93%96-success.svg"/>
</a>
<a href="https://github.com/frappe/frappe/actions/workflows/server-tests.yml">
<img src="https://github.com/frappe/frappe/actions/workflows/server-tests.yml/badge.svg">
</a>
<a href="https://github.com/frappe/frappe/actions/workflows/ui-tests.yml">
<img src="https://github.com/frappe/frappe/actions/workflows/ui-tests.yml/badge.svg?branch=develop">
</a>
<a href='https://frappeframework.com/docs'>
<img src='https://img.shields.io/badge/docs-📖-7575FF.svg?style=flat-square'/>
</a>
<a href='https://www.codetriage.com/frappe/frappe'>
<img src='https://www.codetriage.com/frappe/frappe/badges/users.svg'>
</a>
<a href="https://codecov.io/gh/frappe/frappe">
<img src="https://codecov.io/gh/frappe/frappe/branch/develop/graph/badge.svg?token=XoTa679hIj"/>
</a>
</div>
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com)
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com).
<div align="center" style="max-height: 40px;">
<a href="https://frappecloud.com/frappe/signup">
@ -72,3 +75,5 @@ Full-stack web application framework that uses Python and MariaDB on the server
## License
This repository has been released under the [MIT License](LICENSE).
By contributing to Frappe, you agree that your contributions will be licensed under its MIT License.

View file

@ -1,7 +1,7 @@
# Security Policy
The Frappe team and community take security issues in the Frappe Framework seriously. To report a security issue, fill out the form at [https://erpnext.com/security/report](https://erpnext.com/security/report).
The Frappe team and community take security issues in the Frappe Framework seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).
You can help us make Frappe and consequently all Frappe dependent apps like [ERPNext](https://erpnext.com) more secure by following the [Reporting guidelines](https://erpnext.com/security).
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.

View file

@ -11,6 +11,7 @@ The following 3rd-party software packages may be used by or distributed with <ht
- Leaflet.Locate - (c) 2016 Dominik Moritz
- Leaflet.draw - (c) 2012-2017, Jacob Toye, Jon West, Smartrak
- Leaflet.EasyButton - MIT License, (C) 2014 Daniel Montague
- Identicons - MIT License, (C) 2015, <https://github.com/evuez/identicons>
### Icon Fonts

View file

@ -1 +0,0 @@
skips: ['E0203', 'B605', 'B404', 'B603', 'B607']

View file

@ -2,17 +2,16 @@ codecov:
require_ci_to_pass: yes
coverage:
range: 60..90
status:
project:
default: false
server:
default:
target: auto
threshold: 0.5%
flags:
- server
patch:
default: false
server:
default:
target: 85%
threshold: 0%
only_pulls: true
@ -23,13 +22,39 @@ coverage:
comment:
layout: "diff, flags"
require_changes: true
show_critical_paths: true
flags:
server:
paths:
- ".*\\.py"
- "**/*.py"
carryforward: true
ui-tests:
paths:
- ".*\\.js"
- "**/*.js"
carryforward: true
server-ui:
paths:
- "**/*.py"
carryforward: true
profiling:
critical_files_paths:
- /frappe/api.py
- /frappe/app.py
- /frappe/auth.py
- /frappe/boot.py
- /frappe/client.py
- /frappe/handler.py
- /frappe/migrate.py
- /frappe/sessions.py
- /frappe/utils/*
- /frappe/desk/reportview.py
- /frappe/desk/form/*
- /frappe/model/*
- /frappe/core/doctype/doctype/*
- /frappe/core/doctype/data_import/*
- /frappe/core/doctype/user/*
- /frappe/core/doctype/user/*
- /frappe/query_builder/*
- /frappe/database/*

25
commitlint.config.js Normal file
View file

@ -0,0 +1,25 @@
module.exports = {
parserPreset: "conventional-changelog-conventionalcommits",
rules: {
"subject-empty": [2, "never"],
"type-case": [2, "always", "lower-case"],
"type-empty": [2, "never"],
"type-enum": [
2,
"always",
[
"build",
"chore",
"ci",
"docs",
"feat",
"fix",
"perf",
"refactor",
"revert",
"style",
"test",
],
],
},
};

24
cypress.config.js Normal file
View file

@ -0,0 +1,24 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
projectId: "92odwv",
adminPassword: "admin",
testUser: "frappe@example.com",
defaultCommandTimeout: 20000,
pageLoadTimeout: 15000,
video: true,
videoUploadOnPasses: false,
retries: {
runMode: 2,
openMode: 2,
},
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require("./cypress/plugins/index.js")(on, config);
},
baseUrl: "http://test_site_ui:8000",
specPattern: ["./cypress/integration/*.js", "**/ui_test_*.js"],
},
});

View file

@ -1,15 +0,0 @@
{
"baseUrl": "http://test_site_ui:8000",
"projectId": "92odwv",
"adminPassword": "admin",
"defaultCommandTimeout": 20000,
"pageLoadTimeout": 15000,
"video": true,
"videoUploadOnPasses": false,
"retries": {
"runMode": 2,
"openMode": 2
},
"integrationFolder": ".",
"testFiles": ["cypress/integration/*.js", "**/ui_test_*.js"]
}

View file

@ -13,8 +13,8 @@ export default {
fieldtype: "Data",
in_list_view: 1,
label: "Title",
unique: 1
}
unique: 1,
},
],
links: [],
istable: 1,
@ -24,7 +24,7 @@ export default {
naming_rule: "By fieldname",
owner: "Administrator",
permissions: [],
sort_field: 'modified',
sort_order: 'ASC',
track_changes: 1
};
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -12,38 +12,38 @@ export default {
fieldname: "data",
fieldtype: "Data",
in_list_view: 1,
label: "Data"
label: "Data",
},
{
fieldname: "barcode",
fieldtype: "Barcode",
in_list_view: 1,
label: "Barcode"
label: "Barcode",
},
{
fieldname: "check",
fieldtype: "Check",
in_list_view: 1,
label: "Check"
label: "Check",
},
{
fieldname: "rating",
fieldtype: "Rating",
in_list_view: 1,
label: "Rating"
label: "Rating",
},
{
fieldname: "duration",
fieldtype: "Duration",
in_list_view: 1,
label: "Duration"
label: "Duration",
},
{
fieldname: "date",
fieldtype: "Date",
in_list_view: 1,
label: "Date"
}
label: "Date",
},
],
links: [],
istable: 1,
@ -53,7 +53,7 @@ export default {
naming_rule: "By fieldname",
owner: "Administrator",
permissions: [],
sort_field: 'modified',
sort_order: 'ASC',
track_changes: 1
};
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -1,37 +1,37 @@
export default {
name: 'Custom Submittable DocType',
name: "Custom Submittable DocType",
custom: 1,
actions: [],
is_submittable: 1,
creation: '2019-12-10 06:29:07.215072',
doctype: 'DocType',
creation: "2019-12-10 06:29:07.215072",
doctype: "DocType",
editable_grid: 1,
engine: 'InnoDB',
engine: "InnoDB",
fields: [
{
fieldname: 'enabled',
fieldtype: 'Check',
label: 'Enabled',
fieldname: "enabled",
fieldtype: "Check",
label: "Enabled",
allow_on_submit: 1,
reqd: 1
reqd: 1,
},
{
fieldname: 'title',
fieldtype: 'Data',
label: 'title',
reqd: 1
fieldname: "title",
fieldtype: "Data",
label: "title",
reqd: 1,
},
{
fieldname: 'description',
fieldtype: 'Text Editor',
label: 'Description'
}
fieldname: "description",
fieldtype: "Text Editor",
label: "Description",
},
],
links: [],
modified: '2019-12-10 14:40:53.127615',
modified_by: 'Administrator',
module: 'Custom',
owner: 'Administrator',
modified: "2019-12-10 14:40:53.127615",
modified_by: "Administrator",
module: "Custom",
owner: "Administrator",
permissions: [
{
create: 1,
@ -39,15 +39,15 @@ export default {
email: 1,
print: 1,
read: 1,
role: 'System Manager',
role: "System Manager",
share: 1,
write: 1,
submit: 1,
cancel: 1
}
cancel: 1,
},
],
quick_entry: 1,
sort_field: 'modified',
sort_order: 'ASC',
track_changes: 1
};
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -1,51 +1,51 @@
export default {
name: 'Validation Test',
name: "Validation Test",
custom: 1,
actions: [],
creation: '2019-03-15 06:29:07.215072',
doctype: 'DocType',
creation: "2019-03-15 06:29:07.215072",
doctype: "DocType",
editable_grid: 1,
engine: 'InnoDB',
engine: "InnoDB",
fields: [
{
fieldname: 'email',
fieldtype: 'Data',
label: 'Email',
options: 'Email'
fieldname: "email",
fieldtype: "Data",
label: "Email",
options: "Email",
},
{
fieldname: 'URL',
fieldtype: 'Data',
label: 'URL',
options: 'URL'
fieldname: "URL",
fieldtype: "Data",
label: "URL",
options: "URL",
},
{
fieldname: 'Phone',
fieldtype: 'Data',
label: 'Phone',
options: 'Phone'
fieldname: "Phone",
fieldtype: "Data",
label: "Phone",
options: "Phone",
},
{
fieldname: 'person_name',
fieldtype: 'Data',
label: 'Person Name',
options: 'Name'
fieldname: "person_name",
fieldtype: "Data",
label: "Person Name",
options: "Name",
},
{
fieldname: 'read_only_url',
fieldtype: 'Data',
label: 'Read Only URL',
options: 'URL',
read_only: '1',
default: 'https://frappe.io'
}
fieldname: "read_only_url",
fieldtype: "Data",
label: "Read Only URL",
options: "URL",
read_only: "1",
default: "https://frappe.io",
},
],
issingle: 1,
links: [],
modified: '2021-04-19 14:40:53.127615',
modified_by: 'Administrator',
module: 'Custom',
owner: 'Administrator',
modified: "2021-04-19 14:40:53.127615",
modified_by: "Administrator",
module: "Custom",
owner: "Administrator",
permissions: [
{
create: 1,
@ -53,13 +53,13 @@ export default {
email: 1,
print: 1,
read: 1,
role: 'System Manager',
role: "System Manager",
share: 1,
write: 1
}
write: 1,
},
],
quick_entry: 1,
sort_field: 'modified',
sort_order: 'ASC',
track_changes: 1
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -1,34 +1,34 @@
export default {
name: 'DateTime Test',
name: "DateTime Test",
custom: 1,
actions: [],
creation: '2019-03-15 06:29:07.215072',
doctype: 'DocType',
creation: "2019-03-15 06:29:07.215072",
doctype: "DocType",
editable_grid: 1,
engine: 'InnoDB',
engine: "InnoDB",
fields: [
{
fieldname: 'date',
fieldtype: 'Date',
label: 'Date'
fieldname: "date",
fieldtype: "Date",
label: "Date",
},
{
fieldname: 'time',
fieldtype: 'Time',
label: 'Time'
fieldname: "time",
fieldtype: "Time",
label: "Time",
},
{
fieldname: 'datetime',
fieldtype: 'Datetime',
label: 'Datetime'
}
fieldname: "datetime",
fieldtype: "Datetime",
label: "Datetime",
},
],
issingle: 1,
links: [],
modified: '2019-12-09 14:40:53.127615',
modified_by: 'Administrator',
module: 'Custom',
owner: 'Administrator',
modified: "2019-12-09 14:40:53.127615",
modified_by: "Administrator",
module: "Custom",
owner: "Administrator",
permissions: [
{
create: 1,
@ -36,13 +36,13 @@ export default {
email: 1,
print: 1,
read: 1,
role: 'System Manager',
role: "System Manager",
share: 1,
write: 1
}
write: 1,
},
],
quick_entry: 1,
sort_field: 'modified',
sort_order: 'ASC',
track_changes: 1
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -10,18 +10,18 @@ export default {
engine: "InnoDB",
fields: [
{
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"unique": 1
}
fieldname: "title",
fieldtype: "Data",
label: "Title",
unique: 1,
},
],
links: [
{
"group": "Child Doctype",
"link_doctype": "Doctype With Child Table",
"link_fieldname": "title"
}
group: "Child Doctype",
link_doctype: "Doctype With Child Table",
link_fieldname: "title",
},
],
modified: "2022-02-10 12:03:12.603763",
modified_by: "Administrator",
@ -34,12 +34,12 @@ export default {
email: 1,
print: 1,
read: 1,
role: 'System Manager',
role: "System Manager",
share: 1,
write: 1
}
write: 1,
},
],
sort_field: 'modified',
sort_order: 'ASC',
track_changes: 1
};
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -12,21 +12,21 @@ export default {
fieldname: "title",
fieldtype: "Data",
label: "Title",
unique: 1
unique: 1,
},
{
fieldname: "child_table",
fieldtype: "Table",
label: "Child Table",
options: "Child Table Doctype",
reqd: 1
reqd: 1,
},
{
fieldname: "child_table_1",
fieldtype: "Table",
label: "Child Table 1",
options: "Child Table Doctype 1"
}
options: "Child Table Doctype 1",
},
],
links: [],
modified: "2022-02-10 12:03:12.603763",
@ -41,12 +41,12 @@ export default {
email: 1,
print: 1,
read: 1,
role: 'System Manager',
role: "System Manager",
share: 1,
write: 1
}
write: 1,
},
],
sort_field: 'modified',
sort_order: 'ASC',
track_changes: 1
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -4,29 +4,28 @@ export default {
custom: 1,
is_submittable: 1,
autoname: "field:title",
creation: '2022-03-30 06:29:07.215072',
doctype: 'DocType',
engine: 'InnoDB',
creation: "2022-03-30 06:29:07.215072",
doctype: "DocType",
engine: "InnoDB",
fields: [
{
fieldname: 'title',
fieldtype: 'Data',
label: 'title',
fieldname: "title",
fieldtype: "Data",
label: "title",
unique: 1,
},
{
fieldname: 'phone',
fieldtype: 'Phone',
label: 'Phone'
}
fieldname: "phone",
fieldtype: "Phone",
label: "Phone",
},
],
links: [],
modified: '2019-03-30 14:40:53.127615',
modified_by: 'Administrator',
modified: "2019-03-30 14:40:53.127615",
modified_by: "Administrator",
naming_rule: "By fieldname",
module: 'Custom',
owner: 'Administrator',
module: "Custom",
owner: "Administrator",
permissions: [
{
create: 1,
@ -34,14 +33,14 @@ export default {
email: 1,
print: 1,
read: 1,
role: 'System Manager',
role: "System Manager",
share: 1,
write: 1,
submit: 1,
cancel: 1
}
cancel: 1,
},
],
sort_field: 'modified',
sort_order: 'ASC',
track_changes: 1
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -1,39 +1,39 @@
export default {
name: 'Form With Tab Break',
name: "Form With Tab Break",
custom: 1,
actions: [],
doctype: 'DocType',
engine: 'InnoDB',
doctype: "DocType",
engine: "InnoDB",
fields: [
{
fieldname: 'username',
fieldtype: 'Data',
label: 'Name',
options: 'Name'
fieldname: "username",
fieldtype: "Data",
label: "Name",
options: "Name",
},
{
fieldname: 'tab',
fieldtype: 'Tab Break',
label: 'Tab 2',
fieldname: "tab",
fieldtype: "Tab Break",
label: "Tab 2",
},
{
fieldname: 'Phone',
fieldtype: 'Data',
label: 'Phone',
options: 'Phone',
reqd: 1
fieldname: "Phone",
fieldtype: "Data",
label: "Phone",
options: "Phone",
reqd: 1,
},
],
links: [
{
"group": "Profile",
"link_doctype": "Contact",
"link_fieldname": "user"
group: "Profile",
link_doctype: "Contact",
link_fieldname: "user",
},
],
modified_by: 'Administrator',
module: 'Custom',
owner: 'Administrator',
modified_by: "Administrator",
module: "Custom",
owner: "Administrator",
permissions: [
{
create: 1,
@ -41,14 +41,14 @@ export default {
email: 1,
print: 1,
read: 1,
role: 'System Manager',
role: "System Manager",
share: 1,
write: 1
}
write: 1,
},
],
quick_entry: 1,
autoname: "format: Test-{####}",
sort_field: 'modified',
sort_order: 'ASC',
track_changes: 1
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -0,0 +1,65 @@
export default {
name: "Form Builder Doctype",
custom: 1,
actions: [],
doctype: "DocType",
engine: "InnoDB",
fields: [
{
fieldname: "data3",
fieldtype: "Data",
label: "Data 3",
},
{
fieldname: "tab",
fieldtype: "Tab Break",
label: "Tab 2",
},
{
fieldname: "data",
fieldtype: "Data",
label: "Data",
},
{
fieldname: "check",
fieldtype: "Check",
label: "Check",
},
{
fieldname: "column_1",
fieldtype: "Column Break",
},
{
fieldname: "data1",
fieldtype: "Data",
label: "Data 1",
},
{
fieldname: "section_1",
fieldtype: "Section Break",
},
{
fieldname: "data2",
fieldtype: "Data",
label: "Data 2",
},
],
modified_by: "Administrator",
module: "Custom",
owner: "Administrator",
permissions: [
{
create: 1,
delete: 1,
email: 1,
print: 1,
read: 1,
role: "System Manager",
share: 1,
write: 1,
},
],
sort_field: "modified",
sort_order: "ASC",
track_changes: 1,
};

View file

@ -1,42 +1,43 @@
context('API Resources', () => {
context("API Resources", () => {
before(() => {
cy.visit('/login');
cy.visit("/login");
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
it('Creates two Comments', () => {
cy.insert_doc('Comment', { comment_type: 'Comment', content: "hello" });
cy.insert_doc('Comment', { comment_type: 'Comment', content: "world" });
it("Creates two Comments", () => {
cy.insert_doc("Comment", { comment_type: "Comment", content: "hello" });
cy.insert_doc("Comment", { comment_type: "Comment", content: "world" });
});
it('Lists the Comments', () => {
cy.get_list('Comment')
.its('data')
.then(data => expect(data.length).to.be.at.least(2));
it("Lists the Comments", () => {
cy.get_list("Comment")
.its("data")
.then((data) => expect(data.length).to.be.at.least(2));
cy.get_list('Comment', ['name', 'content'], [['content', '=', 'hello']])
.then(body => {
expect(body).to.have.property('data');
expect(body.data).to.have.lengthOf(1);
expect(body.data[0]).to.have.property('content');
expect(body.data[0]).to.have.property('name');
});
cy.get_list("Comment", ["name", "content"], [["content", "=", "hello"]]).then((body) => {
expect(body).to.have.property("data");
expect(body.data).to.have.lengthOf(1);
expect(body.data[0]).to.have.property("content");
expect(body.data[0]).to.have.property("name");
});
});
it('Gets each Comment', () => {
cy.get_list('Comment').then(body => body.data.forEach(comment => {
cy.get_doc('Comment', comment.name);
}));
it("Gets each Comment", () => {
cy.get_list("Comment").then((body) =>
body.data.forEach((comment) => {
cy.get_doc("Comment", comment.name);
})
);
});
it('Removes the Comments', () => {
cy.get_list('Comment').then(body => {
it("Removes the Comments", () => {
cy.get_list("Comment").then((body) => {
let comment_names = [];
body.data.map(comment => comment_names.push(comment.name));
body.data.map((comment) => comment_names.push(comment.name));
comment_names = [...new Set(comment_names)]; // remove duplicates
comment_names.forEach((comment_name) => {
cy.remove_doc('Comment', comment_name);
cy.remove_doc("Comment", comment_name);
});
});
});

View file

@ -0,0 +1,16 @@
context("Assignment Rule", () => {
before(() => {
cy.login();
});
it("Custom grid buttons work", () => {
cy.new_form("Assignment Rule");
cy.findByRole("button", { name: "All Days" }).should("be.visible").click();
cy.wait(2000);
cy.window()
.its("cur_frm")
.then((frm) => {
expect(frm.doc.assignment_days.length).to.equal(7);
});
});
});

View file

@ -1,48 +1,57 @@
context('Awesome Bar', () => {
context("Awesome Bar", () => {
before(() => {
cy.visit('/login');
cy.visit("/login");
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
beforeEach(() => {
cy.get('.navbar .navbar-home').click();
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').clear();
cy.get(".navbar .navbar-home").click();
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").clear();
});
it('navigates to doctype list', () => {
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('todo', { delay: 700 });
cy.get('.awesomplete').findByRole('listbox').should('be.visible');
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('{enter}', { delay: 700 });
it("navigates to doctype list", () => {
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type("todo", {
delay: 700,
});
cy.get(".awesomplete").findByRole("listbox").should("be.visible");
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type("{enter}", {
delay: 700,
});
cy.get('.title-text').should('contain', 'To Do');
cy.get(".title-text").should("contain", "To Do");
cy.location('pathname').should('eq', '/app/todo');
cy.location("pathname").should("eq", "/app/todo");
});
it('find text in doctype list', () => {
cy.findByPlaceholderText('Search or type a command (Ctrl + G)')
.type('test in todo{enter}', { delay: 700 });
it("find text in doctype list", () => {
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type(
"test in todo{enter}",
{ delay: 700 }
);
cy.get('.title-text').should('contain', 'To Do');
cy.get(".title-text").should("contain", "To Do");
cy.findByPlaceholderText('ID')
.should('have.value', '%test%');
cy.findByPlaceholderText("ID").should("have.value", "%test%");
cy.clear_filters();
});
it('navigates to new form', () => {
cy.findByPlaceholderText('Search or type a command (Ctrl + G)')
.type('new blog post{enter}', { delay: 700 });
it("navigates to new form", () => {
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type(
"new blog post{enter}",
{ delay: 700 }
);
cy.get('.title-text:visible').should('have.text', 'New Blog Post');
cy.get(".title-text:visible").should("have.text", "New Blog Post");
});
it('calculates math expressions', () => {
cy.findByPlaceholderText('Search or type a command (Ctrl + G)')
.type('55 + 32{downarrow}{enter}', { delay: 700 });
it("calculates math expressions", () => {
cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type(
"55 + 32{downarrow}{enter}",
{ delay: 700 }
);
cy.get('.modal-title').should('contain', 'Result');
cy.get('.msgprint').should('contain', '55 + 32 = 87');
cy.get(".modal-title").should("contain", "Result");
cy.get(".msgprint").should("contain", "55 + 32 = 87");
});
});

View file

@ -1,90 +1,95 @@
context('Attach Control', () => {
context("Attach Control", () => {
before(() => {
cy.login();
cy.visit('/app/doctype');
return cy.window().its('frappe').then(frappe => {
return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
name: 'Test Attach Control',
fields: [
{
"label": "Attach File or Image",
"fieldname": "attach",
"fieldtype": "Attach",
"in_list_view": 1,
},
]
cy.visit("/app/doctype");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.xcall("frappe.tests.ui_test_helpers.create_doctype", {
name: "Test Attach Control",
fields: [
{
label: "Attach File or Image",
fieldname: "attach",
fieldtype: "Attach",
in_list_view: 1,
},
],
});
});
});
});
it('Checking functionality for "Link" button in the "Attach" fieldtype', () => {
//Navigating to the new form for the newly created doctype
cy.new_form('Test Attach Control');
cy.new_form("Test Attach Control");
//Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype
cy.findByRole('button', {name: 'Attach'}).click();
cy.findByRole("button", { name: "Attach" }).click();
//Clicking on "Link" button to attach a file using the "Link" button
cy.findByRole('button', {name: 'Link'}).click();
cy.findByPlaceholderText('Attach a web link').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
cy.findByRole("button", { name: "Link" }).click();
cy.findByPlaceholderText("Attach a web link").type(
"https://wallpaperplay.com/walls/full/8/2/b/72402.jpg"
);
//Clicking on the Upload button to upload the file
cy.intercept("POST", "/api/method/upload_file").as("upload_image");
cy.get('.modal-footer').findByRole("button", {name: "Upload"}).click({delay: 500});
cy.get(".modal-footer").findByRole("button", { name: "Upload" }).click({ delay: 500 });
cy.wait("@upload_image");
cy.findByRole('button', {name: 'Save'}).click();
cy.findByRole("button", { name: "Save" }).click();
//Checking if the URL of the attached image is getting displayed in the field of the newly created doctype
cy.get('.attached-file > .ellipsis > .attached-file-link')
.should('have.attr', 'href')
.and('equal', 'https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
cy.get(".attached-file > .ellipsis > .attached-file-link")
.should("have.attr", "href")
.and("equal", "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg");
//Clicking on the "Clear" button
cy.get('[data-action="clear_attachment"]').click();
//Checking if clicking on the clear button clears the field of the doctype form and again displays the attach button
cy.get('.control-input > .btn-sm').should('contain', 'Attach');
cy.get(".control-input > .btn-sm").should("contain", "Attach");
//Deleting the doc
cy.go_to_list('Test Attach Control');
cy.get('.list-row-checkbox').eq(0).click();
cy.get('.actions-btn-group > .btn').contains('Actions').click();
cy.go_to_list("Test Attach Control");
cy.get(".list-row-checkbox").eq(0).click();
cy.get(".actions-btn-group > .btn").contains("Actions").click();
cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click();
cy.click_modal_primary_button('Yes');
cy.click_modal_primary_button("Yes");
});
it('Checking functionality for "Library" button in the "Attach" fieldtype', () => {
//Navigating to the new form for the newly created doctype
cy.new_form('Test Attach Control');
cy.new_form("Test Attach Control");
//Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype
cy.findByRole('button', {name: 'Attach'}).click();
cy.findByRole("button", { name: "Attach" }).click();
//Clicking on "Library" button to attach a file using the "Library" button
cy.findByRole('button', {name: 'Library'}).click();
cy.contains('72402.jpg').click();
cy.findByRole("button", { name: "Library" }).click();
cy.contains("72402.jpg").click();
//Clicking on the Upload button to upload the file
cy.intercept("POST", "/api/method/upload_file").as("upload_image");
cy.get('.modal-footer').findByRole("button", {name: "Upload"}).click({delay: 500});
cy.get(".modal-footer").findByRole("button", { name: "Upload" }).click({ delay: 500 });
cy.wait("@upload_image");
cy.findByRole('button', {name: 'Save'}).click();
cy.findByRole("button", { name: "Save" }).click();
//Checking if the URL of the attached image is getting displayed in the field of the newly created doctype
cy.get('.attached-file > .ellipsis > .attached-file-link')
.should('have.attr', 'href')
.and('equal', 'https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
cy.get(".attached-file > .ellipsis > .attached-file-link")
.should("have.attr", "href")
.and("equal", "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg");
//Clicking on the "Clear" button
cy.get('[data-action="clear_attachment"]').click();
//Checking if clicking on the clear button clears the field of the doctype form and again displays the attach button
cy.get('.control-input > .btn-sm').should('contain', 'Attach');
cy.get(".control-input > .btn-sm").should("contain", "Attach");
//Deleting the doc
cy.go_to_list('Test Attach Control');
cy.get('.list-row-checkbox').eq(0).click();
cy.get('.actions-btn-group > .btn').contains('Actions').click();
cy.go_to_list("Test Attach Control");
cy.get(".list-row-checkbox").eq(0).click();
cy.get(".actions-btn-group > .btn").contains("Actions").click();
cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click();
cy.click_modal_primary_button('Yes');
cy.click_modal_primary_button("Yes");
});
});
});

View file

@ -1,57 +1,64 @@
context('Control Autocomplete', () => {
context("Control Autocomplete", () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
function get_dialog_with_autocomplete(options) {
cy.visit('/app/website');
cy.visit("/app/website");
return cy.dialog({
title: 'Autocomplete',
title: "Autocomplete",
fields: [
{
'label': 'Select an option',
'fieldname': 'autocomplete',
'fieldtype': 'Autocomplete',
'options': options || ['Option 1', 'Option 2', 'Option 3'],
}
]
label: "Select an option",
fieldname: "autocomplete",
fieldtype: "Autocomplete",
options: options || ["Option 1", "Option 2", "Option 3"],
},
],
});
}
it('should set the valid value', () => {
get_dialog_with_autocomplete().as('dialog');
it("should set the valid value", () => {
get_dialog_with_autocomplete().as("dialog");
cy.get('.frappe-control[data-fieldname=autocomplete] input').focus().as('input');
cy.get(".frappe-control[data-fieldname=autocomplete] input").focus().as("input");
cy.wait(1000);
cy.get('@input').type('2', { delay: 300 });
cy.get('.frappe-control[data-fieldname=autocomplete]').findByRole('listbox').should('be.visible');
cy.get('.frappe-control[data-fieldname=autocomplete] input').type('{enter}', { delay: 300 });
cy.get('.frappe-control[data-fieldname=autocomplete] input').blur();
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('autocomplete');
expect(value).to.eq('Option 2');
cy.get("@input").type("2", { delay: 300 });
cy.get(".frappe-control[data-fieldname=autocomplete]")
.findByRole("listbox")
.should("be.visible");
cy.get(".frappe-control[data-fieldname=autocomplete] input").type("{enter}", {
delay: 300,
});
cy.get(".frappe-control[data-fieldname=autocomplete] input").blur();
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("autocomplete");
expect(value).to.eq("Option 2");
dialog.clear();
});
});
it('should set the valid value with different label', () => {
it("should set the valid value with different label", () => {
const options_with_label = [
{ label: "Option 1", value: "option_1" },
{ label: "Option 2", value: "option_2" }
{ label: "Option 2", value: "option_2" },
];
get_dialog_with_autocomplete(options_with_label).as('dialog');
get_dialog_with_autocomplete(options_with_label).as("dialog");
cy.get('.frappe-control[data-fieldname=autocomplete] input').focus().as('input');
cy.get('.frappe-control[data-fieldname=autocomplete]').findByRole('listbox').should('be.visible');
cy.get('@input').type('2', { delay: 300 });
cy.get('.frappe-control[data-fieldname=autocomplete] input').type('{enter}', { delay: 300 });
cy.get('.frappe-control[data-fieldname=autocomplete] input').blur();
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('autocomplete');
expect(value).to.eq('option_2');
cy.get(".frappe-control[data-fieldname=autocomplete] input").focus().as("input");
cy.get(".frappe-control[data-fieldname=autocomplete]")
.findByRole("listbox")
.should("be.visible");
cy.get("@input").type("2", { delay: 300 });
cy.get(".frappe-control[data-fieldname=autocomplete] input").type("{enter}", {
delay: 300,
});
cy.get(".frappe-control[data-fieldname=autocomplete] input").blur();
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("autocomplete");
expect(value).to.eq("option_2");
dialog.clear();
});
});
});

View file

@ -1,55 +1,57 @@
context('Control Barcode', () => {
context("Control Barcode", () => {
beforeEach(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
function get_dialog_with_barcode() {
return cy.dialog({
title: 'Barcode',
title: "Barcode",
fields: [
{
label: 'Barcode',
fieldname: 'barcode',
fieldtype: 'Barcode'
}
]
label: "Barcode",
fieldname: "barcode",
fieldtype: "Barcode",
},
],
});
}
it('should generate barcode on setting a value', () => {
get_dialog_with_barcode().as('dialog');
it("should generate barcode on setting a value", () => {
get_dialog_with_barcode().as("dialog");
cy.focused().blur();
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
.type('123456789')
cy.get(".frappe-control[data-fieldname=barcode]")
.findByRole("textbox")
.type("123456789")
.blur();
cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]')
.should('exist');
cy.get(
'.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]'
).should("exist");
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('barcode');
expect(value).to.contain('<svg');
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("barcode");
expect(value).to.contain("<svg");
expect(value).to.contain('data-barcode-value="123456789"');
});
});
it('should reset when input is cleared', () => {
get_dialog_with_barcode().as('dialog');
it("should reset when input is cleared", () => {
get_dialog_with_barcode().as("dialog");
cy.focused().blur();
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
.type('123456789')
cy.get(".frappe-control[data-fieldname=barcode]")
.findByRole("textbox")
.type("123456789")
.blur();
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
.clear()
.blur();
cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]')
.should('not.exist');
cy.get(".frappe-control[data-fieldname=barcode]").findByRole("textbox").clear().blur();
cy.get(
'.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]'
).should("not.exist");
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('barcode');
expect(value).to.equal('');
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("barcode");
expect(value).to.equal("");
});
});
});

View file

@ -1,77 +1,80 @@
context('Control Color', () => {
context("Control Color", () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
function get_dialog_with_color() {
return cy.dialog({
title: 'Color',
fields: [{
label: 'Color',
fieldname: 'color',
fieldtype: 'Color'
}]
title: "Color",
fields: [
{
label: "Color",
fieldname: "color",
fieldtype: "Color",
},
],
});
}
it('Verifying if the color control is selecting correct', () => {
get_dialog_with_color().as('dialog');
cy.findByPlaceholderText('Choose a color').click();
it("Verifying if the color control is selecting correct", () => {
get_dialog_with_color().as("dialog");
cy.findByPlaceholderText("Choose a color").click();
///Selecting a color from the color palette
cy.get('[style="background-color: rgb(79, 157, 217);"]').click();
//Checking if the css attribute is correct
cy.get('.color-map').should('have.css', 'color', 'rgb(79, 157, 217)');
cy.get('.hue-map').should('have.css', 'color', 'rgb(0, 145, 255)');
cy.get(".color-map").should("have.css", "color", "rgb(79, 157, 217)");
cy.get(".hue-map").should("have.css", "color", "rgb(0, 144, 255)");
//Checking if the correct color is being selected
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('color');
expect(value).to.equal('#4F9DD9');
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("color");
expect(value).to.equal("#4F9DD9");
});
//Selecting a color
cy.get('[style="background-color: rgb(203, 41, 41);"]').click();
//Checking if the correct css is being selected
cy.get('.color-map').should('have.css', 'color', 'rgb(203, 41, 41)');
cy.get('.hue-map').should('have.css', 'color', 'rgb(255, 0, 0)');
cy.get(".color-map").should("have.css", "color", "rgb(203, 41, 41)");
cy.get(".hue-map").should("have.css", "color", "rgb(255, 0, 0)");
//Checking if the correct color is being selected
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('color');
expect(value).to.equal('#CB2929');
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("color");
expect(value).to.equal("#CB2929");
});
//Selecting color from the palette
cy.get('.color-map > .color-selector').click(65, 87, {force: true});
cy.get('.color-map').should('have.css', 'color', 'rgb(56, 0, 0)');
cy.get(".color-map > .color-selector").click(65, 87, { force: true });
cy.get(".color-map").should("have.css", "color", "rgb(56, 0, 0)");
//Checking if the expected color is selected and getting displayed
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('color');
expect(value).to.equal('#380000');
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("color");
expect(value).to.equal("#380000");
});
//Selecting the color from the hue map
cy.get('.hue-map > .hue-selector').click(35, -1, {force: true});
cy.get('.color-map').should('have.css', 'color', 'rgb(56, 45, 0)');
cy.get('.hue-map').should('have.css', 'color', 'rgb(255, 204, 0)');
cy.get('.color-map > .color-selector').click(55, 12, {force: true});
cy.get('.color-map').should('have.css', 'color', 'rgb(46, 37, 0)');
cy.get(".hue-map > .hue-selector").click(35, -1, { force: true });
cy.get(".color-map").should("have.css", "color", "rgb(56, 45, 0)");
cy.get(".hue-map").should("have.css", "color", "rgb(255, 204, 0)");
cy.get(".color-map > .color-selector").click(55, 12, { force: true });
cy.get(".color-map").should("have.css", "color", "rgb(46, 37, 0)");
//Checking if the correct color is being displayed
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('color');
expect(value).to.equal('#2e2500');
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("color");
expect(value).to.equal("#2e2500");
});
//Clearing the field and checking if the field contains the placeholder "Choose a color"
cy.get('.input-with-feedback').click({force: true});
cy.get_field('color', 'Color').type('{selectall}').clear();
cy.get_field('color', 'Color').invoke('attr', 'placeholder').should('contain', 'Choose a color');
cy.get(".input-with-feedback").click({ force: true });
cy.get_field("color", "Color").type("{selectall}").clear();
cy.get_field("color", "Color")
.invoke("attr", "placeholder")
.should("contain", "Choose a color");
});
});
});

View file

@ -1,134 +1,145 @@
context('Data Control', () => {
context("Data Control", () => {
before(() => {
cy.login();
cy.visit('/app/doctype');
return cy.window().its('frappe').then(frappe => {
return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
name: 'Test Data Control',
fields: [
{
"label": "Name",
"fieldname": "name1",
"fieldtype": "Data",
"options": "Name",
"in_list_view": 1,
"reqd": 1,
},
{
"label": "Email-ID",
"fieldname": "email",
"fieldtype": "Data",
"options": "Email",
"in_list_view": 1,
"reqd": 1,
},
{
"label": "Phone No.",
"fieldname": "phone",
"fieldtype": "Data",
"options": "Phone",
"in_list_view": 1,
"reqd": 1,
},
]
cy.visit("/app/doctype");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.xcall("frappe.tests.ui_test_helpers.create_doctype", {
name: "Test Data Control",
fields: [
{
label: "Name",
fieldname: "name1",
fieldtype: "Data",
options: "Name",
in_list_view: 1,
reqd: 1,
},
{
label: "Email-ID",
fieldname: "email",
fieldtype: "Data",
options: "Email",
in_list_view: 1,
reqd: 1,
},
{
label: "Phone No.",
fieldname: "phone",
fieldtype: "Data",
options: "Phone",
in_list_view: 1,
reqd: 1,
},
],
});
});
});
});
it('check custom formatters', () => {
it("check custom formatters", () => {
cy.visit(`/app/doctype/User`);
cy.get('[data-fieldname="fields"] .grid-row[data-idx="2"] [data-fieldname="fieldtype"] .static-area').should('have.text', '🔵 Section Break');
cy.get(
'[data-fieldname="fields"] .grid-row[data-idx="3"] [data-fieldname="fieldtype"] .static-area'
).should("have.text", "Section Break");
});
it('Verifying data control by inputting different patterns for "Name" field', () => {
cy.new_form('Test Data Control');
cy.new_form("Test Data Control");
//Checking the URL for the new form of the doctype
cy.location("pathname").should('eq', '/app/test-data-control/new-test-data-control-1');
cy.get('.title-text').should('have.text', 'New Test Data Control');
cy.get('.frappe-control[data-fieldname="name1"]').find('label').should('have.class', 'reqd');
cy.get('.frappe-control[data-fieldname="email"]').find('label').should('have.class', 'reqd');
cy.get('.frappe-control[data-fieldname="phone"]').find('label').should('have.class', 'reqd');
cy.location("pathname").should("eq", "/app/test-data-control/new-test-data-control-1");
cy.get(".title-text").should("have.text", "New Test Data Control");
cy.get('.frappe-control[data-fieldname="name1"]')
.find("label")
.should("have.class", "reqd");
cy.get('.frappe-control[data-fieldname="email"]')
.find("label")
.should("have.class", "reqd");
cy.get('.frappe-control[data-fieldname="phone"]')
.find("label")
.should("have.class", "reqd");
//Checking if the status is "Not Saved" initially
cy.get('.indicator-pill').should('have.text', 'Not Saved');
cy.get(".indicator-pill").should("have.text", "Not Saved");
//Inputting data in the field
cy.fill_field('name1', '@@###', 'Data');
cy.fill_field('email', 'test@example.com', 'Data');
cy.fill_field('phone', '9834280031', 'Data');
cy.fill_field("name1", "@@###", "Data");
cy.fill_field("email", "test@example.com", "Data");
cy.fill_field("phone", "9834280031", "Data");
//Checking if the border color of the field changes to red
cy.get('.frappe-control[data-fieldname="name1"]').should('have.class', 'has-error');
cy.get('.frappe-control[data-fieldname="name1"]').should("have.class", "has-error");
cy.save();
//Checking for the error message
cy.get('.modal-title').should('have.text', 'Message');
cy.get('.msgprint').should('have.text', '@@### is not a valid Name');
cy.get(".modal-title").should("have.text", "Message");
cy.get(".msgprint").should("have.text", "@@### is not a valid Name");
cy.hide_dialog();
cy.get_field('name1', 'Data').clear({force: true});
cy.fill_field('name1', 'Komal{}/!', 'Data');
cy.get('.frappe-control[data-fieldname="name1"]').should('have.class', 'has-error');
cy.get_field("name1", "Data").clear({ force: true });
cy.fill_field("name1", "Komal{}/!", "Data");
cy.get('.frappe-control[data-fieldname="name1"]').should("have.class", "has-error");
cy.save();
cy.get('.modal-title').should('have.text', 'Message');
cy.get('.msgprint').should('have.text', 'Komal{}/! is not a valid Name');
cy.get(".modal-title").should("have.text", "Message");
cy.get(".msgprint").should("have.text", "Komal{}/! is not a valid Name");
cy.hide_dialog();
});
it('Verifying data control by inputting different patterns for "Email" field', () => {
cy.get_field('name1', 'Data').clear({force: true});
cy.fill_field('name1', 'Komal', 'Data');
cy.get_field('email', 'Data').clear({force: true});
cy.fill_field('email', 'komal', 'Data');
cy.get('.frappe-control[data-fieldname="email"]').should('have.class', 'has-error');
cy.get_field("name1", "Data").clear({ force: true });
cy.fill_field("name1", "Komal", "Data");
cy.get_field("email", "Data").clear({ force: true });
cy.fill_field("email", "komal", "Data");
cy.get('.frappe-control[data-fieldname="email"]').should("have.class", "has-error");
cy.save();
cy.get('.modal-title').should('have.text', 'Message');
cy.get('.msgprint').should('have.text', 'komal is not a valid Email Address');
cy.get(".modal-title").should("have.text", "Message");
cy.get(".msgprint").should("have.text", "komal is not a valid Email Address");
cy.hide_dialog();
cy.get_field('email', 'Data').clear({force: true});
cy.fill_field('email', 'komal@test', 'Data');
cy.get('.frappe-control[data-fieldname="email"]').should('have.class', 'has-error');
cy.get_field("email", "Data").clear({ force: true });
cy.fill_field("email", "komal@test", "Data");
cy.get('.frappe-control[data-fieldname="email"]').should("have.class", "has-error");
cy.save();
cy.get('.modal-title').should('have.text', 'Message');
cy.get('.msgprint').should('have.text', 'komal@test is not a valid Email Address');
cy.get(".modal-title").should("have.text", "Message");
cy.get(".msgprint").should("have.text", "komal@test is not a valid Email Address");
cy.hide_dialog();
});
it('Verifying data control by inputting different patterns for "Phone" field', () => {
cy.get_field('email', 'Data').clear({force: true});
cy.fill_field('email', 'komal@test.com', 'Data');
cy.get_field('phone', 'Data').clear({force: true});
cy.fill_field('phone', 'komal', 'Data');
cy.get('.frappe-control[data-fieldname="phone"]').should('have.class', 'has-error');
cy.findByRole('button', {name: 'Save'}).click({force: true});
cy.get('.modal-title').should('have.text', 'Message');
cy.get('.msgprint').should('have.text', 'komal is not a valid Phone Number');
cy.get_field("email", "Data").clear({ force: true });
cy.fill_field("email", "komal@test.com", "Data");
cy.get_field("phone", "Data").clear({ force: true });
cy.fill_field("phone", "komal", "Data");
cy.get('.frappe-control[data-fieldname="phone"]').should("have.class", "has-error");
cy.findByRole("button", { name: "Save" }).click({ force: true });
cy.get(".modal-title").should("have.text", "Message");
cy.get(".msgprint").should("have.text", "komal is not a valid Phone Number");
cy.hide_dialog();
});
it('Inputting correct data and saving the doc', () => {
it("Inputting correct data and saving the doc", () => {
//Inputting the data as expected and saving the document
cy.get_field('name1', 'Data').clear({force: true});
cy.get_field('email', 'Data').clear({force: true});
cy.get_field('phone', 'Data').clear({force: true});
cy.fill_field('name1', 'Komal', 'Data');
cy.fill_field('email', 'komal@test.com', 'Data');
cy.fill_field('phone', '9432380001', 'Data');
cy.findByRole('button', {name: 'Save'}).click({force: true});
cy.get_field("name1", "Data").clear({ force: true });
cy.get_field("email", "Data").clear({ force: true });
cy.get_field("phone", "Data").clear({ force: true });
cy.fill_field("name1", "Komal", "Data");
cy.fill_field("email", "komal@test.com", "Data");
cy.fill_field("phone", "9432380001", "Data");
cy.findByRole("button", { name: "Save" }).click({ force: true });
//Checking if the fields contains the data which has been filled in
cy.location("pathname").should('not.be', '/app/test-data-control/new-test-data-control-1');
cy.get_field('name1').should('have.value', 'Komal');
cy.get_field('email').should('have.value', 'komal@test.com');
cy.get_field('phone').should('have.value', '9432380001');
cy.location("pathname").should("not.be", "/app/test-data-control/new-test-data-control-1");
cy.get_field("name1").should("have.value", "Komal");
cy.get_field("email").should("have.value", "komal@test.com");
cy.get_field("phone").should("have.value", "9432380001");
});
it('Deleting the doc', () => {
it("Deleting the doc", () => {
//Deleting the inserted document
cy.go_to_list('Test Data Control');
cy.get('.list-row-checkbox').eq(0).click({force: true});
cy.get('.actions-btn-group > .btn').contains('Actions').click();
cy.go_to_list("Test Data Control");
cy.get(".list-row-checkbox").eq(0).click({ force: true });
cy.get(".actions-btn-group > .btn").contains("Actions").click();
cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click();
cy.click_modal_primary_button('Yes');
cy.click_modal_primary_button("Yes");
});
});

View file

@ -1,82 +1,83 @@
context('Date Control', () => {
context("Date Control", () => {
before(() => {
cy.login();
cy.visit('/app');
cy.visit("/app");
});
function get_dialog(date_field_options) {
return cy.dialog({
title: 'Date',
fields: [{
"label": "Date",
"fieldname": "date",
"fieldtype": "Date",
"in_list_view": 1,
...date_field_options
}]
title: "Date",
fields: [
{
label: "Date",
fieldname: "date",
fieldtype: "Date",
in_list_view: 1,
...date_field_options,
},
],
});
}
it('Selecting a date from the datepicker', () => {
it("Selecting a date from the datepicker & check prev & next button", () => {
cy.clear_dialogs();
cy.clear_datepickers();
get_dialog().as('dialog');
cy.get_field('date', 'Date').click();
cy.get('.datepicker--nav-title').click();
cy.get('.datepicker--nav-title').click({force: true});
get_dialog().as("dialog");
cy.get_field("date", "Date").click();
cy.get(".datepicker--nav-title").click();
cy.get(".datepicker--nav-title").click({ force: true });
//Inputing values in the date field
cy.get('.datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]').click();
cy.get('.datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]').click();
cy.get('.datepicker--days > .datepicker--cells > .datepicker--cell[data-date=15]').click();
cy.get(
".datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]"
).click();
cy.get(
".datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]"
).click();
cy.get(".datepicker--days > .datepicker--cells > .datepicker--cell[data-date=15]").click();
// Verify if the selected date is set the date field
cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-01-15');
});
cy.window().its("cur_dialog.fields_dict.date.value").should("be.equal", "2020-01-15");
it('Checking next and previous button', () => {
cy.clear_dialogs();
cy.clear_datepickers();
get_dialog({ default: '2020-01-15' }).as('dialog');
cy.get_field('date', 'Date').click();
cy.get_field("date", "Date").click();
//Clicking on the next button in the datepicker
cy.get('.datepicker--nav-action[data-action=next]').click();
cy.get(".datepicker--nav-action[data-action=next]").click();
//Selecting a date from the datepicker
cy.get('.datepicker--cell[data-date=15]').click({force: true});
cy.get(".datepicker--cell[data-date=15]").click({ force: true });
//Verifying if the selected date has been displayed in the date field
cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-02-15');
cy.window().its("cur_dialog.fields_dict.date.value").should("be.equal", "2020-02-15");
cy.wait(500);
cy.get_field('date', 'Date').click();
cy.get_field("date", "Date").click();
//Clicking on the previous button in the datepicker
cy.get('.datepicker--nav-action[data-action=prev]').click();
cy.get(".datepicker--nav-action[data-action=prev]").click();
//Selecting a date from the datepicker
cy.get('.datepicker--cell[data-date=15]').click({force: true});
cy.get(".datepicker--cell[data-date=15]").click({ force: true });
//Verifying if the selected date has been displayed in the date field
cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-01-15');
cy.window().its("cur_dialog.fields_dict.date.value").should("be.equal", "2020-01-15");
});
it('Clicking on "Today" button gives todays date', () => {
cy.clear_dialogs();
cy.clear_datepickers();
get_dialog().as('dialog');
cy.get_field('date', 'Date').click();
get_dialog().as("dialog");
cy.get_field("date", "Date").click();
//Clicking on "Today" button
cy.get('.datepicker--button').click();
cy.get(".datepicker--button").click();
//Verifying if clicking on "Today" button matches today's date
cy.window().then(win => {
expect(win.cur_dialog.fields_dict.date.value).to.be.equal(win.frappe.datetime.get_today());
cy.window().then((win) => {
expect(win.cur_dialog.fields_dict.date.value).to.be.equal(
win.frappe.datetime.get_today()
);
});
});
});
});

View file

@ -1,42 +1,48 @@
context('Date Range Control', () => {
context("Date Range Control", () => {
before(() => {
cy.login();
cy.visit('/app');
cy.visit("/app");
});
function get_dialog() {
return cy.dialog({
title: 'Date Range',
fields: [{
"label": "Date Range",
"fieldname": "date_range",
"fieldtype": "Date Range",
}]
title: "Date Range",
fields: [
{
label: "Date Range",
fieldname: "date_range",
fieldtype: "Date Range",
},
],
});
}
it('Selecting a date range from the datepicker', () => {
it("Selecting a date range from the datepicker", () => {
cy.clear_dialogs();
cy.clear_datepickers();
get_dialog().as('dialog');
cy.get_field('date_range', 'Date Range').click();
cy.get('.datepicker--nav-title').click();
cy.get('.datepicker--nav-title').click({force: true});
get_dialog().as("dialog");
cy.get_field("date_range", "Date Range").click();
cy.get(".datepicker--nav-title").click();
cy.get(".datepicker--nav-title").click({ force: true });
//Inputing date range values in the date range field
cy.get('.datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]').click();
cy.get('.datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]').click();
cy.get('.datepicker--cell[data-date=1]:first').click({force: true});
cy.get('.datepicker--cell[data-date=15]:first').click({force: true});
cy.get(
".datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]"
).click();
cy.get(
".datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]"
).click();
cy.get(".datepicker--cell[data-date=1]:first").click({ force: true });
cy.get(".datepicker--cell[data-date=15]:first").click({ force: true });
// Verify if the selected date range values is set in the date range field
cy.window()
.its('cur_dialog')
.then(dialog => {
.its("cur_dialog")
.then((dialog) => {
let date_range = dialog.get_value("date_range");
expect(date_range[0]).to.equal('2020-01-01');
expect(date_range[1]).to.equal('2020-01-15');
expect(date_range[0]).to.equal("2020-01-01");
expect(date_range[1]).to.equal("2020-01-15");
});
});
});
});

View file

@ -1,46 +1,46 @@
context('Control Duration', () => {
context("Control Duration", () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
function get_dialog_with_duration(hide_days = 0, hide_seconds = 0) {
return cy.dialog({
title: 'Duration',
fields: [{
'fieldname': 'duration',
'fieldtype': 'Duration',
'hide_days': hide_days,
'hide_seconds': hide_seconds
}]
title: "Duration",
fields: [
{
fieldname: "duration",
fieldtype: "Duration",
hide_days: hide_days,
hide_seconds: hide_seconds,
},
],
});
}
it('should set duration', () => {
get_dialog_with_duration().as('dialog');
cy.get('.frappe-control[data-fieldname=duration] input')
.first()
.click();
cy.get('.duration-input[data-duration=days]')
it("should set duration", () => {
get_dialog_with_duration().as("dialog");
cy.get(".frappe-control[data-fieldname=duration] input").first().click();
cy.get(".duration-input[data-duration=days]")
.type(45, { force: true })
.blur({ force: true });
cy.get('.duration-input[data-duration=minutes]')
.type(30)
.blur({ force: true });
cy.get('.frappe-control[data-fieldname=duration] input').first().should('have.value', '45d 30m');
cy.get('.frappe-control[data-fieldname=duration] input').first().blur();
cy.get('.duration-picker').should('not.be.visible');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('duration');
cy.get(".duration-input[data-duration=minutes]").type(30).blur({ force: true });
cy.get(".frappe-control[data-fieldname=duration] input")
.first()
.should("have.value", "45d 30m");
cy.get(".frappe-control[data-fieldname=duration] input").first().blur();
cy.get(".duration-picker").should("not.be.visible");
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("duration");
expect(value).to.equal(3889800);
cy.hide_dialog();
});
});
it('should hide days or seconds according to duration options', () => {
get_dialog_with_duration(1, 1).as('dialog');
cy.get('.frappe-control[data-fieldname=duration] input').first();
cy.get('.duration-input[data-duration=days]').should('not.be.visible');
cy.get('.duration-input[data-duration=seconds]').should('not.be.visible');
it("should hide days or seconds according to duration options", () => {
get_dialog_with_duration(1, 1).as("dialog");
cy.get(".frappe-control[data-fieldname=duration] input").first();
cy.get(".duration-input[data-duration=days]").should("not.be.visible");
cy.get(".duration-input[data-duration=seconds]").should("not.be.visible");
});
});
});

View file

@ -1,133 +1,159 @@
context('Dynamic Link', () => {
context("Dynamic Link", () => {
before(() => {
cy.login();
cy.visit('/app/doctype');
return cy.window().its('frappe').then(frappe => {
return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
name: 'Test Dynamic Link',
fields: [
{
"label": "Document Type",
"fieldname": "doc_type",
"fieldtype": "Link",
"options": "DocType",
"in_list_view": 1,
"in_standard_filter": 1,
},
{
"label": "Document ID",
"fieldname": "doc_id",
"fieldtype": "Dynamic Link",
"options": "doc_type",
"in_list_view": 1,
"in_standard_filter": 1,
},
]
cy.visit("/app/doctype");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.xcall("frappe.tests.ui_test_helpers.create_doctype", {
name: "Test Dynamic Link",
fields: [
{
label: "Document Type",
fieldname: "doc_type",
fieldtype: "Link",
options: "DocType",
in_list_view: 1,
in_standard_filter: 1,
},
{
label: "Document ID",
fieldname: "doc_id",
fieldtype: "Dynamic Link",
options: "doc_type",
in_list_view: 1,
in_standard_filter: 1,
},
],
});
});
});
});
function get_dialog_with_dynamic_link() {
return cy.dialog({
title: 'Dynamic Link',
fields: [{
"label": "Document Type",
"fieldname": "doc_type",
"fieldtype": "Link",
"options": "DocType",
"in_list_view": 1,
},
{
"label": "Document ID",
"fieldname": "doc_id",
"fieldtype": "Dynamic Link",
"options": "doc_type",
"in_list_view": 1,
}]
title: "Dynamic Link",
fields: [
{
label: "Document Type",
fieldname: "doc_type",
fieldtype: "Link",
options: "DocType",
in_list_view: 1,
},
{
label: "Document ID",
fieldname: "doc_id",
fieldtype: "Dynamic Link",
options: "doc_type",
in_list_view: 1,
},
],
});
}
function get_dialog_with_dynamic_link_option() {
return cy.dialog({
title: 'Dynamic Link',
fields: [{
"label": "Document Type",
"fieldname": "doc_type",
"fieldtype": "Link",
"options": "DocType",
"in_list_view": 1,
},
{
"label": "Document ID",
"fieldname": "doc_id",
"fieldtype": "Dynamic Link",
"get_options": () => {
return "User";
title: "Dynamic Link",
fields: [
{
label: "Document Type",
fieldname: "doc_type",
fieldtype: "Link",
options: "DocType",
in_list_view: 1,
},
"in_list_view": 1,
}]
{
label: "Document ID",
fieldname: "doc_id",
fieldtype: "Dynamic Link",
get_options: () => {
return "User";
},
in_list_view: 1,
},
],
});
}
it('Creating a dynamic link by passing option as function and verifying it in a dialog', () => {
get_dialog_with_dynamic_link_option().as('dialog');
cy.get_field('doc_type').clear();
cy.fill_field('doc_type', 'User', 'Link');
cy.get_field('doc_id').click();
it("Creating a dynamic link by passing option as function and verifying it in a dialog", () => {
get_dialog_with_dynamic_link_option().as("dialog");
cy.get_field("doc_type").clear();
cy.fill_field("doc_type", "User", "Link");
cy.get_field("doc_id").click();
//Checking if the listbox have length greater than 0
cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0);
cy.get('.btn-modal-close').click({force: true});
cy.get('[data-fieldname="doc_id"]')
.find(".awesomplete")
.find("li")
.its("length")
.should("be.gte", 0);
cy.get(".btn-modal-close").click({ force: true });
});
it('Creating a dynamic link and verifying it in a dialog', () => {
get_dialog_with_dynamic_link().as('dialog');
cy.get_field('doc_type').clear();
cy.fill_field('doc_type', 'User', 'Link');
cy.get_field('doc_id').click();
it("Creating a dynamic link and verifying it in a dialog", () => {
get_dialog_with_dynamic_link().as("dialog");
cy.get_field("doc_type").clear();
cy.fill_field("doc_type", "User", "Link");
cy.get_field("doc_id").click();
//Checking if the listbox have length greater than 0
cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0);
cy.get('.btn-modal-close').click({force: true, multiple: true});
cy.get('[data-fieldname="doc_id"]')
.find(".awesomplete")
.find("li")
.its("length")
.should("be.gte", 0);
cy.get(".btn-modal-close").click({ force: true, multiple: true });
});
it('Creating a dynamic link and verifying it', () => {
cy.visit('/app/test-dynamic-link');
it("Creating a dynamic link and verifying it", () => {
cy.visit("/app/test-dynamic-link");
//Clicking on the Document ID field
cy.get_field('doc_type').clear();
cy.get_field("doc_type").clear();
//Entering User in the Doctype field
cy.fill_field('doc_type', 'User', 'Link', {delay: 500});
cy.get_field('doc_id').click();
cy.fill_field("doc_type", "User", "Link", { delay: 500 });
cy.get_field("doc_id").click();
//Checking if the listbox have length greater than 0
cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0);
cy.get('[data-fieldname="doc_id"]')
.find(".awesomplete")
.find("li")
.its("length")
.should("be.gte", 0);
//Opening a new form for dynamic link doctype
cy.new_form('Test Dynamic Link');
cy.get_field('doc_type').clear();
cy.new_form("Test Dynamic Link");
cy.get_field("doc_type").clear();
//Entering User in the Doctype field
cy.fill_field('doc_type', 'User', 'Link', {delay: 500});
cy.get_field('doc_id').click();
cy.fill_field("doc_type", "User", "Link", { delay: 500 });
cy.get_field("doc_id").click();
//Checking if the listbox have length greater than 0
cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0);
cy.get_field('doc_type').clear();
cy.get('[data-fieldname="doc_id"]')
.find(".awesomplete")
.find("li")
.its("length")
.should("be.gte", 0);
cy.get_field("doc_type").clear();
//Entering System Settings in the Doctype field
cy.intercept('/api/method/frappe.desk.search.search_link').as('search_query');
cy.fill_field('doc_type', 'System Settings', 'Link', {delay: 500});
cy.wait('@search_query');
cy.get(`[data-fieldname="doc_type"] ul:visible li:first-child`)
.click({scrollBehavior: false});
cy.intercept("/api/method/frappe.desk.search.search_link").as("search_query");
cy.fill_field("doc_type", "System Settings", "Link", { delay: 500 });
cy.wait("@search_query");
cy.get(`[data-fieldname="doc_type"] ul:visible li:first-child`).click({
scrollBehavior: false,
});
cy.get_field('doc_id').click();
cy.get_field("doc_id").click();
//Checking if the system throws error
cy.get('.modal-title').should('have.text', 'Error');
cy.get('.msgprint').should('have.text', 'System Settings is not a valid DocType for Dynamic Link');
cy.get(".modal-title").should("have.text", "Error");
cy.get(".msgprint").should(
"have.text",
"System Settings is not a valid DocType for Dynamic Link"
);
});
});

View file

@ -11,9 +11,9 @@ context("Control Float", () => {
{
fieldname: "float_number",
fieldtype: "Float",
Label: "Float"
}
]
Label: "Float",
},
],
});
}
@ -21,27 +21,22 @@ context("Control Float", () => {
get_dialog_with_float().as("dialog");
let data = get_data();
data.forEach(x => {
data.forEach((x) => {
cy.window()
.its("frappe")
.then(frappe => {
.then((frappe) => {
frappe.boot.sysdefaults.number_format = x.number_format;
});
x.values.forEach(d => {
x.values.forEach((d) => {
cy.get_field("float_number", "Float").clear();
cy.wait(200);
cy.fill_field("float_number", d.input, "Float").blur();
cy.get_field("float_number", "Float").should(
"have.value",
d.blur_expected
);
cy.get_field("float_number", "Float").should("have.value", d.blur_expected);
cy.get_field("float_number", "Float").focus();
cy.get_field("float_number", "Float").blur();
cy.get_field("float_number", "Float").focus();
cy.get_field("float_number", "Float").should(
"have.value",
d.focus_expected
);
cy.get_field("float_number", "Float").should("have.value", d.focus_expected);
});
});
});
@ -54,19 +49,19 @@ context("Control Float", () => {
{
input: "364.87,334",
blur_expected: "36.487,334",
focus_expected: "36487.334"
focus_expected: "36487.334",
},
{
input: "36487,334",
blur_expected: "36.487,334",
focus_expected: "36487.334"
focus_expected: "36487.334",
},
{
input: "100",
blur_expected: "100,000",
focus_expected: "100"
}
]
focus_expected: "100",
},
],
},
{
number_format: "#,###.##",
@ -74,20 +69,20 @@ context("Control Float", () => {
{
input: "364,87.334",
blur_expected: "36,487.334",
focus_expected: "36487.334"
focus_expected: "36487.334",
},
{
input: "36487.334",
blur_expected: "36,487.334",
focus_expected: "36487.334"
focus_expected: "36487.334",
},
{
input: "100",
blur_expected: "100.000",
focus_expected: "100"
}
]
}
focus_expected: "100",
},
],
},
];
}
});

View file

@ -1,50 +1,55 @@
context('Control Icon', () => {
context("Control Icon", () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
function get_dialog_with_icon() {
return cy.dialog({
title: 'Icon',
fields: [{
label: 'Icon',
fieldname: 'icon',
fieldtype: 'Icon'
}]
title: "Icon",
fields: [
{
label: "Icon",
fieldname: "icon",
fieldtype: "Icon",
},
],
});
}
it('should set icon', () => {
get_dialog_with_icon().as('dialog');
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').click();
it("should set icon", () => {
get_dialog_with_icon().as("dialog");
cy.get(".frappe-control[data-fieldname=icon]").findByRole("textbox").click();
cy.get('.icon-picker .icon-wrapper[id=heart-active]').first().click();
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'heart-active');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('icon');
expect(value).to.equal('heart-active');
cy.get(".icon-picker .icon-wrapper[id=heart-active]").first().click();
cy.get(".frappe-control[data-fieldname=icon]")
.findByRole("textbox")
.should("have.value", "heart-active");
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("icon");
expect(value).to.equal("heart-active");
});
cy.get('.icon-picker .icon-wrapper[id=heart]').first().click();
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'heart');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('icon');
expect(value).to.equal('heart');
cy.get(".icon-picker .icon-wrapper[id=heart]").first().click();
cy.get(".frappe-control[data-fieldname=icon]")
.findByRole("textbox")
.should("have.value", "heart");
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("icon");
expect(value).to.equal("heart");
});
});
it('search for icon and clear search input', () => {
let search_text = 'ed';
cy.get('.icon-picker').findByRole('searchbox').click().type(search_text);
cy.get('.icon-section .icon-wrapper:not(.hidden)').then(i => {
cy.get(`.icon-section .icon-wrapper[id*='${search_text}']`).then(icons => {
it("search for icon and clear search input", () => {
let search_text = "ed";
cy.get(".icon-picker").findByRole("searchbox").click().type(search_text);
cy.get(".icon-section .icon-wrapper:not(.hidden)").then((i) => {
cy.get(`.icon-section .icon-wrapper[id*='${search_text}']`).then((icons) => {
expect(i.length).to.equal(icons.length);
});
});
cy.get('.icon-picker').findByRole('searchbox').clear().blur();
cy.get('.icon-section .icon-wrapper').should('not.have.class', 'hidden');
cy.get(".icon-picker").findByRole("searchbox").clear().blur();
cy.get(".icon-section .icon-wrapper").should("not.have.class", "hidden");
});
});
});

View file

@ -1,93 +1,88 @@
context('Control Link', () => {
context("Control Link", () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
beforeEach(() => {
cy.visit('/app/website');
cy.visit("/app/website");
cy.create_records({
doctype: 'ToDo',
description: 'this is a test todo for link'
}).as('todos');
doctype: "ToDo",
description: "this is a test todo for link",
}).as("todos");
});
function get_dialog_with_link() {
return cy.dialog({
title: 'Link',
title: "Link",
fields: [
{
'label': 'Select ToDo',
'fieldname': 'link',
'fieldtype': 'Link',
'options': 'ToDo',
}
]
label: "Select ToDo",
fieldname: "link",
fieldtype: "Link",
options: "ToDo",
},
],
});
}
function get_dialog_with_user_link() {
function get_dialog_with_gender_link() {
return cy.dialog({
title: 'Link',
title: "Link",
fields: [
{
'label': 'Select User',
'fieldname': 'link',
'fieldtype': 'Link',
'options': 'User',
}
]
label: "Select Gender",
fieldname: "link",
fieldtype: "Link",
options: "Gender",
},
],
});
}
it('should set the valid value', () => {
get_dialog_with_link().as('dialog');
it("should set the valid value", () => {
get_dialog_with_link().as("dialog");
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "User",
"property": "translate_link_fields",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "0"
}, true);
cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "ToDo",
property: "show_title_field_in_link",
property_type: "Check",
doctype_or_field: "DocType",
value: "0",
},
true
);
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "ToDo",
"property": "show_title_field_in_link",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "0"
}, true);
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
cy.wait('@search_link');
cy.get('@input').type('todo for link', { delay: 200 });
cy.wait('@search_link');
cy.get('.frappe-control[data-fieldname=link]').findByRole('listbox').should('be.visible');
cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
cy.get('.frappe-control[data-fieldname=link] input').blur();
cy.get('@dialog').then(dialog => {
cy.get('@todos').then(todos => {
let value = dialog.get_value('link');
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.get("@input").type("todo for link", { delay: 200 });
cy.wait("@search_link");
cy.get(".frappe-control[data-fieldname=link]").findByRole("listbox").should("be.visible");
cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
cy.get(".frappe-control[data-fieldname=link] input").blur();
cy.get("@dialog").then((dialog) => {
cy.get("@todos").then((todos) => {
let value = dialog.get_value("link");
expect(value).to.eq(todos[0]);
});
});
});
it('should unset invalid value', () => {
get_dialog_with_link().as('dialog');
it("should unset invalid value", () => {
get_dialog_with_link().as("dialog");
cy.intercept('POST', '/api/method/frappe.client.validate_link').as('validate_link');
cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link");
cy.get('.frappe-control[data-fieldname=link] input')
.type('invalid value', { delay: 100 })
cy.get(".frappe-control[data-fieldname=link] input")
.type("invalid value", { delay: 100 })
.blur();
cy.wait('@validate_link');
cy.get('.frappe-control[data-fieldname=link] input').should('have.value', '');
cy.wait("@validate_link");
cy.get(".frappe-control[data-fieldname=link] input").should("have.value", "");
});
it("should be possible set empty value explicitly", () => {
@ -95,295 +90,246 @@ context('Control Link', () => {
cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link");
cy.get(".frappe-control[data-fieldname=link] input")
.type(" ", { delay: 100 })
.blur();
cy.get(".frappe-control[data-fieldname=link] input").type(" ", { delay: 100 }).blur();
cy.wait("@validate_link");
cy.get(".frappe-control[data-fieldname=link] input").should("have.value", "");
cy.window()
.its("cur_dialog")
.then((dialog) => {
expect(dialog.get_value("link")).to.equal('');
expect(dialog.get_value("link")).to.equal("");
});
});
it('should route to form on arrow click', () => {
get_dialog_with_link().as('dialog');
it("should route to form on arrow click", () => {
get_dialog_with_link().as("dialog");
cy.intercept('POST', '/api/method/frappe.client.validate_link').as('validate_link');
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get('@todos').then(todos => {
cy.get('.frappe-control[data-fieldname=link] input').as('input');
cy.get('@input').focus();
cy.wait('@search_link');
cy.get('@input').type(todos[0]).blur();
cy.wait('@validate_link');
cy.get('@input').focus();
cy.get("@todos").then((todos) => {
cy.get(".frappe-control[data-fieldname=link] input").as("input");
cy.get("@input").focus();
cy.wait("@search_link");
cy.get("@input").type(todos[0]).blur();
cy.wait("@validate_link");
cy.get("@input").focus();
cy.wait(500); // wait for arrow to show
cy.get('.frappe-control[data-fieldname=link] .btn-open')
.should('be.visible')
.click();
cy.location('pathname').should('eq', `/app/todo/${todos[0]}`);
cy.get(".frappe-control[data-fieldname=link] .btn-open").should("be.visible").click();
cy.location("pathname").should("eq", `/app/todo/${todos[0]}`);
});
});
it('show title field in link', () => {
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "User",
"property": "translate_link_fields",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "0"
}, true);
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "ToDo",
"property": "show_title_field_in_link",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "1"
}, true);
it("show title field in link", () => {
cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "ToDo",
property: "show_title_field_in_link",
property_type: "Check",
doctype_or_field: "DocType",
value: "1",
},
true
);
cy.clear_cache();
cy.wait(500);
get_dialog_with_link().as('dialog');
cy.window().its('frappe').then(frappe => {
if (!frappe.boot) {
frappe.boot = {
link_title_doctypes: ['ToDo']
};
} else {
frappe.boot.link_title_doctypes = ['ToDo'];
}
});
get_dialog_with_link().as("dialog");
cy.window()
.its("frappe")
.then((frappe) => {
if (!frappe.boot) {
frappe.boot = {
link_title_doctypes: ["ToDo"],
};
} else {
frappe.boot.link_title_doctypes = ["ToDo"];
}
});
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
cy.wait('@search_link');
cy.get('@input').type('todo for link');
cy.wait('@search_link');
cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
cy.get('.frappe-control[data-fieldname=link] input').blur();
cy.get('@dialog').then(dialog => {
cy.get('@todos').then(todos => {
let field = dialog.get_field('link');
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.get("@input").type("todo for link");
cy.wait("@search_link");
cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
cy.get(".frappe-control[data-fieldname=link] input").blur();
cy.get("@dialog").then((dialog) => {
cy.get("@todos").then((todos) => {
let field = dialog.get_field("link");
let value = field.get_value();
let label = field.get_label_value();
expect(value).to.eq(todos[0]);
expect(label).to.eq('this is a test todo for link');
expect(label).to.eq("this is a test todo for link");
});
});
});
it('should update dependant fields (via fetch_from)', () => {
cy.get('@todos').then(todos => {
it("should update dependant fields (via fetch_from)", () => {
cy.get("@todos").then((todos) => {
cy.visit(`/app/todo/${todos[0]}`);
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.intercept('POST', '/api/method/frappe.client.validate_link').as('validate_link');
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link");
cy.get('.frappe-control[data-fieldname=assigned_by] input').focus().as('input');
cy.get('@input').type('Administrator', {delay: 100}).blur();
cy.wait('@validate_link');
cy.get('.frappe-control[data-fieldname=assigned_by_full_name] .control-value').should(
'contain', 'Administrator'
cy.get(".frappe-control[data-fieldname=assigned_by] input").focus().as("input");
cy.get("@input").type(cy.config("testUser"), { delay: 100 }).blur();
cy.wait("@validate_link");
cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
"contain",
"Frappe"
);
cy.window()
.its("cur_frm.doc.assigned_by")
.should("eq", "Administrator");
cy.window().its("cur_frm.doc.assigned_by").should("eq", cy.config("testUser"));
// invalid input
cy.get('@input').clear().type('invalid input', {delay: 100}).blur();
cy.get('.frappe-control[data-fieldname=assigned_by_full_name] .control-value').should(
'contain', ''
cy.get("@input").clear().type("invalid input", { delay: 100 }).blur();
cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
"contain",
""
);
cy.window()
.its("cur_frm.doc.assigned_by")
.should("eq", null);
cy.window().its("cur_frm.doc.assigned_by").should("eq", null);
// set valid value again
cy.get('@input').clear().focus();
cy.wait('@search_link');
cy.get('@input').type('Administrator', {delay: 100}).blur();
cy.wait('@validate_link');
cy.get("@input").clear().focus();
cy.wait("@search_link");
cy.get("@input").type(cy.config("testUser"), { delay: 100 }).blur();
cy.wait("@validate_link");
cy.window()
.its("cur_frm.doc.assigned_by")
.should("eq", "Administrator");
cy.window().its("cur_frm.doc.assigned_by").should("eq", cy.config("testUser"));
// clear input
cy.get('@input').clear().blur();
cy.get('.frappe-control[data-fieldname=assigned_by_full_name] .control-value').should(
'contain', ''
cy.get("@input").clear().blur();
cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
"contain",
""
);
cy.window()
.its("cur_frm.doc.assigned_by")
.should("eq", "");
cy.window().its("cur_frm.doc.assigned_by").should("eq", "");
});
});
it("should set default values", () => {
cy.insert_doc("Property Setter", {
"doctype_or_field": "DocField",
"doc_type": "ToDo",
"field_name": "assigned_by",
"property": "default",
"property_type": "Text",
"value": "Administrator"
}, true);
cy.insert_doc(
"Property Setter",
{
doctype_or_field: "DocField",
doc_type: "ToDo",
field_name: "assigned_by",
property: "default",
property_type: "Text",
value: "Administrator",
},
true
);
cy.reload();
cy.new_form("ToDo");
cy.fill_field("description", "new", "Text Editor");
cy.intercept("POST", "/api/method/frappe.desk.form.save.savedocs").as("save_form");
cy.findByRole("button", {name: "Save"}).click();
cy.wait("@save_form");
cy.fill_field("description", "new", "Text Editor").wait(200);
cy.save();
cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
"contain", "Administrator"
"contain",
"Administrator"
);
// if user clears default value explicitly, system should not reset default again
cy.get_field("assigned_by").clear().blur();
cy.intercept("POST", "/api/method/frappe.desk.form.save.savedocs").as("save_form");
cy.findByRole("button", {name: "Save"}).click();
cy.wait("@save_form");
cy.save();
cy.get_field("assigned_by").should("have.value", "");
cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
"contain", ""
"contain",
""
);
});
it('show translated text for link with show_title_field_in_link enabled', () => {
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "ToDo",
"property": "translate_link_fields",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "1"
}, true);
it("show translated text for Gender link field with language de with input in de", () => {
cy.call("frappe.tests.ui_test_helpers.insert_translations").then(() => {
cy.window()
.its("frappe")
.then((frappe) => {
cy.set_value("User", frappe.user.name, { language: "de" });
});
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "ToDo",
"property": "show_title_field_in_link",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "1"
}, true);
cy.clear_cache();
cy.wait(500);
cy.window().its('frappe').then(frappe => {
cy.insert_doc("Translation", {
doctype: "Translation",
language: frappe.boot.lang,
source_text: "this is a test todo for link",
translated_text: "this is a translated test todo for link",
});
});
get_dialog_with_gender_link().as("dialog");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.clear_cache();
cy.wait(500);
cy.window().its('frappe').then(frappe => {
if (!frappe.boot) {
frappe.boot = {
link_title_doctypes: ['ToDo'],
translatable_doctypes: ['ToDo']
};
} else {
frappe.boot.link_title_doctypes = ['ToDo'];
frappe.boot.translatable_doctypes = ['ToDo'];
}
});
get_dialog_with_link().as('dialog');
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
cy.wait('@search_link');
cy.get('@input').type('todo for link', { delay: 100 });
cy.wait('@search_link');
cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
cy.get('.frappe-control[data-fieldname=link] input').blur();
cy.get('@dialog').then(dialog => {
cy.get('@todos').then(todos => {
let field = dialog.get_field('link');
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.get("@input").type("Sonstiges", { delay: 100 });
cy.wait("@search_link");
cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
cy.get(".frappe-control[data-fieldname=link] input").blur();
cy.get("@dialog").then((dialog) => {
let field = dialog.get_field("link");
let value = field.get_value();
let label = field.get_label_value();
expect(value).to.eq(todos[0]);
expect(label).to.eq('this is a translated test todo for link');
expect(value).to.eq("Other");
expect(label).to.eq("Sonstiges");
});
});
});
it('show translated text for link with show_title_field_in_link disabled', () => {
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "User",
"property": "translate_link_fields",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "1"
}, true);
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "ToDo",
"property": "show_title_field_in_link",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "0"
}, true);
cy.window().its('frappe').then(frappe => {
cy.insert_doc("Translation", {
doctype: "Translation",
language: frappe.boot.lang,
source_text: "test@erpnext.com",
translated_text: "translatedtest@erpnext.com",
it("show text for Gender link field with language en", () => {
cy.window()
.its("frappe")
.then((frappe) => {
cy.set_value("User", frappe.user.name, { language: "en" });
});
});
cy.clear_cache();
cy.wait(500);
cy.window().its('frappe').then(frappe => {
if (!frappe.boot) {
frappe.boot = {
translatable_doctypes: ['User']
};
} else {
frappe.boot.translatable_doctypes = ['User'];
}
});
get_dialog_with_gender_link().as("dialog");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
get_dialog_with_user_link().as('dialog');
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
cy.wait('@search_link');
cy.get('@input').type('test@erpnext.com', { delay: 100 });
cy.wait('@search_link');
cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
cy.get('.frappe-control[data-fieldname=link] input').blur();
cy.get('@dialog').then(dialog => {
let field = dialog.get_field('link');
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.get("@input").type("Non-Conforming", { delay: 100 });
cy.wait("@search_link");
cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
cy.get(".frappe-control[data-fieldname=link] input").blur();
cy.get("@dialog").then((dialog) => {
let field = dialog.get_field("link");
let value = field.get_value();
let label = field.get_label_value();
expect(value).to.eq('test@erpnext.com');
expect(label).to.eq('translatedtest@erpnext.com');
expect(value).to.eq("Non-Conforming");
expect(label).to.eq("Non-Conforming");
});
});
it("show custom link option", () => {
cy.window()
.its("frappe")
.then((frappe) => {
frappe.ui.form.ControlLink.link_options = (link) => {
return [
{
html:
"<span class='text-primary custom-link-option'>" +
"<i class='fa fa-search' style='margin-right: 5px;'></i> " +
"Custom Link Option" +
"</span>",
label: "Custom Link Option",
value: "custom__link_option",
action: () => {},
},
];
};
get_dialog_with_link().as("dialog");
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.get("@input").type("custom", { delay: 100 });
cy.get(".custom-link-option").should("be.visible");
});
});
});

View file

@ -7,10 +7,10 @@ context("Control Markdown Editor", () => {
it("should allow inserting images by drag and drop", () => {
cy.visit("/app/web-page/new");
cy.fill_field("content_type", "Markdown", "Select");
cy.get_field("main_section_md", "Markdown Editor").attachFile(
"sample_image.jpg",
cy.get_field("main_section_md", "Markdown Editor").selectFile(
"cypress/fixtures/sample_image.jpg",
{
subjectType: "drag-n-drop"
action: "drag-drop",
}
);
cy.click_modal_primary_button("Upload");

View file

@ -1,4 +1,4 @@
import doctype_with_phone from '../fixtures/doctype_with_phone';
import doctype_with_phone from "../fixtures/doctype_with_phone";
context("Control Phone", () => {
before(() => {
@ -9,10 +9,12 @@ context("Control Phone", () => {
function get_dialog_with_phone() {
return cy.dialog({
title: "Phone",
fields: [{
"fieldname": "phone",
"fieldtype": "Phone",
}]
fields: [
{
fieldname: "phone",
fieldtype: "Phone",
},
],
});
}
@ -27,18 +29,16 @@ context("Control Phone", () => {
let phone_number = "9312672712";
cy.get(".selected-phone > img").click().first();
cy.get_field("phone")
.first()
.click({multiple: true});
cy.get_field("phone").first().click({ multiple: true });
cy.get(".frappe-control[data-fieldname=phone]")
.findByRole("textbox")
.first()
.type(phone_number, {force: true});
.type(phone_number, { force: true });
cy.get_field("phone").first().should("have.value", phone_number);
cy.get_field("phone").first().blur({force: true});
cy.get_field("phone").first().blur({ force: true });
cy.wait(100);
cy.get("@dialog").then(dialog => {
cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("phone");
expect(value).to.equal("+91-" + phone_number);
});
@ -48,10 +48,12 @@ context("Control Phone", () => {
let search_text = "india";
cy.get(".selected-phone").click().first();
cy.get(".phone-picker").findByRole("searchbox").click().type(search_text);
cy.get(".phone-section .phone-wrapper:not(.hidden)").then(i => {
cy.get(`.phone-section .phone-wrapper[id*="${search_text.toLowerCase()}"]`).then(countries => {
expect(i.length).to.equal(countries.length);
});
cy.get(".phone-section .phone-wrapper:not(.hidden)").then((i) => {
cy.get(`.phone-section .phone-wrapper[id*="${search_text.toLowerCase()}"]`).then(
(countries) => {
expect(i.length).to.equal(countries.length);
}
);
});
cy.get(".phone-picker").findByRole("searchbox").clear().blur();

View file

@ -1,56 +1,54 @@
context('Control Rating', () => {
context("Control Rating", () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
function get_dialog_with_rating() {
return cy.dialog({
title: 'Rating',
fields: [{
'fieldname': 'rate',
'fieldtype': 'Rating',
'options': 7
}]
title: "Rating",
fields: [
{
fieldname: "rate",
fieldtype: "Rating",
options: 7,
},
],
});
}
it('click on the star rating to record value', () => {
get_dialog_with_rating().as('dialog');
it("click on the star rating to record value", () => {
get_dialog_with_rating().as("dialog");
cy.get('div.rating')
.children('svg')
.find('.right-half')
cy.get("div.rating")
.children("svg")
.find(".right-half")
.first()
.click()
.should('have.class', 'star-click');
cy.get('@dialog').then(dialog => {
var value = dialog.get_value('rate');
expect(value).to.equal(1/7);
.should("have.class", "star-click");
cy.get("@dialog").then((dialog) => {
var value = dialog.get_value("rate");
expect(value).to.equal(1 / 7);
dialog.hide();
});
});
it('hover on the star', () => {
it("hover on the star", () => {
get_dialog_with_rating();
cy.get('div.rating')
.children('svg')
.find('.right-half')
cy.get("div.rating")
.children("svg")
.find(".right-half")
.first()
.invoke('trigger', 'mouseenter')
.should('have.class', 'star-hover')
.invoke('trigger', 'mouseleave')
.should('not.have.class', 'star-hover');
.invoke("trigger", "mouseenter")
.should("have.class", "star-hover")
.invoke("trigger", "mouseleave")
.should("not.have.class", "star-hover");
});
it('check number of stars in rating', () => {
it("check number of stars in rating", () => {
get_dialog_with_rating();
cy.get('div.rating')
.first()
.children('svg')
.should('have.length', 7);
cy.get("div.rating").first().children("svg").should("have.length", 7);
});
});

View file

@ -1,37 +1,40 @@
context('Control Select', () => {
context("Control Select", () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
function get_dialog_with_select() {
return cy.dialog({
title: 'Select',
fields: [{
'fieldname': 'select_control',
'fieldtype': 'Select',
'placeholder': 'Select an Option',
'options': ['', 'Option 1', 'Option 2', 'Option 2'],
}]
title: "Select",
fields: [
{
fieldname: "select_control",
fieldtype: "Select",
placeholder: "Select an Option",
options: ["", "Option 1", "Option 2", "Option 2"],
},
],
});
}
it('toggles placholder on clicking an option', () => {
get_dialog_with_select().as('dialog');
it("toggles placholder on clicking an option", () => {
get_dialog_with_select().as("dialog");
cy.get('.frappe-control[data-fieldname=select_control] .control-input').as('control');
cy.get('.frappe-control[data-fieldname=select_control] .control-input select').as('select');
cy.get('@control').get('.select-icon').should('exist');
cy.get('@control').get('.placeholder').should('have.css', 'display', 'block');
cy.get('@select').select('Option 1');
cy.findByDisplayValue('Option 1').should('exist');
cy.get('@control').get('.placeholder').should('have.css', 'display', 'none');
cy.get('@select').invoke('val', '');
cy.findByDisplayValue('Option 1').should('not.exist');
cy.get('@control').get('.placeholder').should('have.css', 'display', 'block');
cy.get(".frappe-control[data-fieldname=select_control] .control-input").as("control");
cy.get(".frappe-control[data-fieldname=select_control] .control-input select").as(
"select"
);
cy.get("@control").get(".select-icon").should("exist");
cy.get("@control").get(".placeholder").should("have.css", "display", "block");
cy.get("@select").select("Option 1");
cy.findByDisplayValue("Option 1").should("exist");
cy.get("@control").get(".placeholder").should("have.css", "display", "none");
cy.get("@select").invoke("val", "");
cy.findByDisplayValue("Option 1").should("not.exist");
cy.get("@control").get(".placeholder").should("have.css", "display", "block");
cy.get('@dialog').then(dialog => {
cy.get("@dialog").then((dialog) => {
dialog.hide();
});
});

View file

@ -31,10 +31,7 @@ const check_button_count = (label, group = "TestGroup") => {
.should("be.visible");
//reset viewport
cy.viewport(
Cypress.config("viewportWidth"),
Cypress.config("viewportHeight")
);
cy.viewport(Cypress.config("viewportWidth"), Cypress.config("viewportHeight"));
};
describe(

View file

@ -1,19 +1,19 @@
context('Customize Form', () => {
context("Customize Form", () => {
before(() => {
cy.login();
cy.visit('/app/customize-form');
cy.visit("/app/customize-form");
});
it('Changing to naming rule should update autoname', () => {
it("Changing to naming rule should update autoname", () => {
cy.fill_field("doc_type", "ToDo", "Link").blur();
cy.click_form_section("Naming");
const naming_rule_default_autoname_map = {
"Set by user": "prompt",
"By fieldname": "field:",
'By "Naming Series" field': "naming_series:",
"Expression": "format:",
Expression: "format:",
"Expression (old style)": "",
"Random": "hash",
"By script": ""
Random: "hash",
"By script": "",
};
Cypress._.forOwn(naming_rule_default_autoname_map, (value, naming_rule) => {
cy.fill_field("naming_rule", naming_rule, "Select");

View file

@ -0,0 +1,50 @@
describe("Dashboard view", { scrollBehavior: false }, () => {
before(() => {
cy.login();
cy.visit("/app");
});
it("should load", () => {
const chart = "TODO-YEARLY-TRENDS";
const dashboard = "TODO-TEST-DASHBOARD"; // check slash in name intentionally.
cy.insert_doc(
"Dashboard Chart",
{
is_standard: 0,
chart_name: chart,
chart_type: "Count",
document_type: "ToDo",
parent_document_type: "",
based_on: "creation",
group_by_type: "Count",
timespan: "Last Year",
time_interval: "Yearly",
timeseries: 1,
type: "Line",
filters_json: "[]",
},
true
);
cy.insert_doc(
"Dashboard",
{
name: dashboard,
dashboard_name: dashboard,
is_standard: 0,
charts: [
{
chart: chart,
},
],
},
true
);
cy.visit(`/app/dashboard-view/${dashboard}`);
// expect chart to be loaded
cy.findByText(chart).should("be.visible");
});
});

View file

@ -1,22 +1,22 @@
context('Dashboard Chart', () => {
context("Dashboard Chart", () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
it('Check filter populate for child table doctype', () => {
cy.visit('/app/dashboard-chart/new-dashboard-chart-1');
cy.get('[data-fieldname="parent_document_type"]').should('have.css', 'display', 'none');
it("Check filter populate for child table doctype", () => {
cy.visit("/app/dashboard-chart/new-dashboard-chart-1");
cy.get('[data-fieldname="parent_document_type"]').should("have.css", "display", "none");
cy.get_field('document_type', 'Link');
cy.fill_field('document_type', 'Workspace Link', 'Link').focus().blur();
cy.get_field('document_type', 'Link').should('have.value', 'Workspace Link');
cy.get_field("document_type", "Link");
cy.fill_field("document_type", "Workspace Link", "Link").focus().blur();
cy.get_field("document_type", "Link").should("have.value", "Workspace Link");
cy.fill_field('chart_name', 'Test Chart', 'Data');
cy.fill_field("chart_name", "Test Chart", "Data");
cy.get('[data-fieldname="filters_json"]').click().wait(200);
cy.get('.modal-body .filter-action-buttons .add-filter').click();
cy.get('.modal-body .fieldname-select-area').click();
cy.get('.modal-actions .btn-modal-close').click();
cy.get(".modal-body .filter-action-buttons .add-filter").click();
cy.get(".modal-body .fieldname-select-area").click();
cy.get(".modal-actions .btn-modal-close").click();
});
});
});

View file

@ -1,91 +1,94 @@
import doctype_with_child_table from '../fixtures/doctype_with_child_table';
import child_table_doctype from '../fixtures/child_table_doctype';
import child_table_doctype_1 from '../fixtures/child_table_doctype_1';
import doctype_to_link from '../fixtures/doctype_to_link';
import doctype_with_child_table from "../fixtures/doctype_with_child_table";
import child_table_doctype from "../fixtures/child_table_doctype";
import child_table_doctype_1 from "../fixtures/child_table_doctype_1";
import doctype_to_link from "../fixtures/doctype_to_link";
const doctype_to_link_name = doctype_to_link.name;
const child_table_doctype_name = child_table_doctype.name;
context('Dashboard links', () => {
context("Dashboard links", () => {
before(() => {
cy.visit('/login');
cy.login();
cy.insert_doc('DocType', child_table_doctype, true);
cy.insert_doc('DocType', child_table_doctype_1, true);
cy.insert_doc('DocType', doctype_with_child_table, true);
cy.insert_doc('DocType', doctype_to_link, true);
return cy.window().its('frappe').then(frappe => {
return frappe.xcall("frappe.tests.ui_test_helpers.update_child_table", {
name: child_table_doctype_name
cy.visit("/login");
cy.login("Administrator");
cy.insert_doc("DocType", child_table_doctype, true);
cy.insert_doc("DocType", child_table_doctype_1, true);
cy.insert_doc("DocType", doctype_with_child_table, true);
cy.insert_doc("DocType", doctype_to_link, true);
return cy
.window()
.its("frappe")
.then((frappe) => {
frappe.call("frappe.tests.ui_test_helpers.update_child_table", {
name: child_table_doctype_name,
});
});
});
});
it('Adding a new contact, checking for the counter on the dashboard and deleting the created contact', () => {
cy.visit('/app/contact');
it("Adding a new contact, checking for the counter on the dashboard and deleting the created contact", () => {
cy.visit("/app/contact");
cy.clear_filters();
cy.visit('/app/user');
cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click({ force: true });
cy.visit(`/app/user/${cy.config("testUser")}`);
//To check if initially the dashboard contains only the "Contact" link and there is no counter
cy.get('[data-doctype="Contact"]').should('contain', 'Contact');
cy.select_form_tab("Connections");
cy.get('[data-doctype="Contact"]').should("contain", "Contact");
//Adding a new contact
cy.get('.document-link-badge[data-doctype="Contact"]').click();
cy.wait(300);
cy.findByRole('button', {name: 'Add Contact'}).should('be.visible');
cy.findByRole('button', {name: 'Add Contact'}).click();
cy.get('[data-doctype="Contact"][data-fieldname="first_name"]').type('Admin');
cy.findByRole('button', {name: 'Save'}).click();
cy.visit('/app/user');
cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click({ force: true });
cy.findByRole("button", { name: "Add Contact" }).should("be.visible");
cy.findByRole("button", { name: "Add Contact" }).click();
cy.get('[data-doctype="Contact"][data-fieldname="first_name"]').type("Admin");
cy.findByRole("button", { name: "Save" }).click();
cy.visit(`/app/user/${cy.config("testUser")}`);
//To check if the counter for contact doc is "1" after adding the contact
cy.get('[data-doctype="Contact"] > .count').should('contain', '1');
cy.get('[data-doctype="Contact"]').contains('Contact').click();
//To check if the counter for contact doc is "2" after adding additional contact
cy.select_form_tab("Connections");
cy.get('[data-doctype="Contact"] > .count').should("contain", "2");
cy.get('[data-doctype="Contact"]').contains("Contact").click();
//Deleting the newly created contact
cy.visit('/app/contact');
cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click({ force: true });
cy.findByRole('button', {name: 'Actions'}).click();
cy.visit("/app/contact");
cy.get(".list-subject > .select-like > .list-row-checkbox").eq(0).click({ force: true });
cy.findByRole("button", { name: "Actions" }).click();
cy.get('.actions-btn-group [data-label="Delete"]').click();
cy.findByRole('button', {name: 'Yes'}).click({delay: 700});
cy.findByRole("button", { name: "Yes" }).click({ delay: 700 });
//To check if the counter from the "Contact" doc link is removed
cy.wait(700);
cy.visit('/app/user');
cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click({ force: true });
cy.get('[data-doctype="Contact"]').should('contain', 'Contact');
cy.visit("/app/user");
cy.get(".list-row-col > .level-item > .ellipsis").eq(0).click({ force: true });
cy.get('[data-doctype="Contact"]').should("contain", "Contact");
});
it('Report link in dashboard', () => {
cy.visit('/app/user');
cy.visit('/app/user/Administrator');
cy.get('[data-doctype="Contact"]').should('contain', 'Contact');
cy.findByText('Connections');
it("Report link in dashboard", () => {
cy.visit(`/app/user/${cy.config("testUser")}`);
cy.select_form_tab("Connections");
cy.get('.document-link[data-doctype="Contact"]').contains("Contact");
cy.window()
.its('cur_frm')
.then(cur_frm => {
.its("cur_frm")
.then((cur_frm) => {
cur_frm.dashboard.data.reports = [
{
'label': 'Reports',
'items': ['Website Analytics']
}
label: "Reports",
items: ["Website Analytics"],
},
];
cur_frm.dashboard.render_report_links();
cy.get('[data-report="Website Analytics"]').contains('Website Analytics').click();
cy.findByText('Website Analytics');
cy.get('.document-link[data-report="Website Analytics"]')
.contains("Website Analytics")
.click();
});
});
it('check if child table is populated with linked field on creation from dashboard link', () => {
it("check if child table is populated with linked field on creation from dashboard link", () => {
cy.new_form(doctype_to_link_name);
cy.fill_field("title", "Test Linking");
cy.findByRole("button", {name: "Save"}).click();
cy.findByRole("button", { name: "Save" }).click();
cy.get('.document-link .btn-new').click();
cy.get('.frappe-control[data-fieldname="child_table"] .rows .data-row .col[data-fieldname="doctype_to_link"]')
.should('contain.text', 'Test Linking');
cy.get(".document-link .btn-new").click();
cy.get(
'.frappe-control[data-fieldname="child_table"] .rows .data-row .col[data-fieldname="doctype_to_link"]'
).should("contain.text", "Test Linking");
});
});

View file

@ -1,43 +1,45 @@
import data_field_validation_doctype from '../fixtures/data_field_validation_doctype';
import data_field_validation_doctype from "../fixtures/data_field_validation_doctype";
const doctype_name = data_field_validation_doctype.name;
context('Data Field Input Validation in New Form', () => {
context("Data Field Input Validation in New Form", () => {
before(() => {
cy.login();
cy.visit('/app/website');
return cy.insert_doc('DocType', data_field_validation_doctype, true);
cy.visit("/app/website");
return cy.insert_doc("DocType", data_field_validation_doctype, true);
});
function validateField(fieldname, invalid_value, valid_value) {
// Invalid, should have has-error class
cy.get_field(fieldname).clear().type(invalid_value).blur();
cy.get(`.frappe-control[data-fieldname="${fieldname}"]`).should('have.class', 'has-error');
cy.get(`.frappe-control[data-fieldname="${fieldname}"]`).should("have.class", "has-error");
// Valid value, should not have has-error class
cy.get_field(fieldname).clear().type(valid_value);
cy.get(`.frappe-control[data-fieldname="${fieldname}"]`).should('not.have.class', 'has-error');
cy.get(`.frappe-control[data-fieldname="${fieldname}"]`).should(
"not.have.class",
"has-error"
);
}
describe('Data Field Options', () => {
it('should validate email address', () => {
describe("Data Field Options", () => {
it("should validate email address", () => {
cy.new_form(doctype_name);
validateField('email', 'captian', 'hello@test.com');
validateField("email", "captian", "hello@test.com");
});
it('should validate URL', () => {
validateField('url', 'jkl', 'https://frappe.io');
validateField('url', 'abcd.com', 'http://google.com/home');
validateField('url', '&&http://google.uae', 'gopher://frappe.io');
validateField('url', 'ftt2:://google.in?q=news', 'ftps2://frappe.io/__/#home');
validateField('url', 'ftt2://', 'ntps://localhost'); // For intranet URLs
it("should validate URL", () => {
validateField("url", "jkl", "https://frappe.io");
validateField("url", "abcd.com", "http://google.com/home");
validateField("url", "&&http://google.uae", "gopher://frappe.io");
validateField("url", "ftt2:://google.in?q=news", "ftps2://frappe.io/__/#home");
validateField("url", "ftt2://", "ntps://localhost"); // For intranet URLs
});
it('should validate phone number', () => {
validateField('phone', 'america', '89787878');
it("should validate phone number", () => {
validateField("phone", "america", "89787878");
});
it('should validate name', () => {
validateField('person_name', ' 777Hello', 'James Bond');
it("should validate name", () => {
validateField("person_name", " 777Hello", "James Bond");
});
});
});
});

View file

@ -1,53 +1,52 @@
import datetime_doctype from '../fixtures/datetime_doctype';
import datetime_doctype from "../fixtures/datetime_doctype";
const doctype_name = datetime_doctype.name;
context('Control Date, Time and DateTime', () => {
context("Control Date, Time and DateTime", () => {
before(() => {
cy.login();
cy.visit('/app/website');
return cy.insert_doc('DocType', datetime_doctype, true);
cy.visit("/app/website");
return cy.insert_doc("DocType", datetime_doctype, true);
});
describe('Date formats', () => {
describe("Date formats", () => {
let date_formats = [
{
date_format: 'dd-mm-yyyy',
date_format: "dd-mm-yyyy",
part: 2,
length: 4,
separator: '-'
separator: "-",
},
{
date_format: 'mm/dd/yyyy',
date_format: "mm/dd/yyyy",
part: 0,
length: 2,
separator: '/'
}
separator: "/",
},
];
date_formats.forEach(d => {
it('test date format ' + d.date_format, () => {
cy.set_value('System Settings', 'System Settings', {
date_format: d.date_format
date_formats.forEach((d) => {
it("test date format " + d.date_format, () => {
cy.set_value("System Settings", "System Settings", {
date_format: d.date_format,
});
cy.window()
.its('frappe')
.then(frappe => {
.its("frappe")
.then((frappe) => {
// update sys_defaults value to avoid a reload
frappe.sys_defaults.date_format = d.date_format;
});
cy.new_form(doctype_name);
cy.get('.form-control[data-fieldname=date]').focus();
cy.get('.datepickers-container .datepicker.active')
.should('be.visible');
cy.get(".form-control[data-fieldname=date]").focus();
cy.get(".datepickers-container .datepicker.active").should("be.visible");
cy.get(
'.datepickers-container .datepicker.active .datepicker--cell-day.-current-'
".datepickers-container .datepicker.active .datepicker--cell-day.-current-"
).click({ force: true });
cy.window()
.its('cur_frm')
.then(cur_frm => {
let formatted_value = cur_frm.get_field('date').input.value;
.its("cur_frm")
.then((cur_frm) => {
let formatted_value = cur_frm.get_field("date").input.value;
let parts = formatted_value.split(d.separator);
expect(parts[d.part].length).to.equal(d.length);
});
@ -55,74 +54,72 @@ context('Control Date, Time and DateTime', () => {
});
});
describe('Time formats', () => {
describe("Time formats", () => {
let time_formats = [
{
time_format: 'HH:mm:ss',
value: ' 11:00:12',
match_value: '11:00:12'
time_format: "HH:mm:ss",
value: " 11:00:12",
match_value: "11:00:12",
},
{
time_format: 'HH:mm',
value: ' 11:00:12',
match_value: '11:00'
}
time_format: "HH:mm",
value: " 11:00:12",
match_value: "11:00",
},
];
time_formats.forEach(d => {
it('test time format ' + d.time_format, () => {
cy.set_value('System Settings', 'System Settings', {
time_format: d.time_format
time_formats.forEach((d) => {
it("test time format " + d.time_format, () => {
cy.set_value("System Settings", "System Settings", {
time_format: d.time_format,
});
cy.window()
.its('frappe')
.then(frappe => {
.its("frappe")
.then((frappe) => {
frappe.sys_defaults.time_format = d.time_format;
});
cy.new_form(doctype_name);
cy.fill_field('time', d.value, 'Time').blur();
cy.get_field('time').should('have.value', d.match_value);
cy.fill_field("time", d.value, "Time").blur();
cy.get_field("time").should("have.value", d.match_value);
});
});
});
describe('DateTime formats', () => {
describe("DateTime formats", () => {
let datetime_formats = [
{
date_format: 'dd.mm.yyyy',
time_format: 'HH:mm:ss',
value: ' 02.12.2019 11:00:12',
doc_value: '2019-12-02 00:30:12', // system timezone (America/New_York)
input_value: '02.12.2019 11:00:12' // admin timezone (Asia/Kolkata)
date_format: "dd.mm.yyyy",
time_format: "HH:mm:ss",
value: " 02.12.2019 11:00:12",
doc_value: "2019-12-02 00:30:12", // system timezone (America/New_York)
input_value: "02.12.2019 11:00:12", // admin timezone (Asia/Kolkata)
},
{
date_format: 'mm-dd-yyyy',
time_format: 'HH:mm',
value: ' 12-02-2019 11:00:00',
doc_value: '2019-12-02 00:30:00', // system timezone (America/New_York)
input_value: '12-02-2019 11:00' // admin timezone (Asia/Kolkata)
}
date_format: "mm-dd-yyyy",
time_format: "HH:mm",
value: " 12-02-2019 11:00:00",
doc_value: "2019-12-02 00:30:00", // system timezone (America/New_York)
input_value: "12-02-2019 11:00", // admin timezone (Asia/Kolkata)
},
];
datetime_formats.forEach(d => {
datetime_formats.forEach((d) => {
it(`test datetime format ${d.date_format} ${d.time_format}`, () => {
cy.set_value('System Settings', 'System Settings', {
cy.set_value("System Settings", "System Settings", {
date_format: d.date_format,
time_format: d.time_format
time_format: d.time_format,
});
cy.window()
.its('frappe')
.then(frappe => {
.its("frappe")
.then((frappe) => {
frappe.sys_defaults.date_format = d.date_format;
frappe.sys_defaults.time_format = d.time_format;
});
cy.new_form(doctype_name);
cy.fill_field('datetime', d.value, 'Datetime').blur();
cy.get_field('datetime').should('have.value', d.input_value);
cy.fill_field("datetime", d.value, "Datetime").blur();
cy.get_field("datetime").should("have.value", d.input_value);
cy.window()
.its('cur_frm.doc.datetime')
.should('eq', d.doc_value);
cy.window().its("cur_frm.doc.datetime").should("eq", d.doc_value);
});
});
});

View file

@ -16,4 +16,4 @@
// cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red');
// });
// });
// });
// });

View file

@ -1,135 +1,152 @@
context('Depends On', () => {
context("Depends On", () => {
before(() => {
cy.login();
cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => {
return frappe.xcall('frappe.tests.ui_test_helpers.create_child_doctype', {
name: 'Child Test Depends On',
fields: [
{
"label": "Child Test Field",
"fieldname": "child_test_field",
"fieldtype": "Data",
"in_list_view": 1,
},
{
"label": "Child Dependant Field",
"fieldname": "child_dependant_field",
"fieldtype": "Data",
"in_list_view": 1,
},
{
"label": "Child Display Dependant Field",
"fieldname": "child_display_dependant_field",
"fieldtype": "Data",
"in_list_view": 1,
},
]
cy.visit("/app/website");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.xcall("frappe.tests.ui_test_helpers.create_child_doctype", {
name: "Child Test Depends On",
fields: [
{
label: "Child Test Field",
fieldname: "child_test_field",
fieldtype: "Data",
in_list_view: 1,
},
{
label: "Child Dependant Field",
fieldname: "child_dependant_field",
fieldtype: "Data",
in_list_view: 1,
},
{
label: "Child Display Dependant Field",
fieldname: "child_display_dependant_field",
fieldtype: "Data",
in_list_view: 1,
},
],
});
})
.then((frappe) => {
return frappe.xcall("frappe.tests.ui_test_helpers.create_doctype", {
name: "Test Depends On",
fields: [
{
label: "Test Field",
fieldname: "test_field",
fieldtype: "Data",
},
{
label: "Dependant Field",
fieldname: "dependant_field",
fieldtype: "Data",
mandatory_depends_on: "eval:doc.test_field=='Some Value'",
read_only_depends_on: "eval:doc.test_field=='Some Other Value'",
},
{
label: "Display Dependant Field",
fieldname: "display_dependant_field",
fieldtype: "Data",
depends_on: "eval:doc.test_field=='Value'",
},
{
label: "Child Test Depends On Field",
fieldname: "child_test_depends_on_field",
fieldtype: "Table",
read_only_depends_on: "eval:doc.test_field=='Some Other Value'",
options: "Child Test Depends On",
},
{
label: "Dependent Tab",
fieldname: "dependent_tab",
fieldtype: "Tab Break",
depends_on: "eval:doc.test_field=='Show Tab'",
},
{
fieldname: "tab_section",
fieldtype: "Section Break",
},
{
label: "Field in Tab",
fieldname: "field_in_tab",
fieldtype: "Data",
},
],
});
});
}).then(frappe => {
return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
name: 'Test Depends On',
fields: [
{
"label": "Test Field",
"fieldname": "test_field",
"fieldtype": "Data",
},
{
"label": "Dependant Field",
"fieldname": "dependant_field",
"fieldtype": "Data",
"mandatory_depends_on": "eval:doc.test_field=='Some Value'",
"read_only_depends_on": "eval:doc.test_field=='Some Other Value'",
},
{
"label": "Display Dependant Field",
"fieldname": "display_dependant_field",
"fieldtype": "Data",
'depends_on': "eval:doc.test_field=='Value'"
},
{
"label": "Child Test Depends On Field",
"fieldname": "child_test_depends_on_field",
"fieldtype": "Table",
'read_only_depends_on': "eval:doc.test_field=='Some Other Value'",
'options': "Child Test Depends On"
},
{
"label": "Dependent Tab",
"fieldname": "dependent_tab",
"fieldtype": "Tab Break",
"depends_on": "eval:doc.test_field=='Show Tab'"
},
{
"fieldname": "tab_section",
"fieldtype": "Section Break",
},
{
"label": "Field in Tab",
"fieldname": "field_in_tab",
"fieldtype": "Data",
}
]
});
});
});
it('should show the tab on other setting field value', () => {
cy.new_form('Test Depends On');
cy.fill_field('test_field', 'Show Tab');
cy.get('body').click();
cy.findByRole("tab", {name: "Dependent Tab"}).should('be.visible');
it("should show the tab on other setting field value", () => {
cy.new_form("Test Depends On");
cy.fill_field("test_field", "Show Tab");
cy.get("body").click();
cy.findByRole("tab", { name: "Dependent Tab" }).should("be.visible");
});
it('should set the field as mandatory depending on other fields value', () => {
cy.new_form('Test Depends On');
cy.fill_field('test_field', 'Some Value');
cy.findByRole('button', {name: 'Save'}).click();
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('be.visible');
it("should set the field as mandatory depending on other fields value", () => {
cy.new_form("Test Depends On");
cy.fill_field("test_field", "Some Value");
cy.findByRole("button", { name: "Save" }).click();
cy.get(".msgprint-dialog .modal-title").contains("Missing Fields").should("be.visible");
cy.hide_dialog();
cy.fill_field('test_field', 'Random value');
cy.findByRole('button', {name: 'Save'}).click();
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('not.be.visible');
cy.fill_field("test_field", "Random value");
cy.findByRole("button", { name: "Save" }).click();
cy.get(".msgprint-dialog .modal-title")
.contains("Missing Fields")
.should("not.be.visible");
});
it('should set the field as read only depending on other fields value', () => {
cy.new_form('Test Depends On');
cy.fill_field('dependant_field', 'Some Value');
cy.fill_field('test_field', 'Some Other Value');
cy.get('body').click();
cy.get('.control-input [data-fieldname="dependant_field"]').should('be.disabled');
cy.fill_field('test_field', 'Random Value');
cy.get('body').click();
cy.get('.control-input [data-fieldname="dependant_field"]').should('not.be.disabled');
it("should set the field as read only depending on other fields value", () => {
cy.new_form("Test Depends On");
cy.fill_field("dependant_field", "Some Value");
cy.fill_field("test_field", "Some Other Value");
cy.get("body").click();
cy.get('.control-input [data-fieldname="dependant_field"]').should("be.disabled");
cy.fill_field("test_field", "Random Value");
cy.get("body").click();
cy.get('.control-input [data-fieldname="dependant_field"]').should("not.be.disabled");
});
it('should set the table and its fields as read only depending on other fields value', () => {
cy.new_form('Test Depends On');
cy.fill_field('dependant_field', 'Some Value');
it("should set the table and its fields as read only depending on other fields value", () => {
cy.new_form("Test Depends On");
cy.fill_field("dependant_field", "Some Value");
//cy.fill_field('test_field', 'Some Other Value');
cy.get('.frappe-control[data-fieldname="child_test_depends_on_field"]').as('table');
cy.get('@table').findByRole('button', {name: 'Add Row'}).click();
cy.get('@table').find('[data-idx="1"]').as('row1');
cy.get('@row1').find('.btn-open-row').click();
cy.get('@row1').find('.form-in-grid').as('row1-form_in_grid');
cy.get('.frappe-control[data-fieldname="child_test_depends_on_field"]').as("table");
cy.get("@table").findByRole("button", { name: "Add Row" }).click();
cy.get("@table").find('[data-idx="1"]').as("row1");
cy.get("@row1").find(".btn-open-row").click();
cy.get("@row1").find(".form-in-grid").as("row1-form_in_grid");
//cy.get('@row1-form_in_grid').find('')
cy.fill_table_field('child_test_depends_on_field', '1', 'child_test_field', 'Some Value');
cy.fill_table_field('child_test_depends_on_field', '1', 'child_dependant_field', 'Some Other Value');
cy.fill_table_field("child_test_depends_on_field", "1", "child_test_field", "Some Value");
cy.fill_table_field(
"child_test_depends_on_field",
"1",
"child_dependant_field",
"Some Other Value"
);
cy.get('@row1-form_in_grid').find('.grid-collapse-row').click();
cy.get("@row1-form_in_grid").find(".grid-collapse-row").click();
// set the table to read-only
cy.fill_field('test_field', 'Some Other Value');
cy.fill_field("test_field", "Some Other Value");
// grid row form fields should be read-only
cy.get('@row1').find('.btn-open-row').click();
cy.get("@row1").find(".btn-open-row").click();
cy.get('@row1-form_in_grid').find('.control-input [data-fieldname="child_test_field"]').should('be.disabled');
cy.get('@row1-form_in_grid').find('.control-input [data-fieldname="child_dependant_field"]').should('be.disabled');
cy.get("@row1-form_in_grid")
.find('.control-input [data-fieldname="child_test_field"]')
.should("be.disabled");
cy.get("@row1-form_in_grid")
.find('.control-input [data-fieldname="child_dependant_field"]')
.should("be.disabled");
});
it('should display the field depending on other fields value', () => {
cy.new_form('Test Depends On');
cy.get('.control-input [data-fieldname="display_dependant_field"]').should('not.be.visible');
it("should display the field depending on other fields value", () => {
cy.new_form("Test Depends On");
cy.get('.control-input [data-fieldname="display_dependant_field"]').should(
"not.be.visible"
);
cy.get('.control-input [data-fieldname="test_field"]').clear();
cy.fill_field('test_field', 'Value');
cy.get('body').click();
cy.get('.control-input [data-fieldname="display_dependant_field"]').should('be.visible');
cy.fill_field("test_field", "Value");
cy.get("body").click();
cy.get('.control-input [data-fieldname="display_dependant_field"]').should("be.visible");
});
});

View file

@ -1,79 +1,101 @@
context('Discussions', () => {
context("Discussions", () => {
before(() => {
cy.login();
cy.visit('/app');
return cy.window().its('frappe').then(frappe => {
return frappe.call('frappe.tests.ui_test_helpers.create_data_for_discussions');
});
cy.visit("/app");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.call("frappe.tests.ui_test_helpers.create_data_for_discussions");
});
});
const reply_through_modal = () => {
cy.visit('/test-page-discussions');
cy.visit("/test-page-discussions");
// Open the modal
cy.get('.reply').click();
cy.get(".reply").click();
cy.wait(500);
cy.get('.discussion-modal').should('be.visible');
cy.get(".discussion-modal").should("be.visible");
// Enter title
cy.get('.modal .topic-title').type('Discussion from tests')
.should('have.value', 'Discussion from tests');
cy.get(".modal .topic-title")
.type("Discussion from tests")
.should("have.value", "Discussion from tests");
// Enter comment
cy.get('.modal .comment-field')
.type('This is a discussion from the cypress ui tests.')
.should('have.value', 'This is a discussion from the cypress ui tests.');
cy.get(".modal .comment-field")
.type("This is a discussion from the cypress ui tests.")
.should("have.value", "This is a discussion from the cypress ui tests.");
// Submit
cy.get('.modal .submit-discussion').click();
cy.get(".modal .submit-discussion").click();
cy.wait(2000);
// Check if discussion is added to page and content is visible
cy.get('.sidebar-parent:first .discussion-topic-title').should('have.text', 'Discussion from tests');
cy.get('.discussion-on-page:visible').should('have.class', 'show');
cy.get('.discussion-on-page:visible .reply-card .reply-text')
.should('have.text', 'This is a discussion from the cypress ui tests.\n');
cy.get(".sidebar-parent:first .discussion-topic-title").should(
"have.text",
"Discussion from tests"
);
cy.get(".discussion-on-page:visible").should("have.class", "show");
cy.get(".discussion-on-page:visible .reply-card .reply-text").should(
"have.text",
"This is a discussion from the cypress ui tests.\n"
);
};
const reply_through_comment_box = () => {
cy.get('.discussion-form:visible .comment-field')
.type('This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.')
.should('have.value', 'This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.');
cy.get(".discussion-form:visible .comment-field")
.type(
"This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page."
)
.should(
"have.value",
"This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page."
);
cy.get('.discussion-form:visible .submit-discussion').click();
cy.get(".discussion-form:visible .submit-discussion").click();
cy.wait(3000);
cy.get('.discussion-on-page:visible').should('have.class', 'show');
cy.get('.discussion-on-page:visible').children(".reply-card").eq(1).find(".reply-text")
.should('have.text', 'This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.\n');
cy.get(".discussion-on-page:visible").should("have.class", "show");
cy.get(".discussion-on-page:visible")
.children(".reply-card")
.eq(1)
.find(".reply-text")
.should(
"have.text",
"This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.\n"
);
};
const cancel_and_clear_comment_box = () => {
cy.get('.discussion-form:visible .comment-field')
.type('This is a discussion from the cypress ui tests.')
.should('have.value', 'This is a discussion from the cypress ui tests.');
cy.get(".discussion-form:visible .comment-field")
.type("This is a discussion from the cypress ui tests.")
.should("have.value", "This is a discussion from the cypress ui tests.");
cy.get('.discussion-form:visible .cancel-comment').click();
cy.get('.discussion-form:visible .comment-field').should('have.value', '');
cy.get(".discussion-form:visible .cancel-comment").click();
cy.get(".discussion-form:visible .comment-field").should("have.value", "");
};
const single_thread_discussion = () => {
cy.visit('/test-single-thread');
cy.get('.discussions-sidebar').should('have.length', 0);
cy.get('.reply').should('have.length', 0);
cy.visit("/test-single-thread");
cy.get(".discussions-sidebar").should("have.length", 0);
cy.get(".reply").should("have.length", 0);
cy.get('.discussion-form:visible .comment-field')
.type('This comment is being made on a single thread discussion.')
.should('have.value', 'This comment is being made on a single thread discussion.');
cy.get(".discussion-form:visible .comment-field")
.type("This comment is being made on a single thread discussion.")
.should("have.value", "This comment is being made on a single thread discussion.");
cy.get('.discussion-form:visible .submit-discussion').click();
cy.get(".discussion-form:visible .submit-discussion").click();
cy.wait(3000);
cy.get('.discussion-on-page').children(".reply-card").eq(-1).find(".reply-text")
.should('have.text', 'This comment is being made on a single thread discussion.\n');
cy.get(".discussion-on-page")
.children(".reply-card")
.eq(-1)
.find(".reply-text")
.should("have.text", "This comment is being made on a single thread discussion.\n");
};
it('reply through modal', reply_through_modal);
it('reply through comment box', reply_through_comment_box);
it('cancel and clear comment box', cancel_and_clear_comment_box);
it('single thread discussion', single_thread_discussion);
it("reply through modal", reply_through_modal);
it("reply through comment box", reply_through_comment_box);
it("cancel and clear comment box", cancel_and_clear_comment_box);
it("single thread discussion", single_thread_discussion);
});

View file

@ -1,78 +1,86 @@
context('FileUploader', () => {
context("FileUploader", () => {
before(() => {
cy.login();
cy.visit('/app');
cy.visit("/app");
});
function open_upload_dialog() {
cy.window().its('frappe').then(frappe => {
new frappe.ui.FileUploader();
});
cy.window()
.its("frappe")
.then((frappe) => {
new frappe.ui.FileUploader();
});
}
it('upload dialog api works', () => {
it("upload dialog api works", () => {
open_upload_dialog();
cy.get_open_dialog().should('contain', 'Drag and drop files');
cy.get_open_dialog().should("contain", "Drag and drop files");
cy.hide_dialog();
});
it('should accept dropped files', () => {
it("should accept dropped files", () => {
open_upload_dialog();
cy.get_open_dialog().find('.file-upload-area').attachFile('example.json', {
subjectType: 'drag-n-drop',
});
cy.get_open_dialog().find('.file-name').should('contain', 'example.json');
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click();
cy.wait('@upload_file').its('response.statusCode').should('eq', 200);
cy.get('.modal:visible').should('not.exist');
});
it('should accept uploaded files', () => {
open_upload_dialog();
cy.get_open_dialog().findByRole('button', {name: 'Library'}).click();
cy.findByPlaceholderText('Search by filename or extension').type('example.json');
cy.get_open_dialog().findAllByText('example.json').first().click();
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click();
cy.wait('@upload_file').its('response.body.message')
.should('have.property', 'file_name', 'example.json');
cy.get('.modal:visible').should('not.exist');
});
it('should accept web links', () => {
open_upload_dialog();
cy.get_open_dialog().findByRole('button', {name: 'Link'}).click();
cy.get_open_dialog()
.findByPlaceholderText('Attach a web link')
.type('https://github.com', { delay: 100, force: true });
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click();
cy.wait('@upload_file').its('response.body.message')
.should('have.property', 'file_url', 'https://github.com');
cy.get('.modal:visible').should('not.exist');
.find(".file-upload-area")
.selectFile("cypress/fixtures/example.json", {
action: "drag-drop",
});
cy.get_open_dialog().find(".file-name").should("contain", "example.json");
cy.intercept("POST", "/api/method/upload_file").as("upload_file");
cy.get_open_dialog().findByRole("button", { name: "Upload" }).click();
cy.wait("@upload_file").its("response.statusCode").should("eq", 200);
cy.get(".modal:visible").should("not.exist");
});
it('should allow cropping and optimization for valid images', () => {
it("should accept uploaded files", () => {
open_upload_dialog();
cy.get_open_dialog().find('.file-upload-area').attachFile('sample_image.jpg', {
subjectType: 'drag-n-drop',
});
cy.get_open_dialog().findByRole("button", { name: "Library" }).click();
cy.findByPlaceholderText("Search by filename or extension").type("example.json");
cy.get_open_dialog().findAllByText("example.json").first().click();
cy.intercept("POST", "/api/method/upload_file").as("upload_file");
cy.get_open_dialog().findByRole("button", { name: "Upload" }).click();
cy.wait("@upload_file")
.its("response.body.message")
.should("have.property", "file_name", "example.json");
cy.get(".modal:visible").should("not.exist");
});
cy.get_open_dialog().findAllByText('sample_image.jpg').should('exist');
cy.get_open_dialog().find('.btn-crop').first().click();
cy.get_open_dialog().findByRole('button', {name: 'Crop'}).click();
cy.get_open_dialog().findAllByRole('checkbox', {name: 'Optimize'}).should('exist');
cy.get_open_dialog().findAllByLabelText('Optimize').first().click();
it("should accept web links", () => {
open_upload_dialog();
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click();
cy.wait('@upload_file').its('response.statusCode').should('eq', 200);
cy.get('.modal:visible').should('not.exist');
cy.get_open_dialog().findByRole("button", { name: "Link" }).click();
cy.get_open_dialog()
.findByPlaceholderText("Attach a web link")
.type("https://github.com", { delay: 100, force: true });
cy.intercept("POST", "/api/method/upload_file").as("upload_file");
cy.get_open_dialog().findByRole("button", { name: "Upload" }).click();
cy.wait("@upload_file")
.its("response.body.message")
.should("have.property", "file_url", "https://github.com");
cy.get(".modal:visible").should("not.exist");
});
it("should allow cropping and optimization for valid images", () => {
open_upload_dialog();
cy.get_open_dialog()
.find(".file-upload-area")
.selectFile("cypress/fixtures/sample_image.jpg", {
action: "drag-drop",
});
cy.get_open_dialog().findAllByText("sample_image.jpg").should("exist");
cy.get_open_dialog().find(".btn-crop").first().click();
cy.get_open_dialog().findByRole("button", { name: "Crop" }).click();
cy.get_open_dialog().findAllByRole("checkbox", { name: "Optimize" }).should("exist");
cy.get_open_dialog().findAllByLabelText("Optimize").first().click();
cy.intercept("POST", "/api/method/upload_file").as("upload_file");
cy.get_open_dialog().findByRole("button", { name: "Upload" }).click();
cy.wait("@upload_file").its("response.statusCode").should("eq", 200);
cy.get(".modal:visible").should("not.exist");
});
});

View file

@ -4,42 +4,48 @@ context("First Day of the Week", () => {
});
beforeEach(() => {
cy.visit('/app/system-settings');
cy.findByText('Date and Number Format').click();
cy.visit("/app/system-settings");
cy.findByText("Date and Number Format").click();
});
it("Date control starts with same day as selected in System Settings", () => {
cy.intercept('POST', '/api/method/frappe.core.doctype.system_settings.system_settings.load').as("load_settings");
cy.fill_field('first_day_of_the_week', 'Tuesday', 'Select');
cy.findByRole('button', {name: 'Save'}).click();
cy.intercept(
"POST",
"/api/method/frappe.core.doctype.system_settings.system_settings.load"
).as("load_settings");
cy.fill_field("first_day_of_the_week", "Tuesday", "Select");
cy.findByRole("button", { name: "Save" }).click();
cy.wait("@load_settings");
cy.dialog({
title: 'Date',
title: "Date",
fields: [
{
label: 'Date',
fieldname: 'date',
fieldtype: 'Date'
}
]
label: "Date",
fieldname: "date",
fieldtype: "Date",
},
],
});
cy.get_field('date').click();
cy.get('.datepicker--day-name').eq(0).should('have.text', 'Tu');
cy.get_field("date").click();
cy.get(".datepicker--day-name").eq(0).should("have.text", "Tu");
});
it("Calendar view starts with same day as selected in System Settings", () => {
cy.intercept('POST', '/api/method/frappe.core.doctype.system_settings.system_settings.load').as("load_settings");
cy.fill_field('first_day_of_the_week', 'Monday', 'Select');
cy.findByRole('button', {name: 'Save'}).click();
cy.intercept(
"POST",
"/api/method/frappe.core.doctype.system_settings.system_settings.load"
).as("load_settings");
cy.fill_field("first_day_of_the_week", "Monday", "Select");
cy.findByRole("button", { name: "Save" }).click();
cy.wait("@load_settings");
cy.visit("app/todo/view/calendar/default");
cy.get('.fc-day-header > span').eq(0).should('have.text', 'Mon');
cy.get(".fc-day-header > span").eq(0).should("have.text", "Mon");
});
after(() => {
cy.visit('/app/system-settings');
cy.findByText('Date and Number Format').click();
cy.fill_field('first_day_of_the_week', 'Sunday', 'Select');
cy.findByRole('button', {name: 'Save'}).click();
cy.visit("/app/system-settings");
cy.findByText("Date and Number Format").click();
cy.fill_field("first_day_of_the_week", "Sunday", "Select");
cy.findByRole("button", { name: "Save" }).click();
});
});
});

View file

@ -1,79 +1,96 @@
context('Folder Navigation', () => {
context("Folder Navigation", () => {
before(() => {
cy.visit('/login');
cy.visit("/login");
cy.login();
cy.visit('/app/file');
cy.visit("/app/file");
});
it('Adding Folders', () => {
it("Adding Folders", () => {
//Adding filter to go into the home folder
cy.get('.filter-selector > .btn').findByText('1 filter').click();
cy.findByRole('button', {name: 'Clear Filters'}).click();
cy.get('.filter-action-buttons > .text-muted').findByText('+ Add a Filter').click();
cy.get('.fieldname-select-area > .awesomplete > .form-control').type('Fol{enter}');
cy.get('.filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback').type('Home{enter}');
cy.get('.filter-action-buttons > div > .btn-primary').findByText('Apply Filters').click();
cy.get(".filter-x-button").click();
cy.click_filter_button();
cy.get(".filter-action-buttons > .text-muted").findByText("+ Add a Filter").click();
cy.get(".fieldname-select-area > .awesomplete > .form-control:last").type("Fol{enter}");
cy.get(
".filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback"
).type("Home{enter}");
cy.get(".filter-action-buttons > div > .btn-primary").findByText("Apply Filters").click();
//Adding folder (Test Folder)
cy.click_menu_button("New Folder");
cy.fill_field('value', 'Test Folder');
cy.click_modal_primary_button('Create');
cy.fill_field("value", "Test Folder");
cy.click_modal_primary_button("Create");
});
it('Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct', () => {
it("Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct", () => {
//Navigating inside the Attachments folder
cy.wait(500);
cy.get('[title="Attachments"] > span').click();
//To check if the URL formed after visiting the attachments folder is correct
cy.location('pathname').should('eq', '/app/file/view/home/Attachments');
cy.visit('/app/file/view/home/Attachments');
cy.location("pathname").should("eq", "/app/file/view/home/Attachments");
cy.visit("/app/file/view/home/Attachments");
//Adding folder inside the attachments folder
cy.click_menu_button("New Folder");
cy.fill_field('value', 'Test Folder');
cy.click_modal_primary_button('Create');
cy.fill_field("value", "Test Folder");
cy.click_modal_primary_button("Create");
//Navigating inside the added folder in the Attachments folder
cy.wait(500);
cy.get('[title="Test Folder"] > span').click();
//To check if the URL is correct after visiting the Test Folder
cy.location('pathname').should('eq', '/app/file/view/home/Attachments/Test%20Folder');
cy.visit('/app/file/view/home/Attachments/Test%20Folder');
cy.location("pathname").should("eq", "/app/file/view/home/Attachments/Test%20Folder");
cy.visit("/app/file/view/home/Attachments/Test%20Folder");
//Adding a file inside the Test Folder
cy.findByRole('button', {name: 'Add File'}).eq(0).click({force: true});
cy.get('.file-uploader').findByText('Link').click();
cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
cy.click_modal_primary_button('Upload');
cy.findByRole("button", { name: "Add File" }).eq(0).click({ force: true });
cy.get(".file-uploader").findByText("Link").click();
cy.get(".input-group > input.form-control:visible").as("upload_input");
cy.get("@upload_input").type("https://wallpaperplay.com/walls/full/8/2/b/72402.jpg", {
waitForAnimations: false,
parseSpecialCharSequences: false,
force: true,
delay: 100,
});
cy.click_modal_primary_button("Upload");
//To check if the added file is present in the Test Folder
cy.get('span.level-item > span').should('contain', 'Test Folder');
cy.get('.list-row-container').eq(0).should('contain.text', '72402.jpg');
cy.get('.list-row-checkbox').eq(0).click();
cy.visit("/app/file/view/home/Attachments");
cy.wait(500);
cy.get("span.level-item > a > span").should("contain", "Test Folder");
cy.visit("/app/file/view/home/Attachments/Test%20Folder");
cy.wait(500);
cy.get(".list-row-container").eq(0).should("contain.text", "72402.jpg");
cy.get(".list-row-checkbox").eq(0).click();
cy.intercept({
method: 'POST',
url: 'api/method/frappe.desk.reportview.delete_items'
}).as('file_deleted');
method: "POST",
url: "api/method/frappe.desk.reportview.delete_items",
}).as("file_deleted");
//Deleting the added file from the Test folder
cy.click_action_button("Delete");
cy.click_modal_primary_button('Yes');
cy.wait('@file_deleted');
cy.click_modal_primary_button("Yes");
cy.wait("@file_deleted");
//Deleting the Test Folder
cy.visit('/app/file/view/home/Attachments');
cy.get('.list-row-checkbox').eq(0).click();
cy.visit("/app/file/view/home/Attachments");
cy.get(".list-row-checkbox").eq(0).click();
cy.click_action_button("Delete");
cy.click_modal_primary_button('Yes');
cy.wait('@file_deleted');
cy.click_modal_primary_button("Yes");
cy.wait("@file_deleted");
});
it('Deleting Test Folder from the home', () => {
//Deleting the Test Folder added in the home directory
cy.visit('/app/file/view/home');
cy.get('.level-left > .list-subject > .file-select >.list-row-checkbox').eq(0).click({force: true, delay: 500});
it("Deleting Test Folder from the home", () => {
//Deleting the Test Folder added in the home directory
cy.visit("/app/file/view/home");
cy.get(".level-left > .list-subject > .file-select >.list-row-checkbox")
.eq(0)
.click({ force: true, delay: 500 });
cy.click_action_button("Delete");
cy.click_modal_primary_button('Yes');
cy.click_modal_primary_button("Yes");
});
});

View file

@ -1,98 +1,233 @@
context('Form', () => {
const jump_to_field = (field_label) => {
cy.get("body")
.type("{esc}") // lose focus if any
.type("{ctrl+j}") // jump to field
.type(field_label)
.wait(500)
.type("{enter}")
.wait(200)
.type("{enter}")
.wait(500);
};
const type_value = (value) => {
cy.focused().clear().type(value).type("{esc}");
};
context("Form", () => {
before(() => {
cy.login();
cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
});
cy.visit("/app/website");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
});
});
it('create a new form', () => {
cy.visit('/app/todo/new');
cy.get_field('description', 'Text Editor').type('this is a test todo', {force: true}).wait(200);
cy.get('.page-title').should('contain', 'Not Saved');
beforeEach(() => {
cy.login();
cy.visit("/app/website");
});
it("create a new form", () => {
cy.visit("/app/todo/new");
cy.get_field("description", "Text Editor")
.type("this is a test todo", { force: true })
.wait(200);
cy.get(".page-title").should("contain", "Not Saved");
cy.intercept({
method: 'POST',
url: 'api/method/frappe.desk.form.save.savedocs'
}).as('form_save');
cy.get('.primary-action').click();
cy.wait('@form_save').its('response.statusCode').should('eq', 200);
method: "POST",
url: "api/method/frappe.desk.form.save.savedocs",
}).as("form_save");
cy.get(".primary-action").click();
cy.wait("@form_save").its("response.statusCode").should("eq", 200);
cy.go_to_list('ToDo');
cy.clear_filters()
cy.get('.page-head').findByTitle('To Do').should('exist');
cy.get('.list-row').should('contain', 'this is a test todo');
cy.go_to_list("ToDo");
cy.clear_filters();
cy.get(".page-head").findByTitle("To Do").should("exist");
cy.get(".list-row").should("contain", "this is a test todo");
});
it('navigates between documents with child table list filters applied', () => {
cy.visit('/app/contact');
it("navigates between documents with child table list filters applied", () => {
cy.visit("/app/contact");
cy.clear_filters();
cy.get('.standard-filter-section [data-fieldname="name"] input').type('Test Form Contact 3').blur();
cy.click_listview_row_item_with_text('Test Form Contact 3');
cy.get('.standard-filter-section [data-fieldname="name"] input')
.type("Test Form Contact 3")
.blur();
cy.click_listview_row_item_with_text("Test Form Contact 3");
cy.get('#page-Contact .page-head').findByTitle('Test Form Contact 3').should('exist');
cy.get('.prev-doc').should('be.visible').click();
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist");
cy.get(".prev-doc").should("be.visible").click();
cy.get(".msgprint-dialog .modal-body").contains("No further records").should("be.visible");
cy.hide_dialog();
cy.get('#page-Contact .page-head').findByTitle('Test Form Contact 3').should('exist');
cy.get('.next-doc').should('be.visible').click();
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist");
cy.get(".next-doc").should("be.visible").click();
cy.get(".msgprint-dialog .modal-body").contains("No further records").should("be.visible");
cy.hide_dialog();
cy.get('#page-Contact .page-head').findByTitle('Test Form Contact 3').should('exist');
cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist");
// clear filters
cy.visit('/app/contact');
cy.visit("/app/contact");
cy.clear_filters();
});
it('validates behaviour of Data options validations in child table', () => {
it("validates behaviour of Data options validations in child table", () => {
// test email validations for set_invalid controller
let website_input = 'website.in';
let valid_email = 'user@email.com';
let expectBackgroundColor = 'rgb(255, 245, 245)';
let website_input = "website.in";
let valid_email = "user@email.com";
let expectBackgroundColor = "rgb(255, 245, 245)";
cy.visit('/app/contact/new');
cy.get('.frappe-control[data-fieldname="email_ids"]').as('table');
cy.get('@table').find('button.grid-add-row').click();
cy.get('@table').find('button.grid-add-row').click();
cy.get('@table').find('[data-idx="1"]').as('row1');
cy.get('@table').find('[data-idx="2"]').as('row2');
cy.get('@row1').click();
cy.get('@row1').find('input.input-with-feedback.form-control').as('email_input1');
cy.visit("/app/contact/new");
cy.get('.frappe-control[data-fieldname="email_ids"]').as("table");
cy.get("@table").find("button.grid-add-row").click();
cy.get("@table").find("button.grid-add-row").click();
cy.get("@table").find('[data-idx="1"]').as("row1");
cy.get("@table").find('[data-idx="2"]').as("row2");
cy.get("@row1").click();
cy.get("@row1").find("input.input-with-feedback.form-control").as("email_input1");
cy.get('@email_input1').type(website_input, { waitForAnimations: false });
cy.fill_field('company_name', 'Test Company');
cy.get("@email_input1").type(website_input, { waitForAnimations: false });
cy.fill_field("company_name", "Test Company");
cy.get('@row2').click();
cy.get('@row2').find('input.input-with-feedback.form-control').as('email_input2');
cy.get('@email_input2').type(valid_email, { waitForAnimations: false });
cy.get("@row2").click();
cy.get("@row2").find("input.input-with-feedback.form-control").as("email_input2");
cy.get("@email_input2").type(valid_email, { waitForAnimations: false });
cy.get('@row1').click();
cy.get('@email_input1').should($div => {
cy.get("@row1").click();
cy.get("@email_input1").should(($div) => {
const style = window.getComputedStyle($div[0]);
expect(style.backgroundColor).to.equal(expectBackgroundColor);
});
cy.get('@email_input1').should('have.class', 'invalid');
cy.get("@email_input1").should("have.class", "invalid");
cy.get('@row2').click();
cy.get('@email_input2').should('not.have.class', 'invalid');
cy.get("@row2").click();
cy.get("@email_input2").should("not.have.class", "invalid");
});
it('Shows version conflict warning', { scrollBehavior: false }, () => {
cy.visit('/app/todo');
it("Shows version conflict warning", { scrollBehavior: false }, () => {
cy.visit("/app/todo");
cy.insert_doc("ToDo", {"description": "old"}).then(doc => {
cy.insert_doc("ToDo", { description: "old" }).then((doc) => {
cy.visit(`/app/todo/${doc.name}`);
// make form dirty
cy.fill_field("status", "Cancelled", "Select");
// update doc using api - simulating parallel change by another user
cy.update_doc("ToDo", doc.name, {"status": "Closed"}).then(() => {
cy.findByRole("button", {name: "Refresh"}).click();
cy.update_doc("ToDo", doc.name, { status: "Closed" }).then(() => {
cy.findByRole("button", { name: "Refresh" }).click();
cy.get_field("status", "Select").should("have.value", "Closed");
})
})
});
});
});
it("Jump to field in collapsed section", { scrollBehavior: false }, () => {
cy.new_form("User");
jump_to_field("Location"); // this is in collapsed section
type_value("Bermuda");
cy.get_field("location").should("have.value", "Bermuda");
});
it("let user undo/redo field value changes", { scrollBehavior: false }, () => {
const undo = () => cy.get("body").type("{esc}").type("{ctrl+z}").wait(500);
const redo = () => cy.get("body").type("{esc}").type("{ctrl+y}").wait(500);
cy.new_form("User");
jump_to_field("Email");
type_value("admin@example.com");
jump_to_field("Username");
type_value("admin42");
jump_to_field("Send Welcome Email");
cy.focused().uncheck();
// make a mistake
jump_to_field("Username");
type_value("admin24");
// undo behaviour
undo();
cy.get_field("username").should("have.value", "admin42");
// redo behaviour
redo();
cy.get_field("username").should("have.value", "admin24");
// undo everything & redo everything, ensure same values at the end
undo();
undo();
undo();
undo();
redo();
redo();
redo();
redo();
cy.compare_document({
username: "admin24",
email: "admin@example.com",
send_welcome_email: 0,
});
});
it("update docfield property using set_df_property in child table", () => {
cy.visit("/app/contact/Test Form Contact 1");
cy.window()
.its("cur_frm")
.then((frm) => {
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
// set property before form_render event of child table
cy.get("@table")
.find('[data-idx="1"]')
.invoke("attr", "data-name")
.then((cdn) => {
frm.set_df_property(
"phone_nos",
"hidden",
1,
"Contact Phone",
"is_primary_phone",
cdn
);
});
cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
cy.get(".grid-row-open").as("table-form");
cy.get("@table-form")
.find('.frappe-control[data-fieldname="is_primary_phone"]')
.should("be.hidden");
cy.get("@table-form").find(".grid-footer-toolbar").click();
// set property on form_render event of child table
cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
cy.get("@table")
.find('[data-idx="1"]')
.invoke("attr", "data-name")
.then((cdn) => {
frm.set_df_property(
"phone_nos",
"hidden",
0,
"Contact Phone",
"is_primary_phone",
cdn
);
});
cy.get(".grid-row-open").as("table-form");
cy.get("@table-form")
.find('.frappe-control[data-fieldname="is_primary_phone"]')
.should("be.visible");
cy.get("@table-form").find(".grid-footer-toolbar").click();
});
});
});

View file

@ -0,0 +1,301 @@
import form_builder_doctype from "../fixtures/form_builder_doctype";
const doctype_name = form_builder_doctype.name;
context("Form Builder", () => {
before(() => {
cy.login();
cy.visit("/app");
return cy.insert_doc("DocType", form_builder_doctype, true);
});
it("Open Form Builder for Web Form Doctype/Customize Form", () => {
// doctype
cy.visit("/app/form-builder/Web Form");
cy.get(".form-builder-container").should("exist");
// customize form
cy.visit("/app/form-builder/Web Form/customize");
cy.get(".form-builder-container").should("exist");
});
it("Change Doctype using page title dialog", () => {
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.visit(`/app/form-builder/Web Form`);
cy.get(".form-builder-container").should("exist");
cy.get(".page-title").click();
cy.get(".frappe-control[data-fieldname='doctype'] input").click().as("input");
cy.get("@input").type("{rightArrow} Field", { delay: 200 });
cy.wait("@search_link");
cy.get("@input").type("{enter}").blur();
cy.click_modal_primary_button("Change");
cy.get(".page-title .title-text").should("have.text", "Web Form Field");
});
it("Save without change, check form dirty and reset changes", () => {
cy.visit(`/app/form-builder/${doctype_name}`);
// Save without change
cy.click_doc_primary_button("Save");
cy.get(".desk-alert.orange .alert-message").should("have.text", "No changes to save");
// Check form dirty
cy.get(".tab-content.active .section-columns-container:first .column:first .field:first")
.find("div[title='Double click to edit label']")
.dblclick()
.type("Dirty");
cy.get(".title-area .indicator-pill.orange").should("have.text", "Not Saved");
// Reset changes
cy.get(".page-actions .custom-actions .btn").contains("Reset Changes").click();
cy.get(".title-area .indicator-pill.orange").should("not.exist");
});
it("Add empty section and save", () => {
cy.visit(`/app/form-builder/${doctype_name}`);
let first_section = ".tab-content.active .form-section-container:first";
// add new section
cy.get(first_section).click(15, 10);
cy.get(first_section).find(".section-actions button:first").click();
// save
cy.click_doc_primary_button("Save");
cy.get(".tab-content.active .form-section-container").should("have.length", 1);
});
it("Add Table field and check if columns are rendered", () => {
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.visit(`/app/form-builder/${doctype_name}`);
let first_field =
".tab-content.active .section-columns-container:first .column:first .field:first";
cy.get(".fields-container .field[title='Table']").drag(first_field, {
target: { x: 100, y: 10 },
});
// save
cy.click_doc_primary_button("Save");
// Validate if options is not set
cy.get_open_dialog().find(".msgprint").should("contain", "Options is required");
cy.hide_dialog();
cy.get(first_field).click({ force: true });
cy.get(".sidebar-container .frappe-control[data-fieldname='options'] input")
.click()
.as("input");
cy.get("@input").clear({ force: true }).type("Web Form Field", { delay: 200 });
cy.wait("@search_link");
cy.get("@input").type("{enter}").blur();
cy.get(first_field)
.find(".table-controls .table-column")
.contains("Field")
.should("exist");
cy.get(first_field)
.find(".table-controls .table-column")
.contains("Fieldtype")
.should("exist");
// validate In List View
cy.get(".sidebar-container .field label .label-area").contains("In List View").click();
// save
cy.click_doc_primary_button("Save");
cy.get_open_dialog().find(".msgprint").should("contain", "In List View");
cy.hide_dialog();
cy.get(first_field).click({ force: true });
cy.get(".sidebar-container .field label .label-area").contains("In List View").click();
// validate In Global Search
cy.get(".sidebar-container .field label .label-area").contains("In Global Search").click();
// save
cy.click_doc_primary_button("Save");
cy.get_open_dialog().find(".msgprint").should("contain", "In Global Search");
});
it("Drag Field/Column/Section & Tab", () => {
cy.visit(`/app/form-builder/${doctype_name}`);
let first_column = ".tab-content.active .section-columns-container:first .column:first";
let first_field = first_column + " .field:first";
let label = "div[title='Double click to edit label'] span:first";
// drag first tab to second position
cy.get(".tabs .tab:first").drag(".tabs .tab:nth-child(2)", {
target: { x: 10, y: 10 },
force: true,
});
cy.get(".tabs .tab:first").find(label).should("have.text", "Tab 2");
cy.get(".tabs .tab:first").click();
cy.get(".sidebar-container .tab:first").click();
// drag check field to first column
cy.get(".fields-container .field[title='Check']").drag(first_field, {
target: { x: 100, y: 10 },
});
cy.get(first_column).find(".field").should("have.length", 3);
cy.get(first_field)
.find("div[title='Double click to edit label']")
.dblclick()
.type("Test Check{enter}");
cy.get(first_field).find(label).should("have.text", "Test Check");
// drag the first field to second position
cy.get(first_field).drag(first_column + " .field:nth-child(2)", {
target: { x: 100, y: 10 },
});
cy.get(first_field).find(label).should("have.text", "Data");
// drag first column to second position
cy.get(first_column).click().wait(200);
cy.get(first_column)
.find(".column-actions")
.drag(".section-columns-container:first .column:last", {
target: { x: 100, y: 10 },
force: true,
});
cy.get(first_field).find(label).should("have.text", "Data 1");
let first_section = ".tab-content.active .form-section-container:first";
// drag first section to second position
cy.get(first_section).click().wait(200);
cy.get(first_section)
.find(".section-header")
.drag(".form-section-container:nth-child(2)", {
target: { x: 100, y: 10 },
force: true,
});
cy.get(first_field).find(label).should("have.text", "Data 2");
});
it("Add New Tab/Section/Column to Form", () => {
cy.visit(`/app/form-builder/${doctype_name}`);
let first_section = ".tab-content.active .form-section-container:first";
// add new tab
cy.get(".tab-header").realHover().find(".tab-actions .new-tab-btn").click();
cy.get(".tabs .tab").should("have.length", 3);
// add new section
cy.get(first_section).click(15, 10);
cy.get(first_section).find(".section-actions button:first").click();
cy.get(".tab-content.active .form-section-container").should("have.length", 2);
// add new column
cy.get(first_section).find(".column:first").click(15, 10);
cy.get(first_section).find(".column:first .column-actions button:first").click();
cy.get(first_section).find(".column").should("have.length", 3);
});
it("Remove Tab/Section/Column", () => {
let first_section = ".tab-content.active .form-section-container:first";
// remove column
cy.get(first_section).find(".column:first").click(15, 10);
cy.get(first_section).find(".column:first .column-actions button:last").click();
cy.get(first_section).find(".column").should("have.length", 2);
// remove section
cy.get(first_section).click(15, 10);
cy.get(first_section).find(".section-actions button:last").click();
cy.get(".tab-content.active .form-section-container").should("have.length", 1);
// remove tab
cy.get(".tab-header").realHover().find(".tab-actions .remove-tab-btn").click();
cy.get(".tabs .tab").should("have.length", 2);
});
it("Update Title field Label to New Title through Customize Form", () => {
cy.visit(`/app/form-builder/${doctype_name}`);
let first_field =
".tab-content.active .section-columns-container:first .column:first .field:first";
cy.get(first_field)
.find("div[title='Double click to edit label']")
.dblclick()
.type("{selectall}New Title");
cy.findByRole("button", { name: "Save" }).click({ force: true });
cy.visit("/app/form-builder-doctype/new");
cy.get("[data-fieldname='data3'] .clearfix label").should("have.text", "New Title");
});
it("Validate Duplicate Name & reqd + hidden without default logic", () => {
cy.visit(`/app/form-builder/${doctype_name}`);
let first_field =
".tab-content.active .section-columns-container:first .column:first .field:first";
cy.get(".fields-container .field[title='Data']").drag(first_field, {
target: { x: 100, y: 10 },
});
cy.get(first_field).click();
// validate duplicate name
cy.get(".sidebar-container .frappe-control[data-fieldname='fieldname'] input")
.click()
.as("input");
cy.get("@input").clear({ force: true }).type("data3");
cy.click_doc_primary_button("Save");
cy.get_open_dialog().find(".msgprint").should("contain", "appears multiple times");
cy.hide_dialog();
cy.get(first_field).click();
cy.get("@input").clear({ force: true });
// validate reqd + hidden without default
cy.get(".sidebar-container .field label .label-area").contains("Mandatory").click();
cy.get(".sidebar-container .field label .label-area").contains("Hidden").click();
// save
cy.click_doc_primary_button("Save");
cy.get_open_dialog()
.find(".msgprint")
.should("contain", "cannot be hidden and mandatory without any default value");
});
it("Undo/Redo", () => {
cy.visit(`/app/form-builder/${doctype_name}`);
// click on second tab
cy.get(".tabs .tab:last").click();
let first_column = ".tab-content.active .section-columns-container:first .column:first";
let first_field = first_column + " .field:first";
let label = "div[title='Double click to edit label'] span:first";
// drag the first field to second position
cy.get(first_field).drag(first_column + " .field:nth-child(2)", {
target: { x: 100, y: 10 },
});
cy.get(first_field).find(label).should("have.text", "Check");
// undo
cy.get("body").type("{ctrl}z");
cy.get(first_field).find(label).should("have.text", "Data");
// redo
cy.get("body").type("{ctrl}{shift}z");
cy.get(first_field).find(label).should("have.text", "Check");
});
});

View file

@ -1,31 +1,30 @@
import doctype_with_tab_break from '../fixtures/doctype_with_tab_break';
import doctype_with_tab_break from "../fixtures/doctype_with_tab_break";
const doctype_name = doctype_with_tab_break.name;
context("Form Tab Break", () => {
before(() => {
cy.login();
cy.visit('/app/website');
return cy.insert_doc('DocType', doctype_with_tab_break, true);
cy.visit("/app/website");
return cy.insert_doc("DocType", doctype_with_tab_break, true);
});
it("Should switch tab and open correct tabs on validation error", () => {
cy.new_form(doctype_name);
// test tab switch
cy.findByRole("tab", {name: "Tab 2"}).click();
cy.findByRole("tab", { name: "Tab 2" }).click();
cy.findByText("Phone");
cy.findByRole("tab", {name: "Details"}).click();
cy.findByRole("tab", { name: "Details" }).click();
cy.findByText("Name");
// form should switch to the tab with un-filled mandatory field
cy.fill_field("username", "Test");
cy.findByRole("button", {name: "Save"}).click();
cy.findByRole("button", { name: "Save" }).click();
cy.findByText("Missing Fields");
cy.hide_dialog();
cy.findByText("Phone");
cy.fill_field("phone", "12345678");
cy.findByRole("button", {name: "Save"}).click();
cy.findByRole("button", { name: "Save" }).click();
// After save, first tab should have dashboard
cy.get(".form-tabs > .nav-item").eq(0).click();
cy.findByText("Connections");
});
});
});

View file

@ -1,88 +1,94 @@
context.skip('Form Tour', () => {
context.skip("Form Tour", () => {
before(() => {
cy.login();
cy.visit('/app');
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_form_tour");
});
cy.visit("/app");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.call("frappe.tests.ui_test_helpers.create_form_tour");
});
});
const open_test_form_tour = () => {
cy.visit('/app/form-tour/Test Form Tour');
cy.findByRole('button', {name: 'Show Tour'}).should('be.visible').as('show_tour');
cy.get('@show_tour').click();
cy.visit("/app/form-tour/Test Form Tour");
cy.findByRole("button", { name: "Show Tour" }).should("be.visible").as("show_tour");
cy.get("@show_tour").click();
cy.wait(500);
cy.url().should('include', '/app/contact');
cy.url().should("include", "/app/contact");
};
it('jump to a form tour', open_test_form_tour);
it("jump to a form tour", open_test_form_tour);
it('navigates a form tour', () => {
it("navigates a form tour", () => {
open_test_form_tour();
cy.get('.frappe-driver').should('be.visible');
cy.get('.frappe-control[data-fieldname="first_name"]').as('first_name');
cy.get('@first_name').should('have.class', 'driver-highlighted-element');
cy.get('.frappe-driver').findByRole('button', {name: 'Next'}).as('next_btn');
cy.get(".frappe-driver").should("be.visible");
cy.get('.frappe-control[data-fieldname="first_name"]').as("first_name");
cy.get("@first_name").should("have.class", "driver-highlighted-element");
cy.get(".frappe-driver").findByRole("button", { name: "Next" }).as("next_btn");
// next btn shouldn't move to next step, if first name is not entered
cy.get('@next_btn').click();
cy.get("@next_btn").click();
cy.wait(500);
cy.get('@first_name').should('have.class', 'driver-highlighted-element');
cy.get("@first_name").should("have.class", "driver-highlighted-element");
// after filling the field, next step should be highlighted
cy.fill_field('first_name', 'Test Name', 'Data');
cy.fill_field("first_name", "Test Name", "Data");
cy.wait(500);
cy.get('@next_btn').click();
cy.get("@next_btn").click();
cy.wait(500);
// assert field is highlighted
cy.get('.frappe-control[data-fieldname="last_name"]').as('last_name');
cy.get('@last_name').should('have.class', 'driver-highlighted-element');
cy.get('.frappe-control[data-fieldname="last_name"]').as("last_name");
cy.get("@last_name").should("have.class", "driver-highlighted-element");
// after filling the field, next step should be highlighted
cy.fill_field('last_name', 'Test Last Name', 'Data');
cy.fill_field("last_name", "Test Last Name", "Data");
cy.wait(500);
cy.get('@next_btn').click();
cy.get("@next_btn").click();
cy.wait(500);
// assert field is highlighted
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('phone_nos');
cy.get('@phone_nos').should('have.class', 'driver-highlighted-element');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("phone_nos");
cy.get("@phone_nos").should("have.class", "driver-highlighted-element");
// move to next step
cy.wait(500);
cy.get('@next_btn').click();
cy.get("@next_btn").click();
cy.wait(500);
// assert add row btn is highlighted
cy.get('@phone_nos').find('.grid-add-row').as('add_row');
cy.get('@add_row').should('have.class', 'driver-highlighted-element');
cy.get("@phone_nos").find(".grid-add-row").as("add_row");
cy.get("@add_row").should("have.class", "driver-highlighted-element");
// add a row & move to next step
cy.wait(500);
cy.get('@add_row').click();
cy.get("@add_row").click();
cy.wait(500);
// assert table field is highlighted
cy.get('.grid-row-open .frappe-control[data-fieldname="phone"]').as('phone');
cy.get('@phone').should('have.class', 'driver-highlighted-element');
cy.get('.grid-row-open .frappe-control[data-fieldname="phone"]').as("phone");
cy.get("@phone").should("have.class", "driver-highlighted-element");
// enter value in a table field
let field = cy.fill_table_field('phone_nos', '1', 'phone', '1234567890');
let field = cy.fill_table_field("phone_nos", "1", "phone", "1234567890");
field.blur();
// move to collapse row step
cy.wait(500);
cy.get('.driver-popover-title').contains('Test Title 4').siblings().get('@next_btn').click();
cy.get(".driver-popover-title")
.contains("Test Title 4")
.siblings()
.get("@next_btn")
.click();
cy.wait(500);
// collapse row
cy.get('.grid-row-open .grid-collapse-row').click();
cy.get(".grid-row-open .grid-collapse-row").click();
cy.wait(500);
// assert save btn is highlighted
cy.get('.primary-action').should('have.class', 'driver-highlighted-element');
cy.get(".primary-action").should("have.class", "driver-highlighted-element");
cy.wait(500);
cy.get('.frappe-driver').findByRole('button', {name: 'Save'}).should('be.visible');
cy.get(".frappe-driver").findByRole("button", { name: "Save" }).should("be.visible");
});
});

View file

@ -1,92 +1,114 @@
context('Grid', () => {
context("Grid", () => {
beforeEach(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
before(() => {
cy.login();
cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_contact_phone_nos_records");
});
cy.visit("/app/website");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.call(
"frappe.tests.ui_test_helpers.create_contact_phone_nos_records"
);
});
});
it('update docfield property using update_docfield_property', () => {
cy.visit('/app/contact/Test Contact');
cy.window().its("cur_frm").then(frm => {
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
let field = frm.get_field("phone_nos");
field.grid.update_docfield_property("is_primary_phone", "hidden", true);
it("update docfield property using update_docfield_property", () => {
cy.visit("/app/contact/Test Contact");
cy.window()
.its("cur_frm")
.then((frm) => {
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
let field = frm.get_field("phone_nos");
field.grid.update_docfield_property("is_primary_phone", "hidden", true);
cy.get('@table').find('[data-idx="1"] .edit-grid-row').click();
cy.get('.grid-row-open').as('table-form');
cy.get('@table-form').find('.frappe-control[data-fieldname="is_primary_phone"]').should("be.hidden");
cy.get('@table-form').find('.grid-footer-toolbar').click();
cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
cy.get(".grid-row-open").as("table-form");
cy.get("@table-form")
.find('.frappe-control[data-fieldname="is_primary_phone"]')
.should("be.hidden");
cy.get("@table-form").find(".grid-footer-toolbar").click();
cy.get('@table').find('[data-idx="2"] .edit-grid-row').click();
cy.get('.grid-row-open').as('table-form');
cy.get('@table-form').find('.frappe-control[data-fieldname="is_primary_phone"]').should("be.hidden");
cy.get('@table-form').find('.grid-footer-toolbar').click();
});
cy.get("@table").find('[data-idx="2"] .edit-grid-row').click();
cy.get(".grid-row-open").as("table-form");
cy.get("@table-form")
.find('.frappe-control[data-fieldname="is_primary_phone"]')
.should("be.hidden");
cy.get("@table-form").find(".grid-footer-toolbar").click();
});
});
it('update docfield property using toggle_display', () => {
cy.visit('/app/contact/Test Contact');
cy.window().its("cur_frm").then(frm => {
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
let field = frm.get_field("phone_nos");
field.grid.toggle_display("is_primary_mobile_no", false);
it("update docfield property using toggle_display", () => {
cy.visit("/app/contact/Test Contact");
cy.window()
.its("cur_frm")
.then((frm) => {
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
let field = frm.get_field("phone_nos");
field.grid.toggle_display("is_primary_mobile_no", false);
cy.get('@table').find('[data-idx="1"] .edit-grid-row').click();
cy.get('.grid-row-open').as('table-form');
cy.get('@table-form').find('.frappe-control[data-fieldname="is_primary_mobile_no"]').should("be.hidden");
cy.get('@table-form').find('.grid-footer-toolbar').click();
cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
cy.get(".grid-row-open").as("table-form");
cy.get("@table-form")
.find('.frappe-control[data-fieldname="is_primary_mobile_no"]')
.should("be.hidden");
cy.get("@table-form").find(".grid-footer-toolbar").click();
cy.get('@table').find('[data-idx="2"] .edit-grid-row').click();
cy.get('.grid-row-open').as('table-form');
cy.get('@table-form').find('.frappe-control[data-fieldname="is_primary_mobile_no"]').should("be.hidden");
cy.get('@table-form').find('.grid-footer-toolbar').click();
});
cy.get("@table").find('[data-idx="2"] .edit-grid-row').click();
cy.get(".grid-row-open").as("table-form");
cy.get("@table-form")
.find('.frappe-control[data-fieldname="is_primary_mobile_no"]')
.should("be.hidden");
cy.get("@table-form").find(".grid-footer-toolbar").click();
});
});
it('update docfield property using toggle_enable', () => {
cy.visit('/app/contact/Test Contact');
cy.window().its("cur_frm").then(frm => {
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
let field = frm.get_field("phone_nos");
field.grid.toggle_enable("phone", false);
it("update docfield property using toggle_enable", () => {
cy.visit("/app/contact/Test Contact");
cy.window()
.its("cur_frm")
.then((frm) => {
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
let field = frm.get_field("phone_nos");
field.grid.toggle_enable("phone", false);
cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
cy.get(".grid-row-open").as("table-form");
cy.get("@table-form")
.find('.frappe-control[data-fieldname="phone"] .control-value')
.should("have.class", "like-disabled-input");
cy.get("@table-form").find(".grid-footer-toolbar").click();
cy.get('@table').find('[data-idx="1"] .edit-grid-row').click();
cy.get('.grid-row-open').as('table-form');
cy.get('@table-form').find('.frappe-control[data-fieldname="phone"] .control-value').should('have.class', 'like-disabled-input');
cy.get('@table-form').find('.grid-footer-toolbar').click();
cy.get('@table').find('[data-idx="2"] .edit-grid-row').click();
cy.get('.grid-row-open').as('table-form');
cy.get('@table-form').find('.frappe-control[data-fieldname="phone"] .control-value').should('have.class', 'like-disabled-input');
cy.get('@table-form').find('.grid-footer-toolbar').click();
});
cy.get("@table").find('[data-idx="2"] .edit-grid-row').click();
cy.get(".grid-row-open").as("table-form");
cy.get("@table-form")
.find('.frappe-control[data-fieldname="phone"] .control-value')
.should("have.class", "like-disabled-input");
cy.get("@table-form").find(".grid-footer-toolbar").click();
});
});
it('update docfield property using toggle_reqd', () => {
cy.visit('/app/contact/Test Contact');
cy.window().its("cur_frm").then(frm => {
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
let field = frm.get_field("phone_nos");
field.grid.toggle_reqd("phone", false);
it("update docfield property using toggle_reqd", () => {
cy.visit("/app/contact/Test Contact");
cy.window()
.its("cur_frm")
.then((frm) => {
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
let field = frm.get_field("phone_nos");
field.grid.toggle_reqd("phone", false);
cy.get('@table').find('[data-idx="1"] .edit-grid-row').click();
cy.get('.grid-row-open').as('table-form');
cy.get_field("phone").as('phone-field');
cy.get('@phone-field').focus().clear().wait(500).blur();
cy.get('@phone-field').should("not.have.class", "has-error");
cy.get('@table-form').find('.grid-footer-toolbar').click();
cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
cy.get(".grid-row-open").as("table-form");
cy.get_field("phone").as("phone-field");
cy.get("@phone-field").focus().clear().wait(500).blur();
cy.get("@phone-field").should("not.have.class", "has-error");
cy.get("@table-form").find(".grid-footer-toolbar").click();
cy.get('@table').find('[data-idx="2"] .edit-grid-row').click();
cy.get('.grid-row-open').as('table-form');
cy.get_field("phone").as('phone-field');
cy.get('@phone-field').focus().clear().wait(500).blur();
cy.get('@phone-field').should("not.have.class", "has-error");
cy.get('@table-form').find('.grid-footer-toolbar').click();
});
cy.get("@table").find('[data-idx="2"] .edit-grid-row').click();
cy.get(".grid-row-open").as("table-form");
cy.get_field("phone").as("phone-field");
cy.get("@phone-field").focus().clear().wait(500).blur();
cy.get("@phone-field").should("not.have.class", "has-error");
cy.get("@table-form").find(".grid-footer-toolbar").click();
});
});
});

View file

@ -1,23 +1,23 @@
context('Grid Configuration', () => {
context("Grid Configuration", () => {
beforeEach(() => {
cy.login();
cy.visit('/app/doctype/User');
cy.visit("/app/doctype/User");
});
it('Set user wise grid settings', () => {
it("Set user wise grid settings", () => {
cy.wait(100);
cy.get('.frappe-control[data-fieldname="fields"]').as('table');
cy.get('@table').find('.icon-sm').click();
cy.get('.frappe-control[data-fieldname="fields"]').as("table");
cy.get("@table").find(".icon-sm").click();
cy.wait(100);
cy.get('.frappe-control[data-fieldname="fields_html"]').as('modal');
cy.get('@modal').find('.add-new-fields').click();
cy.get('.frappe-control[data-fieldname="fields_html"]').as("modal");
cy.get("@modal").find(".add-new-fields").click();
cy.wait(100);
cy.get('[type="checkbox"][data-unit="read_only"]').check();
cy.findByRole('button', {name: 'Add'}).click();
cy.findByRole("button", { name: "Add" }).click();
cy.wait(100);
cy.get('[data-fieldname="options"]').invoke('attr', 'value', '1');
cy.get('.form-control.column-width[data-fieldname="options"]').trigger('change');
cy.findByRole('button', {name: 'Update'}).click();
cy.get('[data-fieldname="options"]').invoke("attr", "value", "1");
cy.get('.form-control.column-width[data-fieldname="options"]').trigger("change");
cy.findByRole("button", { name: "Update" }).click();
cy.wait(200);
cy.get('[title="Read Only"').should('be.visible');
cy.get('[title="Read Only"').should("be.visible");
});
});
});

View file

@ -1,40 +1,47 @@
context('Grid Keyboard Shortcut', () => {
context("Grid Keyboard Shortcut", () => {
let total_count = 0;
before(() => {
cy.login();
});
beforeEach(() => {
cy.reload();
cy.visit('/app/contact/new-contact-1');
cy.visit("/app/contact/new-contact-1");
cy.get('.frappe-control[data-fieldname="email_ids"]').find(".grid-add-row").click();
});
it('Insert new row at the end', () => {
cy.add_new_row_in_grid('{ctrl}{shift}{downarrow}', (cy, total_count) => {
cy.get('[data-name="new-contact-email-1"]').should('have.attr', 'data-idx', `${total_count+1}`);
}, total_count);
it("Insert new row at the end", () => {
cy.add_new_row_in_grid(
"{ctrl}{shift}{downarrow}",
(cy, total_count) => {
cy.get('[data-name="new-contact-email-1"]').should(
"have.attr",
"data-idx",
`${total_count + 1}`
);
},
total_count
);
});
it('Insert new row at the top', () => {
cy.add_new_row_in_grid('{ctrl}{shift}{uparrow}', (cy) => {
cy.get('[data-name="new-contact-email-1"]').should('have.attr', 'data-idx', '2');
it("Insert new row at the top", () => {
cy.add_new_row_in_grid("{ctrl}{shift}{uparrow}", (cy) => {
cy.get('[data-name="new-contact-email-1"]').should("have.attr", "data-idx", "2");
});
});
it('Insert new row below', () => {
cy.add_new_row_in_grid('{ctrl}{downarrow}', (cy) => {
cy.get('[data-name="new-contact-email-1"]').should('have.attr', 'data-idx', '1');
it("Insert new row below", () => {
cy.add_new_row_in_grid("{ctrl}{downarrow}", (cy) => {
cy.get('[data-name="new-contact-email-1"]').should("have.attr", "data-idx", "1");
});
});
it('Insert new row above', () => {
cy.add_new_row_in_grid('{ctrl}{uparrow}', (cy) => {
cy.get('[data-name="new-contact-email-1"]').should('have.attr', 'data-idx', '2');
it("Insert new row above", () => {
cy.add_new_row_in_grid("{ctrl}{uparrow}", (cy) => {
cy.get('[data-name="new-contact-email-1"]').should("have.attr", "data-idx", "2");
});
});
});
Cypress.Commands.add('add_new_row_in_grid', (shortcut_keys, callbackFn, total_count) => {
cy.get('.frappe-control[data-fieldname="email_ids"]').as('table');
cy.get('@table').find('.grid-body [data-fieldname="email_id"]').first().click();
cy.get('@table').find('.grid-body [data-fieldname="email_id"]')
.first().type(shortcut_keys);
Cypress.Commands.add("add_new_row_in_grid", (shortcut_keys, callbackFn, total_count) => {
cy.get('.frappe-control[data-fieldname="email_ids"]').as("table");
cy.get("@table").find('.grid-body [data-fieldname="email_id"]').first().click();
cy.get("@table").find('.grid-body [data-fieldname="email_id"]').first().type(shortcut_keys);
callbackFn(cy, total_count);
});
});

View file

@ -1,65 +1,73 @@
context('Grid Pagination', () => {
context("Grid Pagination", () => {
beforeEach(() => {
cy.login();
cy.visit('/app/website');
cy.visit("/app/website");
});
before(() => {
cy.login();
cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_contact_phone_nos_records");
});
cy.visit("/app/website");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.call(
"frappe.tests.ui_test_helpers.create_contact_phone_nos_records"
);
});
});
it('creates pages for child table', () => {
cy.visit('/app/contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').find('.current-page-number').should('have.value', '1');
cy.get('@table').find('.total-page-number').should('contain', '20');
cy.get('@table').find('.grid-body .grid-row').should('have.length', 50);
it("creates pages for child table", () => {
cy.visit("/app/contact/Test Contact");
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
cy.get("@table").find(".current-page-number").should("have.value", "1");
cy.get("@table").find(".total-page-number").should("contain", "20");
cy.get("@table").find(".grid-body .grid-row").should("have.length", 50);
});
it('goes to the next and previous page', () => {
cy.visit('/app/contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').find('.next-page').click();
cy.get('@table').find('.current-page-number').should('have.value', '2');
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '51');
cy.get('@table').find('.prev-page').click();
cy.get('@table').find('.current-page-number').should('have.value', '1');
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '1');
it("goes to the next and previous page", () => {
cy.visit("/app/contact/Test Contact");
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
cy.get("@table").find(".next-page").click();
cy.get("@table").find(".current-page-number").should("have.value", "2");
cy.get("@table")
.find(".grid-body .grid-row")
.first()
.should("have.attr", "data-idx", "51");
cy.get("@table").find(".prev-page").click();
cy.get("@table").find(".current-page-number").should("have.value", "1");
cy.get("@table").find(".grid-body .grid-row").first().should("have.attr", "data-idx", "1");
});
it('adds and deletes rows and changes page', () => {
cy.visit('/app/contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').findByRole('button', {name: 'Add Row'}).click();
cy.get('@table').find('.grid-body .row-index').should('contain', 1001);
cy.get('@table').find('.current-page-number').should('have.value', '21');
cy.get('@table').find('.total-page-number').should('contain', '21');
cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true });
cy.get('@table').findByRole('button', {name: 'Delete'}).click();
cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000);
cy.get('@table').find('.current-page-number').should('have.value', '20');
cy.get('@table').find('.total-page-number').should('contain', '20');
it("adds and deletes rows and changes page", () => {
cy.visit("/app/contact/Test Contact");
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
cy.get("@table").findByRole("button", { name: "Add Row" }).click();
cy.get("@table").find(".grid-body .row-index").should("contain", 1001);
cy.get("@table").find(".current-page-number").should("have.value", "21");
cy.get("@table").find(".total-page-number").should("contain", "21");
cy.get("@table").find(".grid-body .grid-row .grid-row-check").click({ force: true });
cy.get("@table").findByRole("button", { name: "Delete" }).click();
cy.get("@table").find(".grid-body .row-index").last().should("contain", 1000);
cy.get("@table").find(".current-page-number").should("have.value", "20");
cy.get("@table").find(".total-page-number").should("contain", "20");
});
it('go to specific page, use up and down arrow, type characters, 0 page and more than existing page', () => {
cy.visit('/app/contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').find('.current-page-number').focus().clear().type('17').blur();
cy.get('@table').find('.grid-body .row-index').should('contain', 801);
it("go to specific page, use up and down arrow, type characters, 0 page and more than existing page", () => {
cy.visit("/app/contact/Test Contact");
cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
cy.get("@table").find(".current-page-number").focus().clear().type("17").blur();
cy.get("@table").find(".grid-body .row-index").should("contain", 801);
cy.get('@table').find('.current-page-number').focus().type('{uparrow}{uparrow}');
cy.get('@table').find('.current-page-number').should('have.value', '19');
cy.get("@table").find(".current-page-number").focus().type("{uparrow}{uparrow}");
cy.get("@table").find(".current-page-number").should("have.value", "19");
cy.get('@table').find('.current-page-number').focus().type('{downarrow}{downarrow}');
cy.get('@table').find('.current-page-number').should('have.value', '17');
cy.get("@table").find(".current-page-number").focus().type("{downarrow}{downarrow}");
cy.get("@table").find(".current-page-number").should("have.value", "17");
cy.get('@table').find('.current-page-number').focus().clear().type('700').blur();
cy.get('@table').find('.current-page-number').should('have.value', '20');
cy.get("@table").find(".current-page-number").focus().clear().type("700").blur();
cy.get("@table").find(".current-page-number").should("have.value", "20");
cy.get('@table').find('.current-page-number').focus().clear().type('0').blur();
cy.get('@table').find('.current-page-number').should('have.value', '1');
cy.get("@table").find(".current-page-number").focus().clear().type("0").blur();
cy.get("@table").find(".current-page-number").should("have.value", "1");
cy.get('@table').find('.current-page-number').focus().clear().type('abc').blur();
cy.get('@table').find('.current-page-number').should('have.value', '1');
cy.get("@table").find(".current-page-number").focus().clear().type("abc").blur();
cy.get("@table").find(".current-page-number").should("have.value", "1");
});
// it('deletes all rows', ()=> {
// cy.visit('/app/contact/Test Contact');
@ -69,4 +77,4 @@ context('Grid Pagination', () => {
// cy.get('.modal-dialog .btn-primary').contains('Yes').click();
// cy.get('@table').find('.grid-body .grid-row').should('have.length', 0);
// });
});
});

View file

@ -1,107 +1,133 @@
import doctype_with_child_table from '../fixtures/doctype_with_child_table';
import child_table_doctype from '../fixtures/child_table_doctype';
import child_table_doctype_1 from '../fixtures/child_table_doctype_1';
import doctype_with_child_table from "../fixtures/doctype_with_child_table";
import child_table_doctype from "../fixtures/child_table_doctype";
import child_table_doctype_1 from "../fixtures/child_table_doctype_1";
const doctype_with_child_table_name = doctype_with_child_table.name;
context('Grid Search', () => {
context("Grid Search", () => {
before(() => {
cy.visit('/login');
cy.visit("/login");
cy.login();
cy.visit('/app/website');
cy.insert_doc('DocType', child_table_doctype, true);
cy.insert_doc('DocType', child_table_doctype_1, true);
cy.insert_doc('DocType', doctype_with_child_table, true);
return cy.window().its('frappe').then(frappe => {
return frappe.xcall("frappe.tests.ui_test_helpers.insert_doctype_with_child_table_record", {
name: doctype_with_child_table_name
cy.visit("/app/website");
cy.insert_doc("DocType", child_table_doctype, true);
cy.insert_doc("DocType", child_table_doctype_1, true);
cy.insert_doc("DocType", doctype_with_child_table, true);
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.xcall(
"frappe.tests.ui_test_helpers.insert_doctype_with_child_table_record",
{
name: doctype_with_child_table_name,
}
);
});
});
});
it('Test search row visibility', () => {
cy.window().its('frappe').then(frappe => {
frappe.model.user_settings.save('Doctype With Child Table', 'GridView', {
'Child Table Doctype 1': [
{'fieldname': 'data', 'columns': 2},
{'fieldname': 'barcode', 'columns': 1},
{'fieldname': 'check', 'columns': 1},
{'fieldname': 'rating', 'columns': 2},
{'fieldname': 'duration', 'columns': 2},
{'fieldname': 'date', 'columns': 2}
]
it("Test search row visibility", () => {
cy.window()
.its("frappe")
.then((frappe) => {
frappe.model.user_settings.save("Doctype With Child Table", "GridView", {
"Child Table Doctype 1": [
{ fieldname: "data", columns: 2 },
{ fieldname: "barcode", columns: 1 },
{ fieldname: "check", columns: 1 },
{ fieldname: "rating", columns: 2 },
{ fieldname: "duration", columns: 2 },
{ fieldname: "date", columns: 2 },
],
});
});
});
cy.visit(`/app/doctype-with-child-table/Test Grid Search`);
cy.get('.frappe-control[data-fieldname="child_table_1"]').as('table');
cy.get('@table').find('.grid-row-check:last').click();
cy.get('@table').find('.grid-footer').contains('Delete').click();
cy.get('.grid-heading-row .grid-row .search').should('not.exist');
cy.get('.frappe-control[data-fieldname="child_table_1"]').as("table");
cy.get("@table").find(".grid-row-check:last").click();
cy.get("@table").find(".grid-footer").contains("Delete").click();
cy.get(".grid-heading-row .grid-row .search").should("not.exist");
});
it('test search field for different fieldtypes', () => {
it("test search field for different fieldtypes", () => {
cy.visit(`/app/doctype-with-child-table/Test Grid Search`);
cy.get('.frappe-control[data-fieldname="child_table_1"]').as('table');
cy.get('.frappe-control[data-fieldname="child_table_1"]').as("table");
// Index Column
cy.get('@table').find('.grid-heading-row .row-index.search input').type('3');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 2);
cy.get('@table').find('.grid-heading-row .row-index.search input').clear();
cy.get("@table").find(".grid-heading-row .row-index.search input").type("3");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 2);
cy.get("@table").find(".grid-heading-row .row-index.search input").clear();
// Data Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Data"]').type('Data');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 1);
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Data"]').clear();
cy.get("@table")
.find('.grid-heading-row .search input[data-fieldtype="Data"]')
.type("Data");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 1);
cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Data"]').clear();
// Barcode Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Barcode"]').type('092');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 4);
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Barcode"]').clear();
cy.get("@table")
.find('.grid-heading-row .search input[data-fieldtype="Barcode"]')
.type("092");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 4);
cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Barcode"]').clear();
// Check Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Check"]').type('1');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 9);
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Check"]').clear();
cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Check"]').type("1");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 9);
cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Check"]').clear();
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Check"]').type('0');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 11);
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Check"]').clear();
cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Check"]').type("0");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 11);
cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Check"]').clear();
// Rating Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Rating"]').type('3');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 3);
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Rating"]').clear();
cy.get("@table")
.find('.grid-heading-row .search input[data-fieldtype="Rating"]')
.type("3");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 3);
cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Rating"]').clear();
// Duration Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Duration"]').type('3d');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 3);
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Duration"]').clear();
cy.get("@table")
.find('.grid-heading-row .search input[data-fieldtype="Duration"]')
.type("3d");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 3);
cy.get("@table")
.find('.grid-heading-row .search input[data-fieldtype="Duration"]')
.clear();
// Date Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Date"]').type('2022');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 4);
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Date"]').clear();
cy.get("@table")
.find('.grid-heading-row .search input[data-fieldtype="Date"]')
.type("2022");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 4);
cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Date"]').clear();
});
it('test with multiple filter', () => {
cy.get('.frappe-control[data-fieldname="child_table_1"]').as('table');
it("test with multiple filter", () => {
cy.get('.frappe-control[data-fieldname="child_table_1"]').as("table");
// Data Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Data"]').type('a');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 10);
cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Data"]').type("a");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 10);
// Barcode Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Barcode"]').type('0');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 8);
cy.get("@table")
.find('.grid-heading-row .search input[data-fieldtype="Barcode"]')
.type("0");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 8);
// Duration Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Duration"]').type('d');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 5);
cy.get("@table")
.find('.grid-heading-row .search input[data-fieldtype="Duration"]')
.type("d");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 5);
// Date Column
cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Date"]').type('02-');
cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 2);
cy.get("@table")
.find('.grid-heading-row .search input[data-fieldtype="Date"]')
.type("02-");
cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 2);
});
});
});

View file

@ -1,87 +1,134 @@
context('Kanban Board', () => {
context("Kanban Board", () => {
before(() => {
cy.login();
cy.visit('/app');
cy.login("frappe@example.com");
cy.visit("/app");
});
it('Create ToDo Kanban', () => {
cy.visit('/app/todo');
it("Create ToDo Kanban", () => {
cy.visit("/app/todo");
cy.get('.page-actions .custom-btn-group button').click();
cy.get('.page-actions .custom-btn-group ul.dropdown-menu li').contains('Kanban').click();
cy.get(".page-actions .custom-btn-group button").click();
cy.get(".page-actions .custom-btn-group ul.dropdown-menu li").contains("Kanban").click();
cy.focused().blur();
cy.fill_field('board_name', 'ToDo Kanban', 'Data');
cy.fill_field('field_name', 'Status', 'Select');
cy.click_modal_primary_button('Save');
cy.fill_field("board_name", "ToDo Kanban", "Data");
cy.fill_field("field_name", "Status", "Select");
cy.click_modal_primary_button("Save");
cy.get('.title-text').should('contain', 'ToDo Kanban');
cy.get(".title-text").should("contain", "ToDo Kanban");
});
it('Create ToDo from kanban', () => {
it("Create ToDo from kanban", () => {
cy.intercept({
method: 'POST',
url: 'api/method/frappe.client.save'
}).as('save-todo');
method: "POST",
url: "api/method/frappe.client.save",
}).as("save-todo");
cy.click_listview_primary_button('Add ToDo');
cy.click_listview_primary_button("Add ToDo");
cy.fill_field('description', 'Test Kanban ToDo', 'Text Editor').wait(300);
cy.get('.modal-footer .btn-primary').last().click();
cy.fill_field("description", "Test Kanban ToDo", "Text Editor").wait(300);
cy.get(".modal-footer .btn-primary").last().click();
cy.wait('@save-todo');
cy.wait("@save-todo");
});
it('Add and Remove fields', () => {
cy.visit('/app/todo/view/kanban/ToDo Kanban');
it("Add and Remove fields", () => {
cy.visit("/app/todo/view/kanban/ToDo Kanban");
cy.intercept('POST', '/api/method/frappe.desk.doctype.kanban_board.kanban_board.save_settings').as('save-kanban');
cy.intercept('POST', '/api/method/frappe.desk.doctype.kanban_board.kanban_board.update_order').as('update-order');
cy.intercept(
"POST",
"/api/method/frappe.desk.doctype.kanban_board.kanban_board.save_settings"
).as("save-kanban");
cy.intercept(
"POST",
"/api/method/frappe.desk.doctype.kanban_board.kanban_board.update_order"
).as("update-order");
cy.get('.page-actions .menu-btn-group > .btn').click();
cy.get('.page-actions .menu-btn-group .dropdown-menu li').contains('Kanban Settings').click();
cy.get('.add-new-fields').click();
cy.get(".page-actions .menu-btn-group > .btn").click();
cy.get(".page-actions .menu-btn-group .dropdown-menu li")
.contains("Kanban Settings")
.click();
cy.get(".add-new-fields").click();
cy.get('.checkbox-options .checkbox').contains('ID').click();
cy.get('.checkbox-options .checkbox').contains('Status').first().click();
cy.get('.checkbox-options .checkbox').contains('Priority').click();
cy.get(".checkbox-options .checkbox").contains("ID").click();
cy.get(".checkbox-options .checkbox").contains("Status").first().click();
cy.get(".checkbox-options .checkbox").contains("Priority").click();
cy.get('.modal-footer .btn-primary').last().click();
cy.get(".modal-footer .btn-primary").last().click();
cy.get('.frappe-control .label-area').contains('Show Labels').click();
cy.click_modal_primary_button('Save');
cy.get(".frappe-control .label-area").contains("Show Labels").click();
cy.click_modal_primary_button("Save");
cy.wait('@save-kanban');
cy.wait("@save-kanban");
cy.get('.kanban-column[data-column-value="Open"] .kanban-cards').as('open-cards');
cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('contain', 'ID:');
cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('contain', 'Status:');
cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('contain', 'Priority:');
cy.get('.kanban-column[data-column-value="Open"] .kanban-cards').as("open-cards");
cy.get("@open-cards")
.find(".kanban-card .kanban-card-doc")
.first()
.should("contain", "ID:");
cy.get("@open-cards")
.find(".kanban-card .kanban-card-doc")
.first()
.should("contain", "Status:");
cy.get("@open-cards")
.find(".kanban-card .kanban-card-doc")
.first()
.should("contain", "Priority:");
cy.get('.page-actions .menu-btn-group > .btn').click();
cy.get('.page-actions .menu-btn-group .dropdown-menu li').contains('Kanban Settings').click();
cy.get_open_dialog().find('.frappe-control[data-fieldname="fields_html"] div[data-label="ID"] .remove-field').click();
cy.get(".page-actions .menu-btn-group > .btn").click();
cy.get(".page-actions .menu-btn-group .dropdown-menu li")
.contains("Kanban Settings")
.click();
cy.get_open_dialog()
.find(
'.frappe-control[data-fieldname="fields_html"] div[data-label="ID"] .remove-field'
)
.click();
cy.wait('@update-order');
cy.get_open_dialog().find('.frappe-control .label-area').contains('Show Labels').click();
cy.get('.modal-footer .btn-primary').last().click();
cy.wait("@update-order");
cy.get_open_dialog().find(".frappe-control .label-area").contains("Show Labels").click();
cy.get(".modal-footer .btn-primary").last().click();
cy.wait('@save-kanban');
cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('not.contain', 'ID:');
cy.wait("@save-kanban");
cy.get("@open-cards")
.find(".kanban-card .kanban-card-doc")
.first()
.should("not.contain", "ID:");
});
// it('Drag todo', () => {
// cy.intercept({
// method: 'POST',
// url: 'api/method/frappe.desk.doctype.kanban_board.kanban_board.update_order_for_single_card'
// }).as('drag-completed');
it("Checks if Kanban Board edits are blocked for non-System Manager and non-owner of the Board", () => {
cy.switch_to_user("Administrator");
// cy.get('.kanban-card-body')
// .contains('Test Kanban ToDo').first()
// .drag('[data-column-value="Closed"] .kanban-cards', { force: true });
const noSystemManager = "nosysmanager@example.com";
cy.call("frappe.tests.ui_test_helpers.create_test_user", {
username: noSystemManager,
});
cy.remove_role(noSystemManager, "System Manager");
cy.call("frappe.tests.ui_test_helpers.create_todo", { description: "Frappe User ToDo" });
cy.call("frappe.tests.ui_test_helpers.create_admin_kanban");
// cy.wait('@drag-completed');
// });
});
cy.switch_to_user(noSystemManager);
cy.visit("/app/todo/view/kanban/Admin Kanban");
// Menu button should be hidden (dropdown for 'Save Filters' and 'Delete Kanban Board')
cy.get(".no-list-sidebar .menu-btn-group .btn-default[data-original-title='Menu']").should(
"have.length",
0
);
// Kanban Columns should be visible (read-only)
cy.get(".kanban .kanban-column").should("have.length", 2);
// User should be able to add card (has access to ToDo)
cy.get(".kanban .add-card").should("have.length", 2);
// Column actions should be hidden (dropdown for 'Archive' and indicators)
cy.get(".kanban .column-options").should("have.length", 0);
cy.switch_to_user("Administrator");
cy.call("frappe.client.delete", { doctype: "User", name: noSystemManager });
});
after(() => {
cy.call("logout");
});
});

View file

@ -1,39 +1,42 @@
context('List Paging', () => {
context("List Paging", () => {
before(() => {
cy.login();
cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_multiple_todo_records");
});
cy.visit("/app/website");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.call("frappe.tests.ui_test_helpers.create_multiple_todo_records");
});
});
it('test load more with count selection buttons', () => {
cy.visit('/app/todo/view/report');
cy.clear_filters()
it("test load more with count selection buttons", () => {
cy.visit("/app/todo/view/report");
cy.get(".filter-x-button").click();
cy.get('.list-paging-area .list-count').should('contain.text', '20 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '40 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '60 of');
cy.get(".list-paging-area .list-count").should("contain.text", "20 of");
cy.get(".list-paging-area .btn-more").click();
cy.get(".list-paging-area .list-count").should("contain.text", "40 of");
cy.get(".list-paging-area .btn-more").click();
cy.get(".list-paging-area .list-count").should("contain.text", "60 of");
cy.get('.list-paging-area .btn-group .btn-paging[data-value="100"]').click();
cy.get('.list-paging-area .list-count').should('contain.text', '100 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '200 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '300 of');
cy.get(".list-paging-area .list-count").should("contain.text", "100 of");
cy.get(".list-paging-area .btn-more").click();
cy.get(".list-paging-area .list-count").should("contain.text", "200 of");
cy.get(".list-paging-area .btn-more").click();
cy.get(".list-paging-area .list-count").should("contain.text", "300 of");
// check if refresh works after load more
cy.get('.page-head .standard-actions [data-original-title="Refresh"]').click();
cy.get('.list-paging-area .list-count').should('contain.text', '300 of');
cy.get(".list-paging-area .list-count").should("contain.text", "300 of");
cy.get('.list-paging-area .btn-group .btn-paging[data-value="500"]').click();
cy.get('.list-paging-area .list-count').should('contain.text', '500 of');
cy.get('.list-paging-area .btn-more').click();
cy.get(".list-paging-area .list-count").should("contain.text", "500 of");
cy.get(".list-paging-area .btn-more").click();
cy.get('.list-paging-area .list-count').should('contain.text', '1000 of');
cy.get(".list-paging-area .list-count").should("contain.text", "1000 of");
});
});

Some files were not shown because too many files have changed in this diff Show more