diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa2f9fb7..d390e9d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] fail-fast: false steps: @@ -32,7 +32,6 @@ jobs: run: | coverage run --source=toolium -m pytest toolium/test - name: Publish on coveralls.io - if: ${{ matrix.python-version != '2.7' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: Python ${{ matrix.python-version }} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d8398a1d..c8144ea5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ v2.0.0 *Release date: In development* +- Remove Python 2.7, 3.3 and 3.4 support - Update deprecated methods to fix warnings in python3 execution - Move *get_valid_filename* and *makedirs_safe* methods from *toolium.path_utils* to *toolium.utils.path_utils* - Move *Utils* class from *toolium.utils* to *toolium.utils.driver_utils* diff --git a/Makefile b/Makefile index c3762d2a..748ee34d 100644 --- a/Makefile +++ b/Makefile @@ -1,41 +1,23 @@ APP = toolium VERSION ?= $(shell cat VERSION) -RELEASE ?= $(shell git log --pretty=oneline | wc -l | tr -d ' ') -ARCH = noarch -PACKAGE = $(APP)-$(VERSION)-$(RELEASE).$(ARCH).rpm -ROOT = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -MKD2PDF ?= $(shell which markdown-pdf) -VIRTUALENV ?= virtualenv -TEST = tests +ROOT = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) SPHINXBUILD = sphinx-build SPHINXSOURCEDIR = docs SPHINXBUILDDIR = docs/build +TMP = $(ROOT)/tmp +VENV_PREFIX = $(TMP)/.venv +VENV = $(VENV_PREFIX)/$(APP) +REQ = requirements.txt +TESTREQ = requirements_dev.txt ifeq ($(OS),Windows_NT) BIN = Scripts - LIB = Lib TGZ_EXT = zip - PYTHON ?= $(shell which python).exe - PYTHON_EXE ?= $(shell where python | head -n1) else BIN = bin - LIB = lib/python2.7 TGZ_EXT = tar.gz - PYTHON ?= $(shell which python2.7) - PYTHON_EXE = python2.7 endif -TMP=$(ROOT)/tmp -VENV_PREFIX = $(TMP)/.venv -VENV = $(VENV_PREFIX)/$(APP) -REQ = requirements.txt - -TESTREQ = requirements_dev.txt - -COVERAGE_ARGS=--with-coverage --cover-erase --cover-package=$(APP) \ - --cover-branches --cover-xml \ - --cover-xml-file=$(ROOT)/dist/coverage.xml - all: default default: @@ -49,7 +31,6 @@ default: @echo " venv - Create and update virtual environments" @echo " install - Install application" @echo " sdist - Build a tar.gz software distribution of the package" -# @echo " rpm - Create $(PACKAGE) rpm" @echo " egg - Create python egg" @echo " unittest - Execute unit tests" @echo " doc - Build sphinx documentation" @@ -58,68 +39,51 @@ default: init: mkdir -p $(ROOT)/dist - # mkdir -p $(TMP)/rpmbuild/SOURCES - # mkdir -p $(TMP)/rpmbuild/BUILD - # mkdir -p $(TMP)/rpmbuild/RPMS - -sdist: init venv - @echo ">>> Creating source distribution..." - $(VENV)/$(BIN)/python setup.py sdist - @echo ">>> OK. TGZ generated in $(ROOT)/dist" - @echo - -disabled-rpm: init sdist - @echo ">>> Creating RPM package..." - cp $(ROOT)/dist/$(APP)-$(VERSION).${TGZ_EXT} $(TMP)/rpmbuild/SOURCES - rpmbuild -bb $(APP).spec \ - --define "version $(VERSION)" \ - --define "release $(RELEASE)" \ - --define "arch $(ARCH)" \ - --define "_target_os linux" \ - --define "_topdir $(TMP)/rpmbuild" \ - --define "buildroot $(TMP)/rpmbuild/BUILDROOT" \ - --define "__python $(PYTHON)" \ - --define "python_sitelib $(VENV)/$(LIB)/site-packages" \ - --define "_sysconfdir /etc" \ - --define "_bindir /usr/bin" \ - --nodeps \ - --buildroot=$(TMP)/rpmbuild/BUILDROOT - - cp $(TMP)/rpmbuild/RPMS/$(ARCH)/$(PACKAGE) $(ROOT)/dist - @echo ">>> OK. RPM generated in $(ROOT)/dist" - @echo venv: $(VENV) $(VENV): $(REQ) $(TESTREQ) mkdir -p $@; \ export GIT_SSL_NO_VERIFY=true; \ - $(VIRTUALENV) --no-site-packages --distribute -p $(PYTHON) $@; \ - $@/$(BIN)/pip install --upgrade -r $(REQ); \ - $@/$(BIN)/pip install --upgrade -r $(TESTREQ); \ + $(PYTHON_EXE) -m venv $@; \ + source $(VENV)/$(BIN)/activate; \ + python -m pip install --upgrade -r $(REQ); \ + python -m pip install --upgrade -r $(TESTREQ); + +sdist: init venv + @echo ">>> Creating source distribution..." + source $(VENV)/$(BIN)/activate; \ + python setup.py sdist + @echo ">>> OK. TGZ generated in $(ROOT)/dist" + @echo unittest: init venv - $(VENV)/$(BIN)/pytest toolium/test + source $(VENV)/$(BIN)/activate; \ + python -m pytest toolium/test coverage: init venv - $(VENV)/$(BIN)/nosetests $(COVERAGE_ARGS) $(UNIT_TEST_ARGS) + source $(VENV)/$(BIN)/activate; \ + coverage run --source=toolium -m pytest toolium/test @echo ">>> OK. Coverage reports generated in $(ROOT)/dist" -# Just for development purposes. It uses your active python2.7 +# Just for development purposes # Remember to update your requirements if needed egg: - $(PYTHON_EXE) setup.py bdist_egg + python -m pip install setuptools; \ + python setup.py bdist_egg -# Just for development purposes. It uses your active python2.7 +# Just for development purposes # Remember to update your requirements if needed install: - $(PYTHON_EXE) setup.py install + python -m pip install setuptools; \ + python setup.py install -doc: venv +doc: init venv @echo ">>> Cleaning sphinx doc files..." rm -rf $(SPHINXBUILDDIR)/html @echo ">>> Generating doc files..." - $(VENV)/$(BIN)/$(SPHINXBUILD) -b html $(SPHINXSOURCEDIR) $(SPHINXBUILDDIR)/html + source $(VENV)/$(BIN)/activate; \ + $(SPHINXBUILD) -b html $(SPHINXSOURCEDIR) $(SPHINXBUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(SPHINXBUILDDIR)/html." diff --git a/README.rst b/README.rst index 45cd279f..bc51555e 100644 --- a/README.rst +++ b/README.rst @@ -19,11 +19,8 @@ pattern and includes a simple visual testing solution. Getting Started --------------- -The requirements to install Toolium are `Python 2.7 or 3.3+ `_ and -`pip `_. If you use Python 2.7.9+, you don't need to install pip separately. - Run ``pip install toolium`` to install the latest version from `PyPi `_. It's -highly recommendable to use a virtualenv. +highly recommendable to use a virtualenv. The minimal python version to use Toolium is `Python 3.5 `_. The main dependencies are: diff --git a/requirements.txt b/requirements.txt index 56e90c3f..31510475 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,5 @@ -requests>=2.12.4 # api tests +requests>=2.12.4,<3 # api tests selenium>=2.53.6,<4 # web tests Appium-Python-Client>=0.24,<1 # mobile tests -six>=1.10.0 screeninfo==0.3.1 -typing==3.7.4.1 -lxml==4.6.2 +lxml==4.* diff --git a/requirements_dev.txt b/requirements_dev.txt index c1c19ff5..cb8552b5 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,10 +1,8 @@ -Sphinx==1.8.5 ; python_version < '3.0' -Sphinx==3.* ; python_version >= '3.5' +Sphinx==3.* lettuce==0.2.23 -pytest==4.6.11 ; python_version < '3.0' -pytest==6.* ; python_version >= '3.5' +pytest==6.* coverage==5.* -coveralls==3.* ; python_version >= '3.5' +coveralls==3.* mock==3.* requests-mock==1.* needle==0.5.0 diff --git a/setup.py b/setup.py index c7cb4a59..4d43d02b 100644 --- a/setup.py +++ b/setup.py @@ -63,9 +63,6 @@ def get_long_description(): 'License :: OSI Approved :: Apache Software License', 'Natural Language :: English', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/toolium/config_driver.py b/toolium/config_driver.py index e7a341d9..b025c97e 100644 --- a/toolium/config_driver.py +++ b/toolium/config_driver.py @@ -19,12 +19,11 @@ import ast import logging import os - from appium import webdriver as appiumdriver +from configparser import NoSectionError from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.firefox.options import Options -from six.moves.configparser import NoSectionError # Python 2 and 3 compatibility from toolium.driver_wrappers_pool import DriverWrappersPool diff --git a/toolium/config_parser.py b/toolium/config_parser.py index cfe639ec..594f2df7 100644 --- a/toolium/config_parser.py +++ b/toolium/config_parser.py @@ -17,11 +17,11 @@ """ import logging +from configparser import ConfigParser, NoSectionError, NoOptionError +from io import StringIO -from six.moves import configparser, StringIO # Python 2 and 3 compatibility - -class ExtendedConfigParser(configparser.ConfigParser): +class ExtendedConfigParser(ConfigParser): def optionxform(self, optionstr): """Override default optionxform in ConfigParser""" return optionstr @@ -37,7 +37,7 @@ def get_optional(self, section, option, default=None): """ try: return self.get(section, option) - except (configparser.NoSectionError, configparser.NoOptionError): + except (NoSectionError, NoOptionError): return default def getboolean_optional(self, section, option, default=False): @@ -51,7 +51,7 @@ def getboolean_optional(self, section, option, default=False): """ try: return self.getboolean(section, option) - except (configparser.NoSectionError, configparser.NoOptionError): + except (NoSectionError, NoOptionError): return default def deepcopy(self): @@ -68,12 +68,7 @@ def deepcopy(self): # Create a new config object config_copy = ExtendedConfigParser() - try: - # Python 3 - config_copy.read_file(config_string) - except AttributeError: - # Python 2.7 - config_copy.readfp(config_string) + config_copy.read_file(config_string) return config_copy diff --git a/toolium/pageelements/page_elements.py b/toolium/pageelements/page_elements.py index 78d83f03..e8b39cf8 100644 --- a/toolium/pageelements/page_elements.py +++ b/toolium/pageelements/page_elements.py @@ -94,8 +94,7 @@ def web_elements(self): return self._web_elements @property - def page_elements(self): - # type: () -> List[Any] + def page_elements(self) -> List[Any]: """Find multiple PageElement using element locator :returns: list of page element objects diff --git a/toolium/test/test_jira.py b/toolium/test/test_jira.py index 8c4d1401..b07b641f 100644 --- a/toolium/test/test_jira.py +++ b/toolium/test/test_jira.py @@ -110,12 +110,7 @@ def test_change_jira_status_attachments(logger): body_bytes = req_mock.last_request.body # Check requested url - try: - # Python 3 - body = "".join(map(chr, body_bytes)) - except TypeError: - # Python 2.7 - body = body_bytes + body = "".join(map(chr, body_bytes)) for partial_url in ['"jiraStatus"\r\n\r\nPass', '"jiraTestCaseId"\r\n\r\nTOOLIUM-1', '"summaryPrefix"\r\n\r\nprefix', '"labels"\r\n\r\nlabel1 label2', '"comments"\r\n\r\ncomment', '"version"\r\n\r\nRelease 1.0', '"build"\r\n\r\n453', diff --git a/toolium/test/utils/test_path_utils.py b/toolium/test/utils/test_path_utils.py index 1e7b5622..0a0ad308 100644 --- a/toolium/test/utils/test_path_utils.py +++ b/toolium/test/utils/test_path_utils.py @@ -17,15 +17,11 @@ """ import os -try: - import Queue as queue # Py2 -except ImportError: - import queue as queue # Py3 +import pytest +import queue as queue import threading import uuid -import pytest - from toolium.utils.path_utils import get_valid_filename, makedirs_safe filename_tests = ( diff --git a/toolium/test_cases.py b/toolium/test_cases.py index b3dca546..fef00a7a 100644 --- a/toolium/test_cases.py +++ b/toolium/test_cases.py @@ -17,7 +17,6 @@ """ import logging -import sys import unittest from toolium.config_driver import get_error_message_from_exception @@ -65,19 +64,8 @@ def setUp(self): def tearDown(self): # Get unit test exception - py2_exception = sys.exc_info()[1] - try: - # Python 3.4+ - exception_info = self._outcome.errors[-1][1] if len(self._outcome.errors) > 0 else None - exception = exception_info[1] if exception_info else None - except AttributeError: - try: - # Python 3.3 - exceptions_list = self._outcomeForDoCleanups.failures + self._outcomeForDoCleanups.errors - exception = exceptions_list[0][1] if exceptions_list else None - except AttributeError: - # Python 2.7 - exception = py2_exception + exception_info = self._outcome.errors[-1][1] if len(self._outcome.errors) > 0 else None + exception = exception_info[1] if exception_info else None if not exception: self._test_passed = True diff --git a/toolium/utils/dataset.py b/toolium/utils/dataset.py index d98ea0ee..37301d31 100644 --- a/toolium/utils/dataset.py +++ b/toolium/utils/dataset.py @@ -21,9 +21,6 @@ import datetime import logging -from six import string_types -from six.moves import xrange - logger = logging.getLogger(__name__) @@ -49,7 +46,7 @@ def replace_param(param, language='es'): :param language: language to configure date format for NOW and TODAY :return: data with the correct replacements """ - if not isinstance(param, string_types): + if not isinstance(param, str): return param new_param, is_replaced = _replace_param_type(param) @@ -179,11 +176,11 @@ def _replace_param_fixed_length(param): if any(x in param for x in ['STRING_ARRAY_WITH_LENGTH_', 'INTEGER_ARRAY_WITH_LENGTH_']): seeds = {'STRING': 'a', 'INTEGER': 1} seed, length = param[1:-1].split('_ARRAY_WITH_LENGTH_') - new_param = list(seeds[seed] for x in xrange(int(length))) + new_param = list(seeds[seed] for x in range(int(length))) is_replaced = True elif 'JSON_WITH_LENGTH_' in param: length = int(param[1:-1].split('JSON_WITH_LENGTH_')[1]) - new_param = dict((str(x), str(x)) for x in xrange(length)) + new_param = dict((str(x), str(x)) for x in range(length)) is_replaced = True elif any(x in param for x in ['STRING_WITH_LENGTH_', 'INTEGER_WITH_LENGTH_']): seeds = {'STRING': 'a', 'INTEGER': '1'} diff --git a/toolium/utils/download_files.py b/toolium/utils/download_files.py index 54a687d2..1d867a21 100644 --- a/toolium/utils/download_files.py +++ b/toolium/utils/download_files.py @@ -18,22 +18,16 @@ import difflib import filecmp import os +import requests import time +from urllib.parse import urljoin +from urllib.request import urlretrieve, urlopen -try: - from urllib import urlretrieve, urlopen # Py2 - from urlparse import urljoin -except ImportError: - from urllib.request import urlretrieve, urlopen # Py3 - from urllib.parse import urljoin - -import requests from lxml import html from toolium.driver_wrappers_pool import DriverWrappersPool from toolium.utils.path_utils import makedirs_safe - DOWNLOADS_SERVICE_PORT = 8001 DOWNLOADS_FOLDER = 'downloads' diff --git a/toolium/utils/driver_utils.py b/toolium/utils/driver_utils.py index c7591287..f8acf638 100644 --- a/toolium/utils/driver_utils.py +++ b/toolium/utils/driver_utils.py @@ -16,21 +16,17 @@ limitations under the License. """ -# Python 2.7 -from __future__ import division - import logging import os -import time -from io import open - import requests +import time from datetime import datetime +from io import open from selenium.common.exceptions import NoSuchElementException, TimeoutException, StaleElementReferenceException +from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.common.action_chains import ActionChains -from six.moves.urllib.parse import urlparse # Python 2 and 3 compatibility +from urllib.parse import urlparse from toolium.utils.path_utils import get_valid_filename, makedirs_safe diff --git a/toolium/utils/path_utils.py b/toolium/utils/path_utils.py index 9dac24dd..de7fa9f3 100644 --- a/toolium/utils/path_utils.py +++ b/toolium/utils/path_utils.py @@ -18,9 +18,9 @@ from os import makedirs try: - from os import errno # Py2, < Py3.7 + from os import errno # Py <3.7 except ImportError: - import errno # Py3.7 + import errno # Py 3.7+ import re FILENAME_MAX_LENGTH = 100 diff --git a/toolium/visual_test.py b/toolium/visual_test.py index fe6734c1..29ac4c8c 100644 --- a/toolium/visual_test.py +++ b/toolium/visual_test.py @@ -16,8 +16,6 @@ limitations under the License. """ -from __future__ import division # Python 2.7 - import datetime import itertools import logging @@ -28,7 +26,6 @@ from os import path from selenium.common.exceptions import NoSuchElementException -from six.moves import xrange # Python 2 and 3 compatibility from toolium.driver_wrappers_pool import DriverWrappersPool from toolium.utils.path_utils import get_valid_filename, makedirs_safe @@ -274,8 +271,8 @@ def exclude_elements(self, img, web_elements): for web_element in web_elements: element_box = self.get_element_box(web_element) - for x, y in itertools.product(xrange(element_box[0], element_box[2]), - xrange(element_box[1], element_box[3])): + for x, y in itertools.product(range(element_box[0], element_box[2]), + range(element_box[1], element_box[3])): try: pixel_data[x, y] = (0, 0, 0, 255) except IndexError: