diff --git a/odoo/tests/__init__.py b/odoo/tests/__init__.py index 4b678c6f375a7..9be911a80509b 100644 --- a/odoo/tests/__init__.py +++ b/odoo/tests/__init__.py @@ -1,2 +1,3 @@ from . import common from .common import * +from . import selenite \ No newline at end of file diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 2a88090a23ea2..bf5fabb6d0820 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -16,17 +16,14 @@ import re import select import subprocess +import tempfile import threading import time import unittest from contextlib import contextmanager from datetime import datetime, timedelta, date from pprint import pformat -try: - from selenium import webdriver - from selenium.webdriver.common.desired_capabilities import DesiredCapabilities -except ImportError: - webdriver = None +from . import selenite import requests from decorator import decorator @@ -530,15 +527,9 @@ def setUp(self): if not hasattr(self, 'logger'): self.logger = _logger - if webdriver is None: - raise unittest.SkipTest("Selenium not installed") + if not odoo.tools.config['selenium_driver']: + raise unittest.SkipTest("Selenium not installed or missconfigured") self.logger.info('Setting up Selenium test case') - self.chrome_bin_path = '/usr/bin/chromium-browser' - self.chrome_driver_path = '/usr/lib/chromium-browser/chromedriver' - if not os.path.exists(self.chrome_bin_path): - raise unittest.SkipTest("Chrome not found in '{}'".format(self.chrome_bin_path)) - if not os.path.exists(self.chrome_driver_path): - raise unittest.SkipTest("Chrome driver not found in '{}'".format(self.chrome_driver_path)) if self.registry_test_mode: self.registry.enter_test_mode() @@ -552,22 +543,16 @@ def setUp(self): self.opener = requests.Session() self.opener.cookies['session_id'] = self.session_id - # Chrome headless setup - chrome_options = webdriver.ChromeOptions() - chrome_options.binary_location = self.chrome_bin_path - chrome_options.add_argument('--headless') - chrome_options.add_argument('--disable-gpu') - chrome_options.add_argument('--start-maximized') - chrome_options.add_argument('--window-size=1920,1080') - desire = DesiredCapabilities.CHROME - desire['loggingPrefs'] = {'browser': 'ALL'} - self.browser = webdriver.Chrome(self.chrome_driver_path, chrome_options=chrome_options, desired_capabilities=desire) - #desire = DesiredCapabilities.FIREFOX - #self.logger.info('DESIRE: {}'.format()) - #desire['args'] = ['-headless',] - #self.browser = webdriver.Firefox(executable_path='/usr/lib/firefox/geckodriver', capabilities=desire) - self.browser.implicitly_wait(5) - self.addCleanup(self.browser.quit) + # configure the selenium browser driver + driver_select = selenite.DriverSelect(odoo.tools.config['selenium_driver'], driver_path=odoo.tools.config['selenium_driver_path'], browser_path=odoo.tools.config['selenium_browser_path']) + self.driver = driver_select() + self.driver.implicitly_wait(5) + self.addCleanup(self.driver.quit) + + # configure get_screenshots + self.screenshot_path = odoo.tools.config['screenshot_path'] + if not self.screenshot_path or not os.path.isdir(self.screenshot_path) or not os.access(self.screenshot_path, os.W_OK): + self.screenshot_path = tempfile.mkdtemp() self.opener = requests.Session() self.opener.cookies['session_id'] = self.session_id @@ -584,7 +569,7 @@ def _build_url(self, url_path): return "http://{}:{}{}".format(HOST, PORT, url_path) def browser_get(self, url_path): - return self.browser.get(self._build_url(url_path)) + return self.driver.get(self._build_url(url_path)) def authenticate(self, user, password): self.logger.info('Pre authenticate session with user/password: {}/{}'.format(user, password)) @@ -611,7 +596,7 @@ def authenticate(self, user, password): odoo.http.root.session_store.save(session) self.browser_get('/') - self.browser.add_cookie({'domain': '127.0.0.1', 'name': 'session_id', 'value': self.session_id}) + self.driver.add_cookie({'domain': '127.0.0.1', 'name': 'session_id', 'value': self.session_id}) def _wait_ready(self, ready_js_code, max_tries=10): """Selenium should wait for the page to be ready but this is a safeguard.""" @@ -622,7 +607,7 @@ def _wait_ready(self, ready_js_code, max_tries=10): if tries > max_tries: break time.sleep(0.1 * tries) - res = self.browser.execute_script("return {}".format(ready_js_code)) + res = self.driver.execute_script("return {}".format(ready_js_code)) if res: return tries += 1 @@ -633,10 +618,10 @@ def selenium_run(self, url_path, js_code, ready='window', login=None, max_tries= """Runs a js_code javascript test in Chrome headless""" self.authenticate(login, login) self.browser_get(url_path) - self.browser.execute_script('localStorage.clear();') + self.driver.execute_script('localStorage.clear();') self._wait_ready(ready) if ready else self.logger.info('No Ready code, running directly') self.logger.info("Running JS test code: '{}'".format(js_code)) - self.browser.execute_script('return eval({})'.format(js_code)) + self.driver.execute_script('return eval({})'.format(js_code)) tries = 1 start_time = time.time() @@ -644,7 +629,7 @@ def selenium_run(self, url_path, js_code, ready='window', login=None, max_tries= if tries > max_tries: break time.sleep(0.1 * tries) - for log_line in self.browser.get_log('browser'): + for log_line in self.driver.get_log('browser'): if log_line.get('message', '').lower().endswith('"ok"'): waiting_time = time.time() - start_time self.logger.info('JS Test succesfully passed (tries: {} - waiting time: {})'.format(tries, waiting_time)) @@ -663,8 +648,9 @@ def selenium_run(self, url_path, js_code, ready='window', login=None, max_tries= def take_screenshot(self): filename = "selenium-shot_{}.png".format(time.strftime("%Y-%m-%d_%H-%M-%S")) - filepath = os.path.join('/tmp', filename) - self.browser.get_screenshot_as_file(filepath) + filepath = os.path.join(self.screenshot_path, filename) + if self.driver.get_screenshot_as_file(filepath): + _logger.info("Screenshot written in '{}'".format(filepath)) def _wait_remaining_requests(self): t0 = int(time.time()) diff --git a/odoo/tests/selenite.py b/odoo/tests/selenite.py new file mode 100644 index 0000000000000..4045985aabfae --- /dev/null +++ b/odoo/tests/selenite.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import os +import logging +import sys + +from odoo.tools.which import which + +_logger = logging.getLogger(__name__) + +try: + from selenium import webdriver + from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + from selenium.webdriver.firefox.firefox_binary import FirefoxBinary + from selenium.webdriver.firefox.options import Options +except ImportError: + _logger.warning('Selenium not installed, Http tests will be skipped') + webdriver = None + +SELENIUM_DRIVERS = ('chrome', 'chromium', 'firefox') + + +class SeleniteError(Exception): + pass + + +class DriverSelect(): + """A callable object that returns a selenium driver""" + + def __init__(self, driver_name, driver_path=None, browser_path=None): + self.driver_name = driver_name + self.driver_path = driver_path + self.browser_path = browser_path + self._check_pathes() + + def __call__(self): + if self.driver_name == 'chrome': + return self._get_chrome() + elif self.driver_name == 'chromium': + return self._get_chrome(browser_name='chromium') + elif self.driver_name == 'firefox': + return self._get_firefox() + + def _get_chrome(self, browser_name=None): + chrome_options = webdriver.ChromeOptions() + if self.browser_path is not None: + chrome_options.binary_location = self.browser_path + chrome_options.set_headless() + chrome_options.add_argument('--disable-gpu') + chrome_options.add_argument('--start-maximized') + chrome_options.add_argument('--window-size=1920,1080') + desire = DesiredCapabilities.CHROME + if browser_name: + desire['browserName'] = browser_name + desire['loggingPrefs'] = {'browser': 'ALL'} + if self.driver_path: + return webdriver.Chrome(self.driver_path, chrome_options=chrome_options, desired_capabilities=desire) + else: + return webdriver.Chrome(chrome_options=chrome_options, desired_capabilities=desire) + + def _get_firefox(self): + __logger.warning("Firefox doesn't allow to fetch console log. Your tests are willing to fail.") + firefox_capabilities = DesiredCapabilities.FIREFOX + if not self.browser_path: + self.browser_path = which('firefox') + desire = DesiredCapabilities.FIREFOX + options = Options() + options.set_headless() + binary = FirefoxBinary(self.browser_path) + if self.driver_path: + return webdriver.Firefox(executable_path=self.driver_path, firefox_binary=binary, firefox_options=options, log_path='/dev/null') + else: + return webdriver.Firefox(firefox_binary=binary, firefox_options=options) + + def _check_pathes(self): + if self.driver_path and not os.path.exists(self.driver_path): + raise SeleniteError("Driver not found") + if self.browser_path and not os.path.exists(self.browser_path): + raise SeleniteError("Browser not found") diff --git a/odoo/tools/config.py b/odoo/tools/config.py index 43d11f7b532a1..ee5fdedcfb54b 100644 --- a/odoo/tools/config.py +++ b/odoo/tools/config.py @@ -11,9 +11,12 @@ import os import sys import odoo + from .. import release, conf, loglevels from . import appdirs, pycompat +SELENIUM_DRIVERS = ('chrome', 'chromium', 'firefox') + from passlib.context import CryptContext crypt_context = CryptContext(schemes=['pbkdf2_sha512', 'plaintext'], deprecated=['plaintext']) @@ -153,6 +156,16 @@ def __init__(self, fname=None): # Testing Group group = optparse.OptionGroup(parser, "Testing Configuration") + group.add_option("--selenium-driver", dest="selenium_driver", my_default="chrome", + type="choice", + choices=SELENIUM_DRIVERS, + help="Selenium driver/browser to use. Supported: {}".format('/'.join(SELENIUM_DRIVERS))) + group.add_option("--driver-path", dest="selenium_driver_path", my_default='', + help="Path to the selenium driver (default auto discover)") + group.add_option("--browser-path", dest="selenium_browser_path", my_default='', + help="Path to the browser used by selenium driver (default auto discover)") + group.add_option("--screenshot-path", dest="screenshot_path", my_default='', + help="Where to put screenshots when a selenium test fails") group.add_option("--test-file", dest="test_file", my_default=False, help="Launch a python test file.") group.add_option("--test-report-directory", dest="test_report_directory", my_default=False, @@ -426,6 +439,7 @@ def die(cond, msg): 'stop_after_init', 'logrotate', 'without_demo', 'http_enable', 'syslog', 'list_db', 'proxy_mode', 'test_file', 'test_enable', 'test_commit', 'test_report_directory', + 'selenium_driver', 'selenium_driver_path', 'selenium_browser_path', 'screenshot_path', 'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent', 'data_dir', 'server_wide_modules',