Commit 7b2cf724 authored by tengs's avatar tengs Committed by Commit bot

Add end-to-end testing tool for Smart Lock.

This tool is based on telemetry and exercises the Smart Lock setup flow. This CL
also introduces the framework that will allow implementing other end-to-end
tests.

BUG=chromium:442728
NOTRY=true

Review URL: https://codereview.chromium.org/1004283002

Cr-Commit-Position: refs/heads/master@{#322604}
parent 4f788f02
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
import subprocess
import sys
import time
# Add the telemetry directory to Python's search paths.
current_directory = os.path.dirname(os.path.realpath(__file__))
telemetry_dir = os.path.realpath(
os.path.join(current_directory, '..', '..', '..', 'tools', 'telemetry'))
if telemetry_dir not in sys.path:
sys.path.append(telemetry_dir)
from telemetry.core import browser_options
from telemetry.core import browser_finder
from telemetry.core import extension_to_load
from telemetry.core import exceptions
from telemetry.core import util
from telemetry.core.platform import cros_interface
logger = logging.getLogger('proximity_auth.%s' % __name__)
class AccountPickerScreen(object):
""" Wrapper for the ChromeOS account picker screen.
The account picker screen is the WebContents page used for both the lock
screen and signin screen.
Note: This class assumes the account picker screen only has one user. If there
are multiple user pods, the first one will be used.
"""
class AuthType:
""" The authentication type expected for a user pod. """
OFFLINE_PASSWORD = 0
ONLINE_SIGN_IN = 1
NUMERIC_PIN = 2
USER_CLICK = 3
EXPAND_THEN_USER_CLICK = 4
FORCE_OFFLINE_PASSWORD = 5
class SmartLockState:
""" The state of the Smart Lock icon on a user pod.
"""
NOT_SHOWN = 'not_shown'
AUTHENTICATED = 'authenticated'
LOCKED = 'locked'
HARD_LOCKED = 'hardlocked'
TO_BE_ACTIVATED = 'to_be_activated'
SPINNER = 'spinner'
# JavaScript expression for getting the user pod on the page
_GET_POD_JS = 'document.getElementById("pod-row").pods[0]'
def __init__(self, oobe, chromeos):
"""
Args:
oobe: Inspector page of the OOBE WebContents.
chromeos: The parent Chrome wrapper.
"""
self._oobe = oobe
self._chromeos = chromeos
@property
def is_lockscreen(self):
return self._oobe.EvaluateJavaScript(
'!document.getElementById("sign-out-user-item").hidden')
@property
def auth_type(self):
return self._oobe.EvaluateJavaScript('%s.authType' % self._GET_POD_JS)
@property
def smart_lock_state(self):
icon_shown = self._oobe.EvaluateJavaScript(
'!%s.customIconElement.hidden' % self._GET_POD_JS)
if not icon_shown:
return self.SmartLockState.NOT_SHOWN
class_list_dict = self._oobe.EvaluateJavaScript(
'%s.customIconElement.querySelector(".custom-icon")'
'.classList' % self._GET_POD_JS)
class_list = [v for k,v in class_list_dict.items() if k != 'length']
if 'custom-icon-unlocked' in class_list:
return self.SmartLockState.AUTHENTICATED
if 'custom-icon-locked' in class_list:
return self.SmartLockState.LOCKED
if 'custom-icon-hardlocked' in class_list:
return self.SmartLockState.HARD_LOCKED
if 'custom-icon-locked-to-be-activated' in class_list:
return self.SmartLockState.TO_BE_ACTIVATED
if 'custom-icon-spinner' in class_list:
return self.SmartLockState.SPINNER
def WaitForSmartLockState(self, state, wait_time_secs=60):
""" Waits for the Smart Lock icon to reach the given state.
Args:
state: A value in AccountPickerScreen.SmartLockState
wait_time_secs: The time to wait
Returns:
True if the state is reached within the wait time, else False.
"""
try:
util.WaitFor(lambda: self.smart_lock_state == state, wait_time_secs)
return True
except exceptions.TimeoutException:
return False
def EnterPassword(self):
""" Enters the password to unlock or sign-in.
Raises:
TimeoutException: entering the password fails to enter/resume the user
session.
"""
assert(self.auth_type == self.AuthType.OFFLINE_PASSWORD or
self.auth_type == self.AuthType.FORCE_OFFLINE_PASSWORD)
oobe = self._oobe
oobe.EvaluateJavaScript(
'%s.passwordElement.value = "%s"' % (
self._GET_POD_JS, self._chromeos.password))
oobe.EvaluateJavaScript(
'%s.activate()' % self._GET_POD_JS)
util.WaitFor(lambda: (self._chromeos.session_state ==
ChromeOS.SessionState.IN_SESSION),
5)
def UnlockWithClick(self):
""" Clicks the user pod to unlock or sign-in. """
assert(self.auth_type == self.AuthType.USER_CLICK)
self._oobe.EvaluateJavaScript('%s.activate()' % self._GET_POD_JS)
class SmartLockSettings(object):
""" Wrapper for the Smart Lock settings in chromeos://settings.
"""
def __init__(self, tab, chromeos):
"""
Args:
tab: Inspector page of the chromeos://settings tag.
chromeos: The parent Chrome wrapper.
"""
self._tab = tab
self._chromeos = chromeos
@property
def is_smart_lock_enabled(self):
''' Returns true if the settings show that Smart Lock is enabled. '''
return self._tab.EvaluateJavaScript(
'!document.getElementById("easy-unlock-enabled").hidden')
def TurnOffSmartLock(self):
""" Turns off Smart Lock.
Smart Lock is turned off by clicking the turn-off button and navigating
through the resulting overlay.
Raises:
TimeoutException: Timed out waiting for Smart Lock to be turned off.
"""
assert(self.is_smart_lock_enabled)
tab = self._tab
tab.EvaluateJavaScript(
'document.getElementById("easy-unlock-turn-off-button").click()')
util.WaitFor(lambda: tab.EvaluateJavaScript(
'!document.getElementById("easy-unlock-turn-off-overlay").hidden && '
'document.getElementById("easy-unlock-turn-off-confirm") != null'),
10)
tab.EvaluateJavaScript(
'document.getElementById("easy-unlock-turn-off-confirm").click()')
util.WaitFor(lambda: tab.EvaluateJavaScript(
'!document.getElementById("easy-unlock-disabled").hidden'), 15)
def StartSetup(self):
""" Starts the Smart Lock setup flow by clicking the button.
"""
assert(not self.is_smart_lock_enabled)
self._tab.EvaluateJavaScript(
'document.getElementById("easy-unlock-setup-button").click()')
def StartSetupAndReturnApp(self):
""" Runs the setup and returns the wrapper to the setup app.
After clicking the setup button in the settings page, enter the password to
reauthenticate the user before the app launches.
Returns:
A SmartLockApp object of the app that was launched.
Raises:
TimeoutException: Timed out waiting for app.
"""
self.StartSetup()
util.WaitFor(lambda: (self._chromeos.session_state ==
ChromeOS.SessionState.LOCK_SCREEN),
5)
lock_screen = self._chromeos.GetAccountPickerScreen()
lock_screen.EnterPassword()
util.WaitFor(lambda: self._chromeos.GetSmartLockApp() is not None, 10)
return self._chromeos.GetSmartLockApp()
class SmartLockApp(object):
""" Wrapper for the Smart Lock setup dialog.
Note: This does not include the app's background page.
"""
class PairingState:
""" The current state of the setup flow. """
SCAN = 'scan'
PAIR = 'pair'
CLICK_FOR_TRIAL_RUN = 'click_for_trial_run'
TRIAL_RUN_COMPLETED = 'trial_run_completed'
def __init__(self, app_page, chromeos):
"""
Args:
app_page: Inspector page of the app window.
chromeos: The parent Chrome wrapper.
"""
self._app_page = app_page
self._chromeos = chromeos
@property
def pairing_state(self):
''' Returns the state the app is currently in.
Raises:
ValueError: The current state is unknown.
'''
state = self._app_page.EvaluateJavaScript(
'document.body.getAttribute("step")')
if state == 'scan':
return SmartLockApp.PairingState.SCAN
elif state == 'pair':
return SmartLockApp.PairingState.PAIR
elif state == 'complete':
button_text = self._app_page.EvaluateJavaScript(
'document.getElementById("pairing-button").textContent')
button_text = button_text.strip().lower()
if button_text == 'try it out':
return SmartLockApp.PairingState.CLICK_FOR_TRIAL_RUN
elif button_text == 'done':
return SmartLockApp.PairingState.TRIAL_RUN_COMPLETED
else:
raise ValueError('Unknown button text: %s', button_text)
else:
raise ValueError('Unknown pairing state: %s' % state)
def FindPhone(self, retries=3):
""" Starts the initial step to find nearby phones.
The app must be in the SCAN state.
Args:
retries: The number of times to retry if no phones are found.
Returns:
True if a phone is found, else False.
"""
assert(self.pairing_state == self.PairingState.SCAN)
for _ in xrange(retries):
self._ClickPairingButton()
if self.pairing_state == self.PairingState.PAIR:
return True
# Wait a few seconds before retrying.
time.sleep(10)
return False
def PairPhone(self):
""" Starts the step of finding nearby phones.
The app must be in the PAIR state.
Returns:
True if pairing succeeded, else False.
"""
assert(self.pairing_state == self.PairingState.PAIR)
self._ClickPairingButton()
return self.pairing_state == self.PairingState.CLICK_FOR_TRIAL_RUN
def StartTrialRun(self):
""" Starts the trial run.
The app must be in the CLICK_FOR_TRIAL_RUN state.
Raises:
TimeoutException: Timed out starting the trial run.
"""
assert(self.pairing_state == self.PairingState.CLICK_FOR_TRIAL_RUN)
self._app_page.EvaluateJavaScript(
'document.getElementById("pairing-button").click()')
util.WaitFor(lambda: (self._chromeos.session_state ==
ChromeOS.SessionState.LOCK_SCREEN),
10)
def DismissApp(self):
""" Dismisses the app after setup is completed.
The app must be in the TRIAL_RUN_COMPLETED state.
"""
assert(self.pairing_state == self.PairingState.TRIAL_RUN_COMPLETED)
self._app_page.EvaluateJavaScript(
'document.getElementById("pairing-button").click()')
def _ClickPairingButton(self):
self._app_page.EvaluateJavaScript(
'document.getElementById("pairing-button").click()')
util.WaitFor(lambda: self._app_page.EvaluateJavaScript(
'!document.getElementById("pairing-button").disabled'), 60)
util.WaitFor(lambda: self._app_page.EvaluateJavaScript(
'!document.getElementById("pairing-button-title")'
'.classList.contains("animated-fade-out")'), 5)
util.WaitFor(lambda: self._app_page.EvaluateJavaScript(
'!document.getElementById("pairing-button-title")'
'.classList.contains("animated-fade-in")'), 5)
class ChromeOS(object):
""" Wrapper for a remote ChromeOS device.
Operations performed through this wrapper are sent through the network to
Chrome using the Chrome DevTools API. Therefore, any function may throw an
exception if the communication to the remote device is severed.
"""
class SessionState:
""" The state of the user session.
"""
SIGNIN_SCREEN = 'signin_screen'
IN_SESSION = 'in_session'
LOCK_SCREEN = 'lock_screen'
_SMART_LOCK_SETTINGS_URL = 'chrome://settings/search#Smart%20Lock'
def __init__(self, remote_address, username, password, ssh_port=None):
"""
Args:
remote_address: The remote address of the cros device.
username: The username of the account to test.
password: The password of the account to test.
ssh_port: The ssh port to connect to.
"""
self._remote_address = remote_address;
self._username = username
self._password = password
self._ssh_port = ssh_port
self._browser = None
self._cros_interface = None
self._background_page = None
self._processes = []
@property
def username(self):
''' Returns the username of the user to login. '''
return self._username
@property
def password(self):
''' Returns the password of the user to login. '''
return self._password
@property
def session_state(self):
''' Returns the state of the user session. '''
assert(self._browser is not None)
if self._browser.oobe_exists:
if self._cros_interface.IsCryptohomeMounted(self.username, False):
return self.SessionState.LOCK_SCREEN
else:
return self.SessionState.SIGNIN_SCREEN
else:
return self.SessionState.IN_SESSION;
@property
def cryptauth_access_token(self):
try:
util.WaitFor(lambda: self._background_page.EvaluateJavaScript(
'var __token = __token || null; '
'chrome.identity.getAuthToken(function(token) {'
' __token = token;'
'}); '
'__token != null'), 5)
return self._background_page.EvaluateJavaScript('__token');
except exceptions.TimeoutException:
logger.error('Failed to get access token.');
return ''
def __enter__(self):
return self
def __exit__(self, *args):
if self._browser is not None:
self._browser.Close()
if self._cros_interface is not None:
self._cros_interface.CloseConnection()
for process in self._processes:
process.terminate()
def Start(self, local_app_path=None):
""" Connects to the ChromeOS device and logs in.
Args:
local_app_path: A path on the local device containing the Smart Lock app
to use instead of the app on the ChromeOS device.
Return:
|self| for using in a "with" statement.
"""
assert(self._browser is None)
finder_opts = browser_options.BrowserFinderOptions('cros-chrome')
finder_opts.CreateParser().parse_args(args=[])
finder_opts.cros_remote = self._remote_address
if self._ssh_port is not None:
finder_opts.cros_remote_ssh_port = self._ssh_port
finder_opts.verbosity = 1
browser_opts = finder_opts.browser_options
browser_opts.create_browser_with_oobe = True
browser_opts.disable_component_extensions_with_background_pages = False
browser_opts.gaia_login = True
browser_opts.username = self._username
browser_opts.password = self._password
browser_opts.auto_login = True
self._cros_interface = cros_interface.CrOSInterface(
finder_opts.cros_remote,
finder_opts.cros_remote_ssh_port,
finder_opts.cros_ssh_identity)
browser_opts.disable_default_apps = local_app_path is not None
if local_app_path is not None:
easy_unlock_app = extension_to_load.ExtensionToLoad(
path=local_app_path,
browser_type='cros-chrome',
is_component=True)
finder_opts.extensions_to_load.append(easy_unlock_app)
retries = 3
while self._browser is not None or retries > 0:
try:
browser_to_create = browser_finder.FindBrowser(finder_opts)
self._browser = browser_to_create.Create(finder_opts);
break;
except (exceptions.LoginException) as e:
logger.error('Timed out logging in: %s' % e);
if retries == 1:
raise
bg_page_path = '/_generated_background_page.html'
util.WaitFor(
lambda: self._FindSmartLockAppPage(bg_page_path) is not None,
10);
self._background_page = self._FindSmartLockAppPage(bg_page_path)
return self
def GetAccountPickerScreen(self):
""" Returns the wrapper for the lock screen or sign-in screen.
Return:
An instance of AccountPickerScreen.
Raises:
TimeoutException: Timed out waiting for account picker screen to load.
"""
assert(self._browser is not None)
assert(self.session_state == self.SessionState.LOCK_SCREEN or
self.session_state == self.SessionState.SIGNIN_SCREEN)
oobe = self._browser.oobe
def IsLockScreenResponsive():
return (oobe.EvaluateJavaScript("typeof Oobe == 'function'") and
oobe.EvaluateJavaScript(
"typeof Oobe.authenticateForTesting == 'function'"))
util.WaitFor(IsLockScreenResponsive, 10)
util.WaitFor(lambda: oobe.EvaluateJavaScript(
'document.getElementById("pod-row") && '
'document.getElementById("pod-row").pods && '
'document.getElementById("pod-row").pods.length > 0'), 10)
return AccountPickerScreen(oobe, self)
def GetSmartLockSettings(self):
""" Returns the wrapper for the Smart Lock settings.
A tab will be navigated to chrome://settings if it does not exist.
Return:
An instance of SmartLockSettings.
Raises:
TimeoutException: Timed out waiting for settings page.
"""
if not len(self._browser.tabs):
self._browser.New()
tab = self._browser.tabs[0]
url = tab.EvaluateJavaScript('document.location.href')
if url != self._SMART_LOCK_SETTINGS_URL:
tab.Navigate(self._SMART_LOCK_SETTINGS_URL)
# Wait for settings page to be responsive.
util.WaitFor(lambda: tab.EvaluateJavaScript(
'document.getElementById("easy-unlock-disabled") && '
'document.getElementById("easy-unlock-enabled") && '
'(!document.getElementById("easy-unlock-disabled").hidden || '
' !document.getElementById("easy-unlock-enabled").hidden)'), 10)
settings = SmartLockSettings(tab, self)
logger.info('Started Smart Lock settings: enabled=%s' %
settings.is_smart_lock_enabled)
return settings
def GetSmartLockApp(self):
""" Returns the wrapper for the Smart Lock setup app.
Return:
An instance of SmartLockApp or None if the app window does not exist.
"""
app_page = self._FindSmartLockAppPage('/pairing.html')
if app_page is not None:
# Wait for app window to be responsive.
util.WaitFor(lambda: app_page.EvaluateJavaScript(
'document.getElementById("pairing-button") != null'), 10)
return SmartLockApp(app_page, self)
return None
def RunBtmon(self):
""" Runs the btmon command.
Return:
A subprocess.Popen object of the btmon process.
"""
assert(self._cros_interface)
cmd = self._cros_interface.FormSSHCommandLine(['btmon'])
process = subprocess.Popen(args=cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
self._processes.append(process)
return process
def _FindSmartLockAppPage(self, page_name):
try:
extensions = self._browser.extensions.GetByExtensionId(
'mkaemigholebcgchlkbankmihknojeak')
except KeyError:
return None
for extension_page in extensions:
pathname = extension_page.EvaluateJavaScript('document.location.pathname')
if pathname == page_name:
return extension_page
return None
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import httplib
import json
import logging
import pprint
import time
logger = logging.getLogger('proximity_auth.%s' % __name__)
_GOOGLE_APIS_URL = 'www.googleapis.com'
_REQUEST_PATH = '/cryptauth/v1/%s?alt=JSON'
class CryptAuthClient(object):
""" A client for making blocking CryptAuth API calls. """
def __init__(self, access_token, google_apis_url=_GOOGLE_APIS_URL):
self._access_token = access_token
self._google_apis_url = google_apis_url
def GetMyDevices(self):
""" Invokes the GetMyDevices API.
Returns:
A list of devices or None if the API call fails.
Each device is a dictionary of the deserialized JSON returned by
CryptAuth.
"""
request_data = {
'approvedForUnlockRequired': False,
'allowStaleRead': False,
'invocationReason': 13 # REASON_MANUAL
}
response = self._SendRequest('deviceSync/getmydevices', request_data)
return response['devices'] if response is not None else None
def GetUnlockKey(self):
"""
Returns:
The unlock key registered with CryptAuth if it exists or None.
The device is a dictionary of the deserialized JSON returned by CryptAuth.
"""
devices = self.GetMyDevices()
if devices is None:
return None
for device in devices:
if device['unlockKey']:
return device
return None
def ToggleEasyUnlock(self, enable, public_key=''):
""" Calls the ToggleEasyUnlock API.
Args:
enable: True to designate the device specified by |public_key| as an
unlock key.
public_key: The public key of the device to toggle. Ignored if |enable| is
False, which toggles all unlock keys off.
Returns:
True upon success, else False.
"""
request_data = { 'enable': enable, }
if not enable:
request_data['applyToAll'] = True
else:
request_data['publicKey'] = public_key
response = self._SendRequest('deviceSync/toggleeasyunlock', request_data)
return response is not None
def FindEligibleUnlockDevices(self, time_delta_millis=None):
""" Finds devices eligible to be an unlock key.
Args:
time_delta_millis: If specified, then only return eligible devices that
have contacted CryptAuth in the last time delta.
Returns:
A tuple containing two lists, one of eligible devices and the other of
ineligible devices.
Each device is a dictionary of the deserialized JSON returned by
CryptAuth.
"""
request_data = {}
if time_delta_millis is not None:
request_data['maxLastUpdateTimeDeltaMillis'] = time_delta_millis * 1000;
response = self._SendRequest(
'deviceSync/findeligibleunlockdevices', request_data)
if response is None:
return None
eligibleDevices = (
response['eligibleDevices'] if 'eligibleDevices' in response else [])
ineligibleDevices = (
response['ineligibleDevices'] if (
'ineligibleDevices' in response) else [])
return eligibleDevices, ineligibleDevices
def PingPhones(self, timeout_secs=10):
""" Asks CryptAuth to ping registered phones and determine their status.
Args:
timeout_secs: The number of seconds to wait for phones to respond.
Returns:
A tuple containing two lists, one of eligible devices and the other of
ineligible devices.
Each device is a dictionary of the deserialized JSON returned by
CryptAuth.
"""
response = self._SendRequest(
'deviceSync/senddevicesynctickle',
{ 'tickleType': 'updateEnrollment' })
if response is None:
return None
# We wait for phones to update their status with CryptAuth.
logger.info('Waiting for %s seconds for phone status...' % timeout_secs)
time.sleep(timeout_secs)
return self.FindEligibleUnlockDevices(time_delta_millis=timeout_secs)
def _SendRequest(self, function_path, request_data):
""" Sends an HTTP request to CryptAuth and returns the deserialized
response.
"""
conn = httplib.HTTPSConnection(self._google_apis_url)
path = _REQUEST_PATH % function_path
headers = {
'authorization': 'Bearer ' + self._access_token,
'Content-Type': 'application/json'
}
body = json.dumps(request_data)
logger.info('Making request to %s with body:\n%s' % (
path, pprint.pformat(request_data)))
conn.request('POST', path, body, headers)
response = conn.getresponse()
if response.status == 204:
return {}
if response.status != 200:
logger.warning('Request to %s failed: %s' % (path, response.status))
return None
return json.loads(response.read())
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Script that exercises the Smart Lock setup flow, testing that a nearby phone
can be found and used to unlock a Chromebook.
Note: This script does not currently automate Android phones, so make sure that
a phone is properly configured and online before starting the test.
Usage:
python setup_test.py --remote_address REMOTE_ADDRESS
--username USERNAME
--password PASSWORD
[--app_path APP_PATH]
[--ssh_port SSH_PORT]
If |--app_path| is provided, then a copy of the Smart Lock app on the local
machine will be used instead of the app on the ChromeOS device.
"""
import argparse
import cros
import cryptauth
import logging
import os
import subprocess
import sys
import tempfile
logger = logging.getLogger('proximity_auth.%s' % __name__)
class SmartLockSetupError(Exception):
pass
def pingable_address(address):
try:
subprocess.check_output(['ping', '-c', '1', '-W', '1', address])
except subprocess.CalledProcessError:
raise argparse.ArgumentError('%s cannot be reached.' % address)
return address
def email(arg):
tokens = arg.lower().split('@')
if len(tokens) != 2 or '.' not in tokens[1]:
raise argparse.ArgumentError('%s is not a valid email address' % arg)
name, domain = tokens
if domain == 'gmail.com':
name = name.replace('.', '')
return '@'.join([name, domain])
def directory(path):
if not os.path.isdir(path):
raise argparse.ArgumentError('%s is not a directory' % path)
return path
def ParseArgs():
parser = argparse.ArgumentParser(prog='python setup_test.py')
parser.add_argument('--remote_address', required=True, type=pingable_address)
parser.add_argument('--username', required=True, type=email)
parser.add_argument('--password', required=True)
parser.add_argument('--ssh_port', type=int)
parser.add_argument('--app_path', type=directory)
args = parser.parse_args()
return args
def CheckCryptAuthState(access_token):
cryptauth_client = cryptauth.CryptAuthClient(access_token)
# Check if we can make CryptAuth requests.
if cryptauth_client.GetMyDevices() is None:
logger.error('Cannot reach CryptAuth on test machine.')
return False
if cryptauth_client.GetUnlockKey() is not None:
logger.info('Smart Lock currently enabled, turning off on Cryptauth...')
if not cryptauth_client.ToggleEasyUnlock(False):
logger.error('ToggleEasyUnlock request failed.')
return False
result = cryptauth_client.FindEligibleUnlockDevices()
if result is None:
logger.error('FindEligibleUnlockDevices request failed')
return False
eligibleDevices, _ = result
if len(eligibleDevices) == 0:
logger.warn('No eligible phones found, trying to ping phones...')
result = cryptauth_client.PingPhones()
if result is None or not len(result[0]):
logger.error('Pinging phones failed :(')
return False
else:
logger.info('Pinging phones succeeded!')
else:
logger.info('Found eligible device: %s' % (
eligibleDevices[0]['friendlyDeviceName']))
return True
def _NavigateSetupDialog(chromeos, app):
logger.info('Scanning for nearby phones...')
btmon = chromeos.RunBtmon()
find_phone_success = app.FindPhone()
btmon.terminate()
if not find_phone_success:
fd, filepath = tempfile.mkstemp(prefix='btmon-')
os.write(fd, btmon.stdout.read())
os.close(fd)
logger.info('Logs for btmon can be found at %s' % filepath)
raise SmartLockSetupError("Failed to find nearby phone.")
logger.info('Phone found! Starting pairing...')
if not app.PairPhone():
raise SmartLockSetupError("Failed to pair with phone.")
logger.info('Pairing success! Starting trial run...')
app.StartTrialRun()
logger.info('Unlocking for trial run...')
lock_screen = chromeos.GetAccountPickerScreen()
lock_screen.WaitForSmartLockState(
lock_screen.SmartLockState.AUTHENTICATED)
lock_screen.UnlockWithClick()
logger.info('Trial run success! Dismissing app...')
app.DismissApp()
def RunSetupTest(args):
logger.info('Starting test for %s at %s' % (
args.username, args.remote_address))
if args.app_path is not None:
logger.info('Replacing Smart Lock app with %s' % args.app_path)
chromeos = cros.ChromeOS(
args.remote_address, args.username, args.password, ssh_port=args.ssh_port)
with chromeos.Start(local_app_path=args.app_path):
logger.info('Chrome initialized')
# TODO(tengs): The access token is currently fetched from the Smart Lock
# app's background page. To be more robust, we should instead mint the
# access token ourselves.
if not CheckCryptAuthState(chromeos.cryptauth_access_token):
raise SmartLockSetupError('Failed to check CryptAuth state')
logger.info('Opening Smart Lock settings...')
settings = chromeos.GetSmartLockSettings()
assert(not settings.is_smart_lock_enabled)
logger.info('Starting Smart Lock setup flow...')
app = settings.StartSetupAndReturnApp()
_NavigateSetupDialog(chromeos, app)
def main():
logging.basicConfig()
logging.getLogger('proximity_auth').setLevel(logging.INFO)
args = ParseArgs()
RunSetupTest(args)
if __name__ == '__main__':
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment