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 |