| #!/usr/bin/env vpython3 | |
| # Copyright 2021 The Chromium Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """A smoke test to verify Chrome doesn't crash and basic rendering is functional | |
| when parsing a newly given variations seed. | |
| """ | |
| import argparse | |
| import http | |
| import json | |
| import logging | |
| import os | |
| import shutil | |
| import sys | |
| import tempfile | |
| from functools import partial | |
| from http.server import SimpleHTTPRequestHandler | |
| from threading import Thread | |
| # vpython-provided modules. | |
| import packaging.version # pylint: disable=import-error | |
| # //third_party/webdriver/pylib imports. | |
| from selenium import webdriver | |
| from selenium.webdriver import ChromeOptions | |
| from selenium.webdriver.chrome.service import Service | |
| from selenium.webdriver.common.by import By | |
| from selenium.common.exceptions import NoSuchElementException | |
| from selenium.common.exceptions import WebDriverException | |
| # //testing/scripts imports. | |
| import common | |
| import variations_seed_access_helper as seed_helper | |
| from skia_gold_infra import finch_skia_gold_utils | |
| _THIS_DIR = os.path.abspath(os.path.dirname(__file__)) | |
| _CHROMIUM_SRC_DIR = os.path.realpath(os.path.join(_THIS_DIR, '..', '..')) | |
| sys.path.append(os.path.join(_CHROMIUM_SRC_DIR, 'build')) | |
| # //build imports. | |
| from skia_gold_common.skia_gold_properties import SkiaGoldProperties | |
| _VARIATIONS_TEST_DATA = 'variations_smoke_test_data' | |
| _VERSION_STRING = 'PRODUCT_VERSION' | |
| _FLAG_RELEASE_VERSION = packaging.version.parse('105.0.5176.3') | |
| # Constants for the waiting for seed from finch server | |
| _MAX_ATTEMPTS = 2 | |
| _WAIT_TIMEOUT_IN_SEC = 0.5 | |
| # Test cases to verify web elements can be rendered correctly. | |
| _TEST_CASES = [ | |
| { | |
| # data:text/html,<h1 id="success">Success</h1> | |
| 'url': 'data:text/html,%3Ch1%20id%3D%22success%22%3ESuccess%3C%2Fh1%3E', | |
| 'expected_id': 'success', | |
| 'expected_text': 'Success', | |
| }, | |
| { | |
| 'url': 'http://localhost:8000', | |
| 'expected_id': 'sites-chrome-userheader-title', | |
| 'expected_text': 'The Chromium Projects', | |
| 'skia_gold_image': 'finch_smoke_render_chromium_org_html', | |
| }, | |
| ] | |
| def _get_httpd(): | |
| """Returns a HTTPServer instance.""" | |
| hostname = 'localhost' | |
| port = 8000 | |
| directory = os.path.join(_THIS_DIR, _VARIATIONS_TEST_DATA, 'http_server') | |
| handler = partial(SimpleHTTPRequestHandler, directory=directory) | |
| httpd = http.server.HTTPServer((hostname, port), handler) | |
| httpd.timeout = 0.5 | |
| httpd.allow_reuse_address = True | |
| return httpd | |
| def _get_platform(): | |
| """Returns the host platform. | |
| Returns: | |
| One of 'linux', 'win' and 'mac'. | |
| """ | |
| if sys.platform == 'win32' or sys.platform == 'cygwin': | |
| return 'win' | |
| if sys.platform.startswith('linux'): | |
| return 'linux' | |
| if sys.platform == 'darwin': | |
| return 'mac' | |
| raise RuntimeError( | |
| 'Unsupported platform: %s. Only Linux (linux*) and Mac (darwin) and ' | |
| 'Windows (win32 or cygwin) are supported' % sys.platform | |
| ) | |
| def _find_chrome_binary(): # pylint: disable=inconsistent-return-statements | |
| """Finds and returns the relative path to the Chrome binary. | |
| This function assumes that the CWD is the build directory. | |
| Returns: | |
| A relative path to the Chrome binary. | |
| """ | |
| platform = _get_platform() | |
| if platform == 'linux': | |
| return os.path.join('.', 'chrome') | |
| if platform == 'mac': | |
| chrome_name = 'Google Chrome' | |
| return os.path.join( | |
| '.', chrome_name + '.app', 'Contents', 'MacOS', chrome_name | |
| ) | |
| if platform == 'win': | |
| return os.path.join('.', 'chrome.exe') | |
| def _run_tests(work_dir, skia_util, *args): | |
| """Runs the smoke tests. | |
| Args: | |
| work_dir: A working directory to store screenshots and other artifacts. | |
| skia_util: A FinchSkiaGoldUtil used to do pixel test. | |
| args: Arguments to be passed to the chrome binary. | |
| Returns: | |
| 0 if tests passed, otherwise 1. | |
| """ | |
| skia_gold_session = skia_util.SkiaGoldSession | |
| path_chrome = _find_chrome_binary() | |
| path_chromedriver = os.path.join('.', 'chromedriver') | |
| hardcoded_seed_path = os.path.join( | |
| _THIS_DIR, | |
| _VARIATIONS_TEST_DATA, | |
| 'variations_seed_beta_%s.json' % _get_platform(), | |
| ) | |
| path_seed = seed_helper.get_test_seed_file_path(hardcoded_seed_path) | |
| user_data_dir = tempfile.mkdtemp() | |
| crash_dump_dir = tempfile.mkdtemp() | |
| _, log_file = tempfile.mkstemp() | |
| # Crashpad is a separate process and its dump locations is set via env | |
| # variable. | |
| os.environ['BREAKPAD_DUMP_LOCATION'] = crash_dump_dir | |
| chrome_options = ChromeOptions() | |
| chrome_options.binary_location = path_chrome | |
| chrome_options.add_argument('user-data-dir=' + user_data_dir) | |
| chrome_options.add_argument('log-file=' + log_file) | |
| chrome_options.add_argument('variations-test-seed-path=' + path_seed) | |
| chrome_options.add_argument('disable-field-trial-config') | |
| for arg in args: | |
| chrome_options.add_argument(arg) | |
| # By default, ChromeDriver passes in --disable-backgroud-networking, however, | |
| # fetching variations seeds requires network connection, so override it. | |
| chrome_options.add_experimental_option( | |
| 'excludeSwitches', ['disable-background-networking'] | |
| ) | |
| driver = None | |
| try: | |
| # Starts Chrome with the test seed injected. | |
| chromedriver_service = Service(executable_path=path_chromedriver) | |
| driver = webdriver.Chrome( | |
| service=chromedriver_service, options=chrome_options | |
| ) | |
| # Run test cases: visit urls and verify certain web elements are rendered | |
| # correctly. | |
| for t in _TEST_CASES: | |
| driver.get(t['url']) | |
| driver.set_window_size(1280, 1024) | |
| element = driver.find_element(By.ID, t['expected_id']) | |
| if not element.is_displayed() or t['expected_text'] != element.text: | |
| logging.error( | |
| 'Test failed because element: "%s" is not visibly found after ' | |
| 'visiting url: "%s"', | |
| t['expected_text'], | |
| t['url'], | |
| ) | |
| return 1 | |
| if 'skia_gold_image' in t: | |
| image_name = t['skia_gold_image'] | |
| sc_file = os.path.join(work_dir, image_name + '.png') | |
| driver.find_element(By.ID, 'body').screenshot(sc_file) | |
| force_dryrun = False | |
| if skia_util.IsTryjobRun and skia_util.IsRetryWithoutPatch: | |
| force_dryrun = True | |
| status, error = skia_gold_session.RunComparison( | |
| name=image_name, png_file=sc_file, force_dryrun=force_dryrun | |
| ) | |
| if status: | |
| finch_skia_gold_utils.log_skia_gold_status_code( | |
| skia_gold_session, image_name, status, error | |
| ) | |
| return status | |
| driver.quit() | |
| except NoSuchElementException as e: | |
| logging.error('Failed to find the expected web element.\n%s', e) | |
| return 1 | |
| except WebDriverException as e: | |
| if os.listdir(crash_dump_dir): | |
| logging.error('Chrome crashed and exited abnormally.\n%s', e) | |
| else: | |
| logging.error('Uncaught WebDriver exception thrown.\n%s', e) | |
| return 1 | |
| finally: | |
| shutil.rmtree(user_data_dir, ignore_errors=True) | |
| shutil.rmtree(crash_dump_dir, ignore_errors=True) | |
| # Print logs for debugging purpose. | |
| with open(log_file) as f: | |
| logging.info('Chrome logs for debugging:\n%s', f.read()) | |
| shutil.rmtree(log_file, ignore_errors=True) | |
| if driver: | |
| driver.quit() | |
| return 0 | |
| def _start_local_http_server(): | |
| """Starts a local http server. | |
| Returns: | |
| A local http.server.HTTPServer. | |
| """ | |
| httpd = _get_httpd() | |
| address = 'http://{}:{}'.format(httpd.server_name, httpd.server_port) | |
| logging.info('%s is used as local http server.', address) | |
| thread = Thread(target=httpd.serve_forever) | |
| thread.setDaemon(True) | |
| thread.start() | |
| return httpd | |
| def main_run(args): | |
| """Runs the variations smoke tests.""" | |
| logging.basicConfig(level=logging.INFO) | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--isolated-script-test-output', type=str) | |
| parser.add_argument('--isolated-script-test-filter', type=str) | |
| SkiaGoldProperties.AddCommandLineArguments(parser) | |
| args, rest = parser.parse_known_args() | |
| temp_dir = tempfile.mkdtemp() | |
| httpd = _start_local_http_server() | |
| skia_util = finch_skia_gold_utils.FinchSkiaGoldUtil(temp_dir, args) | |
| try: | |
| rc = _run_tests(temp_dir, skia_util, *rest) | |
| if args.isolated_script_test_output: | |
| with open(args.isolated_script_test_output, 'w') as f: | |
| common.record_local_script_results( | |
| 'run_variations_smoke_tests', f, [], rc == 0 | |
| ) | |
| finally: | |
| httpd.shutdown() | |
| shutil.rmtree(temp_dir, ignore_errors=True) | |
| return rc | |
| def main_compile_targets(args): | |
| """Returns the list of targets to compile in order to run this test.""" | |
| json.dump(['chrome', 'chromedriver'], args.output) | |
| return 0 | |
| if __name__ == '__main__': | |
| if 'compile_targets' in sys.argv: | |
| funcs = { | |
| 'run': None, | |
| 'compile_targets': main_compile_targets, | |
| } | |
| sys.exit(common.run_script(sys.argv[1:], funcs)) | |
| sys.exit(main_run(sys.argv[1:])) |