diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a59ccb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +dist +palladium_python.egg-info +palladium/config.json +palladium/chromium \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..222acbd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Siddhant Kushwaha + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/palladium/__init__.py b/palladium/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/palladium/chrome_custom.py b/palladium/chrome_custom.py new file mode 100644 index 0000000..aacfed8 --- /dev/null +++ b/palladium/chrome_custom.py @@ -0,0 +1,75 @@ +import os +import time +import random +import logging + +from collections import Sized + +from selenium.webdriver import Chrome, ChromeOptions + +from palladium import params + + +class ChromeCustom(Chrome): + logs_dir = os.path.join(params.project_dir, 'logs') + + def __init__(self, headless=True): + chrome_options = ChromeOptions() + + if headless: + chrome_options.add_argument('--headless') + chrome_options.add_argument('--no-sandbox') + chrome_options.add_argument('--disable-dev-shm-usage') + chrome_options.add_argument("--window-size=1920,1080") + chrome_options.binary_location = params.chromebinary + + super().__init__(params.chromedriver, options=chrome_options) + + def __del__(self): + try: + self.close() + except: + pass + + def find_element_by_css_selector(self, css_selector): + return self.attempt(lambda: super(Chrome, self).find_element_by_css_selector(css_selector)) + + def find_elements_by_css_selector(self, css_selector): + return self.attempt(lambda: super(Chrome, self).find_elements_by_css_selector(css_selector)) + + def find_element_by_xpath(self, xpath): + return self.attempt(lambda: super(Chrome, self).find_element_by_xpath(xpath)) + + def find_elements_by_xpath(self, xpath): + return self.attempt(lambda: super(Chrome, self).find_elements_by_xpath(xpath)) + + def attempt( + self, + method, + wait_time=4, + total_attempts=4, + ): + os.makedirs(self.logs_dir, exist_ok=True) + for i in range(total_attempts): + try: + val = method() + if isinstance(val, Sized) and len(val) == 0: + raise Exception('Empty sized iterable.') + return val + except Exception as exp: + time.sleep(wait_time) + if i + 1 == total_attempts: + logging.exception(exp) + name = ''.join([str(i) for i in [random.randint(0, 9) for j in range(12)]]) + self.get_screenshot_as_file(os.path.join(self.logs_dir, f'screenshot_{name}.png')) + raise Exception(f'Attempt failed for method. {method}') + + def highlight(self, element, color='red', border=5, effect_time=5): + def apply_style(s): + element._parent.execute_script("arguments[0].setAttribute('style', arguments[1]);", + element, s) + + original_style = element.get_attribute('style') + apply_style("border: {0}px solid {1};".format(border, color)) + time.sleep(effect_time) + apply_style(original_style) diff --git a/palladium/chromium_setup.py b/palladium/chromium_setup.py new file mode 100644 index 0000000..309a663 --- /dev/null +++ b/palladium/chromium_setup.py @@ -0,0 +1,121 @@ +import os +import json +import zipfile +import platform + +from dateutil.parser import parse +from datetime import datetime + +import requests +from viper.download import download + + +def setup(dirpath): + """ ------ Read config/state ----------------------------------------------------------------------------------- """ + + try: + with open(os.path.join(dirpath, 'config.json'), 'r') as config_fp: + config = json.load(config_fp) + except: + config = {} + + modified_time = config.get('modified_time', None) + if modified_time is not None: + modified_time = parse(modified_time) + + current_time = datetime.now() + + if modified_time is not None and (current_time - modified_time).days < 30: + print('Skipping setup.') + return + + config['modified_time'] = current_time.isoformat() + + """ ------ Harcoded values for different formats :/ ------------------------------------------------------------ """ + + binary_id_by_platform = { + 'Darwin': 'mac', + 'Linux': 'linux', + 'Windows': 'win', + } + + driver_id_by_platform = { + 'Darwin': 'mac64', + 'Linux': 'linux64', + 'Windows': 'win32', + } + + prefix_by_platform = { + 'Darwin': 'Mac', + 'Linux': 'Linux_x64', + 'Windows': 'Win', + } + + binary_path_by_platform = { + 'Windows': os.path.join('chrome-win', 'chrome.exe'), + 'Linux': os.path.join('chrome-linux', 'chrome'), + 'Darwin': os.path.join('chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium') + } + + driver_path_by_platform = { + 'Windows': os.path.join('chromedriver_win32', 'chromedriver.exe'), + 'Linux': os.path.join('chromedriver_linux64', 'chromedriver'), + 'Darwin': os.path.join('chromedriver_mac64', 'chromedriver'), + } + + """ ------ Begin download and extraction ----------------------------------------------------------------------- """ + + zipdir = os.path.join(dirpath, 'chromium') + platform_name = platform.system() + + """ ------ Get revision -----------------------------------------------------------------------------------------""" + + revision = requests.get(f'https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/' + f'{prefix_by_platform[platform_name]}%2FLAST_CHANGE?alt=media').content.decode() + + """ ------ Get binary-- -----------------------------------------------------------------------------------------""" + + chromebinary_link = f'https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/' \ + f'{prefix_by_platform[platform_name]}%2F{revision}' \ + f'%2Fchrome-{binary_id_by_platform[platform_name]}.zip?alt=media' + + chromebinary_zipname = f'chromium_{platform_name}.zip' + chromebinary_zippath = os.path.join(zipdir, chromebinary_zipname) + chromebinary_dir = os.path.join(zipdir, 'chromebinary') + + download(link=chromebinary_link, path=zipdir, filename=chromebinary_zipname) + with zipfile.ZipFile(chromebinary_zippath, 'r') as zip_ref: + zip_ref.extractall(chromebinary_dir) + + """ ------ Get driver -------------------------------------------------------------------------------------------""" + + chromedriver_version = requests.get('https://chromedriver.storage.googleapis.com/LATEST_RELEASE').content.decode() + chromedriver_link = f'https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/' \ + f'{prefix_by_platform[platform_name]}%2F{revision}' \ + f'%2Fchromedriver_{driver_id_by_platform[platform_name]}.zip?alt=media' + + chromedriver_zipname = f'chromedriver_{platform_name}.zip' + chromedriver_zippath = os.path.join(zipdir, chromedriver_zipname) + chromedriver_dir = os.path.join(zipdir, 'chromedriver') + + download(link=chromedriver_link, path=zipdir, filename=chromedriver_zipname) + with zipfile.ZipFile(chromedriver_zippath, 'r') as zip_ref: + zip_ref.extractall(chromedriver_dir) + + """ ------ Output and add to config -----------------------------------------------------------------------------""" + + binary_path = os.path.join(zipdir, 'chromebinary', binary_path_by_platform[platform_name]) + driver_path = os.path.join(zipdir, 'chromedriver', driver_path_by_platform[platform_name]) + + print(f'Binary path => {binary_path}') + print(f'Driver path => {driver_path}') + + config['chromebinary'] = binary_path + config['chromedriver'] = driver_path + + with open(os.path.join(dirpath, 'config.json'), 'w') as config_fp: + json.dump(config, config_fp) + + +if __name__ == '__main__': + setup('.') diff --git a/palladium/params.py b/palladium/params.py new file mode 100644 index 0000000..027c016 --- /dev/null +++ b/palladium/params.py @@ -0,0 +1,17 @@ +import os +import sys +import json + +from palladium.chromium_setup import setup + +project_dir = os.path.dirname(sys.modules['__main__'].__file__) + +module_dir = os.path.dirname(os.path.realpath(__file__)) + +setup(module_dir) + +with open(os.path.join(module_dir, 'config.json'), 'r') as fp: + config = json.load(fp) + +chromebinary = config['chromebinary'] +chromedriver = config['chromedriver'] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..557e86a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,26 @@ +bleach==3.1.5 +certifi==2020.6.20 +chardet==3.0.4 +colorama==0.4.3 +docutils==0.16 +idna==2.10 +importlib-metadata==1.7.0 +keyring==21.3.0 +packaging==20.4 +pkginfo==1.5.0.1 +Pygments==2.6.1 +pyparsing==2.4.7 +python-dateutil==2.8.1 +pywin32-ctypes==0.2.0 +readme-renderer==26.0 +requests==2.24.0 +requests-toolbelt==0.9.1 +rfc3986==1.4.0 +selenium==3.141.0 +six==1.15.0 +tqdm==4.48.2 +twine==3.2.0 +urllib3==1.25.10 +viper-python==0.1.0 +webencodings==0.5.1 +zipp==3.1.0 diff --git a/run.py b/run.py new file mode 100644 index 0000000..0083559 --- /dev/null +++ b/run.py @@ -0,0 +1,4 @@ +from palladium.chrome_custom import ChromeCustom + +# driver = ChromeCustom(headless=False) +# driver.get('http://www.google.com') diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c3994ff --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +import pathlib +import setuptools +from distutils.core import setup + +HERE = pathlib.Path(__file__).parent +README = (HERE / "README.md").read_text() + +setup( + name='palladium-python', + packages=['palladium'], + version='0.1.1', + license='MIT', + description='Common utility functions.', + author='Siddhant Kushwaha', + author_email='k16.siddhant@gmail.com', + url='https://github.com/siddhantkushwaha/palladium', + download_url='https://github.com/siddhantkushwaha/palladium/archive/0.1.1.tar.gz', + keywords=['CHROMIUM', 'SELENIUM', 'AUTOMATION', 'TESTING'], + install_requires=[ + 'requests', + 'selenium', + 'python-dateutil', + 'viper-python', + ], + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Build Tools', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + ], + include_package_data=True +)