| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """A wrapper around adb commands called by chromedriver. | 6 """A wrapper around adb commands called by chromedriver. |
| 7 | 7 |
| 8 Prerequisites: | 8 Preconditions: |
| 9 - A single device is attached. | 9 - A single device is attached. |
| 10 - adb is in PATH. | 10 - adb is in PATH. |
| 11 | 11 |
| 12 This script should write everything (including stacktraces) to stdout. | 12 This script should write everything (including stacktraces) to stdout. |
| 13 """ | 13 """ |
| 14 | 14 |
| 15 import collections |
| 15 import optparse | 16 import optparse |
| 16 import subprocess | 17 import subprocess |
| 17 import sys | 18 import sys |
| 18 import traceback | 19 import traceback |
| 19 | 20 |
| 20 | 21 |
| 21 # {<package name>: (<activity name>, <device abstract socket>)} | 22 PackageInfo = collections.namedtuple('PackageInfo', ['activity', 'socket']) |
| 22 PACKAGE_INFO = {'org.chromium.chrome.testshell': | 23 CHROME_INFO = PackageInfo('Main', 'chrome_devtools_remote') |
| 23 ('org.chromium.chrome.testshell.ChromiumTestShellActivity', | 24 PACKAGE_INFO = { |
| 24 'chromium_testshell_devtools_remote'), | 25 'org.chromium.chrome.testshell': |
| 25 'com.google.android.apps.chrome': | 26 PackageInfo('ChromiumTestShellActivity', |
| 26 ('com.google.android.apps.chrome.Main', | 27 'chromium_testshell_devtools_remote'), |
| 27 'chrome_devtools_remote')} | 28 'com.google.android.apps.chrome': CHROME_INFO, |
| 29 'com.chrome.dev': CHROME_INFO, |
| 30 'com.chrome.beta': CHROME_INFO, |
| 31 'com.android.chrome': CHROME_INFO, |
| 32 } |
| 28 | 33 |
| 29 | 34 |
| 30 class AdbError(Exception): | 35 class AdbError(Exception): |
| 31 def __init__(self, message, output, cmd): | 36 def __init__(self, message, output, cmd): |
| 32 self.message = message | 37 self.message = message |
| 33 self.output = output | 38 self.output = output |
| 34 self.cmd = cmd | 39 self.cmd = cmd |
| 35 | 40 |
| 36 def __str__(self): | 41 def __str__(self): |
| 37 return ('%s\nCommand: "%s"\nOutput: "%s"' % | 42 return ('%s\nCommand: "%s"\nOutput: "%s"' % |
| 38 (self.message, self.cmd, self.output)) | 43 (self.message, self.cmd, self.output)) |
| 39 | 44 |
| 40 | 45 |
| 41 def RunAdbCommand(args, cwd=None): | 46 def RunAdbCommand(args): |
| 42 """Executes an ADB command and returns its output. | 47 """Executes an ADB command and returns its output. |
| 43 | 48 |
| 44 Args: | 49 Args: |
| 45 args: A sequence of program arguments supplied to adb. | 50 args: A sequence of program arguments supplied to adb. |
| 46 cwd: If not None, the subprocess's current directory will be changed to | |
| 47 |cwd| before it's executed. | |
| 48 | 51 |
| 49 Returns: | 52 Returns: |
| 50 A tuple: (stdout + stderr, command string) | 53 output of the command (stdout + stderr). |
| 51 | 54 |
| 52 Raises: | 55 Raises: |
| 53 AdbError: if exit code is non-zero. | 56 AdbError: if exit code is non-zero. |
| 54 """ | 57 """ |
| 55 args = ['adb', '-d'] + args | 58 args = ['adb', '-d'] + args |
| 56 try: | 59 try: |
| 57 p = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, | 60 p = subprocess.Popen(args, stdout=subprocess.PIPE, |
| 58 stderr=subprocess.STDOUT) | 61 stderr=subprocess.STDOUT) |
| 59 stdout, _ = p.communicate() | 62 out, _ = p.communicate() |
| 60 args = ' '.join(args) | |
| 61 if p.returncode: | 63 if p.returncode: |
| 62 raise AdbError('Command failed.', stdout, args) | 64 raise AdbError('Command failed.', out, args) |
| 63 return stdout, args | 65 return out |
| 64 except OSError as e: | 66 except OSError as e: |
| 65 print 'Make sure adb command is in PATH.' | 67 print 'Make sure adb command is in PATH.' |
| 66 raise e | 68 raise e |
| 67 | 69 |
| 68 | 70 |
| 69 def SetChromeFlags(): | 71 def SetChromeFlags(): |
| 70 """Sets the command line flags file on device. | 72 """Sets the command line flags file on device. |
| 71 | 73 |
| 72 Raises: | 74 Raises: |
| 73 AdbError: If failed to write the flags file to device. | 75 AdbError: If failed to write the flags file to device. |
| 74 """ | 76 """ |
| 75 out, cmd = RunAdbCommand([ | 77 cmd = [ |
| 76 'shell', | 78 'shell', |
| 77 'echo chrome --disable-fre --metrics-recording-only ' | 79 'echo chrome --disable-fre --metrics-recording-only ' |
| 78 '--enable-remote-debugging > /data/local/chrome-command-line' | 80 '--enable-remote-debugging > /data/local/chrome-command-line;' |
| 79 ]) | 81 'echo $?' |
| 80 if out.strip(): | 82 ] |
| 83 out = RunAdbCommand(cmd).strip() |
| 84 if out != '0': |
| 81 raise AdbError('Failed to set the command line flags.', out, cmd) | 85 raise AdbError('Failed to set the command line flags.', out, cmd) |
| 82 | 86 |
| 83 | 87 |
| 84 def ClearAppData(package): | 88 def ClearAppData(package): |
| 85 """Clears the app data. | 89 """Clears the app data. |
| 86 | 90 |
| 87 Args: | 91 Args: |
| 88 package: Application package name. | 92 package: Application package name. |
| 89 | 93 |
| 90 Raises: | 94 Raises: |
| 91 AdbError: if any step fails. | 95 AdbError: if any step fails. |
| 92 """ | 96 """ |
| 93 out, cmd = RunAdbCommand(['shell', 'pm clear %s' % package]) | 97 cmd = ['shell', 'pm clear %s' % package] |
| 94 # am/pm package do not return valid exit codes. | 98 # am/pm package do not return valid exit codes. |
| 99 out = RunAdbCommand(cmd) |
| 95 if 'Success' not in out: | 100 if 'Success' not in out: |
| 96 raise AdbError('Failed to clear the profile.', out, cmd) | 101 raise AdbError('Failed to clear the profile.', out, cmd) |
| 97 | 102 |
| 98 | 103 |
| 99 def LaunchApp(package): | 104 def StartActivity(package): |
| 100 """Launches the application. | 105 """Start the activity in the package. |
| 101 | 106 |
| 102 Args: | 107 Args: |
| 103 package: Application package name. | 108 package: Application package name. |
| 104 | 109 |
| 105 Raises: | 110 Raises: |
| 106 AdbError: if any step fails. | 111 AdbError: if any step fails. |
| 107 """ | 112 """ |
| 108 out, cmd = RunAdbCommand([ | 113 cmd = [ |
| 109 'shell', | 114 'shell', |
| 110 'am start -a android.intent.action.VIEW -S -W -n %s/%s ' | 115 'am start -a android.intent.action.VIEW -S -W -n %s/.%s ' |
| 111 '-d "data:text/html;charset=utf-8,"' % | 116 '-d "data:text/html;charset=utf-8,"' % |
| 112 (package, PACKAGE_INFO[package][0])]) | 117 (package, PACKAGE_INFO[package].activity)] |
| 118 out = RunAdbCommand(cmd) |
| 113 if 'Complete' not in out: | 119 if 'Complete' not in out: |
| 114 raise AdbError('Failed to start the app. %s', out, cmd) | 120 raise AdbError('Failed to start the activity. %s', out, cmd) |
| 115 | 121 |
| 116 | 122 |
| 117 def Forward(package, host_port): | 123 def Forward(package, host_port): |
| 118 """Forward host socket to devtools socket on the device. | 124 """Forward host socket to devtools socket on the device. |
| 119 | 125 |
| 120 Args: | 126 Args: |
| 121 package: Application package name. | 127 package: Application package name. |
| 122 host_port: Port on host to forward. | 128 host_port: Port on host to forward. |
| 123 | 129 |
| 124 Raises: | 130 Raises: |
| 125 AdbError: if command fails. | 131 AdbError: if command fails. |
| 126 """ | 132 """ |
| 127 RunAdbCommand(['forward', 'tcp:%d' % host_port, 'localabstract:%s' % | 133 cmd = ['forward', 'tcp:%d' % host_port, |
| 128 PACKAGE_INFO[package][1]]) | 134 'localabstract:%s' % PACKAGE_INFO[package].socket] |
| 135 RunAdbCommand(cmd) |
| 129 | 136 |
| 130 | 137 |
| 131 if __name__ == '__main__': | 138 if __name__ == '__main__': |
| 132 try: | 139 try: |
| 133 parser = optparse.OptionParser() | 140 parser = optparse.OptionParser() |
| 134 parser.add_option( | 141 parser.add_option( |
| 135 '', '--package', help='Application package name.') | 142 '', '--package', help='Application package name.') |
| 136 parser.add_option( | 143 parser.add_option( |
| 137 '', '--launch', action='store_true', | 144 '', '--launch', action='store_true', |
| 138 help='Launch the app with a fresh profile and forward devtools port.') | 145 help='Launch the app with a fresh profile and forward devtools port.') |
| 139 parser.add_option( | 146 parser.add_option( |
| 140 '', '--port', type='int', default=33081, | 147 '', '--port', type='int', default=33081, |
| 141 help='Host port to forward for launch operation [default: %default].') | 148 help='Host port to forward for launch operation [default: %default].') |
| 142 options, _ = parser.parse_args() | 149 options, _ = parser.parse_args() |
| 143 | 150 |
| 144 if not options.package: | 151 if not options.package: |
| 145 raise Exception('No package specified.') | 152 raise Exception('No package specified.') |
| 146 | 153 |
| 147 if options.package not in PACKAGE_INFO: | 154 if options.package not in PACKAGE_INFO: |
| 148 raise Exception('Unkown package provided. Supported packages are:\n %s' % | 155 raise Exception('Unknown package provided. Supported packages are:\n %s' % |
| 149 PACKAGE_INFO.keys()) | 156 PACKAGE_INFO.keys()) |
| 150 | 157 |
| 151 if options.launch: | 158 if options.launch: |
| 152 SetChromeFlags() | 159 SetChromeFlags() |
| 153 ClearAppData(options.package) | 160 ClearAppData(options.package) |
| 154 LaunchApp(options.package) | 161 StartActivity(options.package) |
| 155 Forward(options.package, options.port) | 162 Forward(options.package, options.port) |
| 156 else: | 163 else: |
| 157 raise Exception('No options provided.') | 164 raise Exception('No options provided.') |
| 158 except: | 165 except: |
| 159 traceback.print_exc(file=sys.stdout) | 166 traceback.print_exc(file=sys.stdout) |
| 160 sys.exit(1) | 167 sys.exit(1) |
| 161 | 168 |
| OLD | NEW |