# 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.
import json
import logging
import os
import posixpath
import sys
import time
import zipfile
from collections import namedtuple
from devil.android import apk_helper
from devil.android import logcat_monitor
from py_utils.tempfile_ext import NamedTemporaryDirectory
from telemetry.core import util
from telemetry.testing import serially_executed_browser_test_case
logger = logging.getLogger(__name__)
ComponentData = namedtuple('ComponentData', ['component_id', 'browser_args'])
_SET_CONDITION_HTML = """
var test_harness = {}
test_harness.test_succeeded = true;
window.webview_smoke_test_harness = test_harness;
"""
_COMPONENT_NAME_TO_DATA = {
'WebViewAppsPackageNamesAllowlist': ComponentData(
component_id = 'aemllinfpjdgcldgaelcgakpjmaekbai',
browser_args = ['--vmodule=*_allowlist_component_*=2'])
}
_LOGCAT_FILTERS = [
'chromium:v',
'cr_*:v',
'DEBUG:I',
'StrictMode:D',
'WebView*:v'
]
class WebViewCrxSmokeTests(
serially_executed_browser_test_case.SeriallyExecutedBrowserTestCase):
_device = None
_device_components_dir = None
_logcat_monitor = None
@classmethod
def Name(cls):
return 'webview_crx_smoke_tests'
@classmethod
def SetBrowserOptions(cls, finder_options):
"""Sets browser command line arguments
Args:
finder_options: Command line arguments for starting the browser"""
finder_options_copy = finder_options.Copy()
finder_options_copy.browser_options.AppendExtraBrowserArgs(
_COMPONENT_NAME_TO_DATA[finder_options.component_name].browser_args)
super(WebViewCrxSmokeTests, cls).SetBrowserOptions(finder_options_copy)
cls._device = cls._browser_to_create._platform_backend.device
@classmethod
def StartBrowser(cls):
"""Start browser and wait for it's pid to appear in the processes list"""
super(WebViewCrxSmokeTests, cls).StartBrowser()
# Wait until the browser is up and running
for _ in range(60):
logger.info('Waiting 1 second for the browser to start running')
time.sleep(1)
if cls.browser._browser_backend.processes:
break
assert cls.browser._browser_backend.processes, 'Browser did not start'
logger.info('Browser is running')
@classmethod
def SetUpProcess(cls):
"""Prepares the test device"""
super(WebViewCrxSmokeTests, cls).SetUpProcess()
assert cls._finder_options.crx_file, '--crx-file is required'
assert cls._finder_options.component_name, '--component-name is required'
cls.SetBrowserOptions(cls._finder_options)
webview_package_name = cls._finder_options.webview_package_name
if not webview_package_name:
webview_provider_apk = (cls._browser_to_create
.settings.GetApkName(cls._device))
webview_apk_path = util.FindLatestApkOnHost(
cls._finder_options.chrome_root, webview_provider_apk)
webview_package_name = apk_helper.GetPackageName(webview_apk_path)
cls._device_components_dir = ('/data/data/%s/app_webview/components' %
webview_package_name)
if cls.child.artifact_output_dir:
logcat_output_dir = os.path.dirname(cls.child.artifact_output_dir)
else:
logcat_output_dir = os.getcwd()
# Set up a logcat monitor
cls._logcat_monitor = logcat_monitor.LogcatMonitor(
cls._device.adb,
output_file=os.path.join(logcat_output_dir,
'%s_logcat.txt' % cls.Name()),
filter_specs=_LOGCAT_FILTERS)
cls._logcat_monitor.Start()
cls._MaybeClearOutComponentsDir()
component_id = _COMPONENT_NAME_TO_DATA.get(
cls._finder_options.component_name).component_id
with zipfile.ZipFile(cls._finder_options.crx_file) as crx_archive, \
NamedTemporaryDirectory() as tmp_dir, \
crx_archive.open('manifest.json') as manifest:
crx_device_dir = posixpath.join(
cls._device_components_dir, 'cps',
component_id, '1_%s' % json.loads(manifest.read())['version'])
try:
# Create directory on the test device for the CRX files
logger.info('Creating directory %r on device' % crx_device_dir)
output = cls._device.RunShellCommand(
['mkdir', '-p', crx_device_dir])
logger.debug('Recieved the following output from adb: %s' % output)
except Exception as e:
logger.exception('Exception %r was raised' % str(e))
raise
# Move CRX files to the device directory
crx_archive.extractall(tmp_dir)
cls._MoveNewCrxToDevice(tmp_dir, crx_device_dir)
# Start the browser after the device is in a clean state and the CRX
# files are loaded onto the device
cls.StartBrowser()
@classmethod
def _MoveNewCrxToDevice(cls, src_dir, crx_device_dir):
"""Pushes the CRX files onto the test device
Args:
src_dir: Source directory containing CRX files
crx_device_dir: Destination directory for CRX files on the test device"""
for f in os.listdir(src_dir):
src_path = os.path.join(src_dir, f)
dest_path = posixpath.join(crx_device_dir, f)
assert os.path.isfile(src_path), '%r is not a file' % src_path
logger.info('Pushing %r to %r' % (src_path, dest_path))
cls._device.adb.Push(src_path, dest_path)
@classmethod
def _MaybeClearOutComponentsDir(cls):
"""Clears out CRX files on test device so that
the test starts with a clean state """
try:
output = cls._device.RemovePath(cls._device_components_dir,
recursive=True,
force=True)
logger.debug('Recieved the following output from adb: %s' % output)
except Exception as e:
logger.exception('Exception %r was raised' % str(e))
raise
@classmethod
def AddCommandlineArgs(cls, parser):
"""Adds test suite specific command line arguments"""
parser.add_argument('--crx-file',
action='store',
help='Path to CRX file')
parser.add_argument('--component-name',
action='store',
help='Component name',
choices=list(_COMPONENT_NAME_TO_DATA.keys()))
parser.add_argument('--webview-package-name',
action='store',
help='WebView package name')
def Test_run_webview_smoke_test(self):
"""Test waits for a javascript condition to be set on the WebView shell"""
browser_tab = self.browser.tabs[0]
browser_tab.action_runner.Navigate(
'about:blank', script_to_evaluate_on_commit=_SET_CONDITION_HTML)
# Wait 5 minutes for the javascript condition. If the condition is not
# set within 5 minutes then an exception will be raised which will cause
# the test to fail.
browser_tab.action_runner.WaitForJavaScriptCondition(
'window.webview_smoke_test_harness.test_succeeded', timeout=300)
@classmethod
def TearDownProcess(cls):
super(WebViewCrxSmokeTests, cls).TearDownProcess()
cls._logcat_monitor.Stop()
def load_tests(*_):
return serially_executed_browser_test_case.LoadAllTestsInModule(sys.modules[__name__])