Update configs. IGNORE BROKEN CHANGESETS CLOSED TREE NO BUG a=release ba=release
[gecko.git] / testing / webcompat / mach_commands.py
blobd67caadaecd4f74e5566fde9c344c2e74c1f2bce
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 import argparse
6 import io
7 import os
8 import platform
9 import sys
11 from mach.decorators import Command
12 from mozbuild.base import MozbuildObject
14 here = os.path.abspath(os.path.dirname(__file__))
16 GVE = "org.mozilla.geckoview_example"
19 def get(url):
20 import requests
22 resp = requests.get(url)
23 resp.raise_for_status()
24 return resp
27 def untar(fileobj, dest):
28 import tarfile
30 with tarfile.open(fileobj=fileobj, mode="r") as tar_data:
31 tar_data.extractall(path=dest)
34 def unzip(fileobj, dest):
35 import zipfile
37 with zipfile.ZipFile(fileobj) as zip_data:
38 zip_data.extractall(path=dest)
41 def create_parser_interventions():
42 from mozlog import commandline
44 parser = argparse.ArgumentParser()
45 parser.add_argument("--webdriver-binary", help="Path to webdriver binary")
46 parser.add_argument(
47 "--webdriver-port",
48 action="store",
49 default="4444",
50 help="Port on which to run WebDriver",
52 parser.add_argument(
53 "--webdriver-ws-port",
54 action="store",
55 default="9222",
56 help="Port on which to run WebDriver BiDi websocket",
58 parser.add_argument("--bug", help="Bug to run tests for")
59 parser.add_argument(
60 "--do2fa",
61 action="store_true",
62 default=False,
63 help="Do two-factor auth live in supporting tests",
65 parser.add_argument(
66 "--config", help="Path to JSON file containing logins and other settings"
68 parser.add_argument(
69 "--debug", action="store_true", default=False, help="Debug failing tests"
71 parser.add_argument(
72 "--headless",
73 action="store_true",
74 default=False,
75 help="Run firefox in headless mode",
77 parser.add_argument(
78 "--interventions",
79 action="store",
80 default="both",
81 choices=["enabled", "disabled", "both", "none"],
82 help="Enable webcompat interventions",
84 parser.add_argument(
85 "--shims",
86 action="store",
87 default="none",
88 choices=["enabled", "disabled", "both", "none"],
89 help="Enable SmartBlock shims",
91 parser.add_argument(
92 "--platform",
93 action="store",
94 choices=["android", "desktop"],
95 help="Platform to target",
98 desktop_group = parser.add_argument_group("Desktop-specific arguments")
99 desktop_group.add_argument("--binary", help="Path to browser binary")
101 android_group = parser.add_argument_group("Android-specific arguments")
102 android_group.add_argument(
103 "--device-serial",
104 action="store",
105 help="Running Android instances to connect to, if not emulator-5554",
107 android_group.add_argument(
108 "--package-name",
109 action="store",
110 default=GVE,
111 help="Android package name to use",
114 commandline.add_logging_group(parser)
115 return parser
118 class InterventionTest(MozbuildObject):
119 def set_default_kwargs(self, logger, command_context, kwargs):
120 platform = kwargs["platform"]
121 binary = kwargs["binary"]
122 device_serial = kwargs["device_serial"]
123 is_gve_build = command_context.substs.get("MOZ_APP_NAME") == "fennec"
125 if platform == "android" or (
126 platform is None and binary is None and (device_serial or is_gve_build)
128 kwargs["platform"] = "android"
129 else:
130 kwargs["platform"] = "desktop"
132 if kwargs["platform"] == "desktop" and kwargs["binary"] is None:
133 kwargs["binary"] = self.get_binary_path()
135 if kwargs["webdriver_binary"] is None:
136 webdriver_binary = self.get_binary_path(
137 "geckodriver", validate_exists=False
140 if not os.path.exists(webdriver_binary):
141 webdriver_binary = self.install_geckodriver(
142 logger, dest=os.path.dirname(webdriver_binary)
145 if not os.path.exists(webdriver_binary):
146 logger.error("Can't find geckodriver")
147 sys.exit(1)
148 kwargs["webdriver_binary"] = webdriver_binary
150 def platform_string_geckodriver(self):
151 uname = platform.uname()
152 platform_name = {"Linux": "linux", "Windows": "win", "Darwin": "macos"}.get(
153 uname[0]
156 if platform_name in ("linux", "win"):
157 bits = "64" if uname[4] == "x86_64" else "32"
158 elif platform_name == "macos":
159 bits = ""
160 else:
161 raise ValueError(f"No precompiled geckodriver for platform {uname}")
163 return f"{platform_name}{bits}"
165 def install_geckodriver(self, logger, dest):
166 """Install latest Geckodriver."""
167 if dest is None:
168 dest = os.path.join(self.distdir, "dist", "bin")
170 is_windows = platform.uname()[0] == "Windows"
172 release = get(
173 "https://api.github.com/repos/mozilla/geckodriver/releases/latest"
174 ).json()
175 ext = "zip" if is_windows else "tar.gz"
176 platform_name = self.platform_string_geckodriver()
177 name_suffix = f"-{platform_name}.{ext}"
178 for item in release["assets"]:
179 if item["name"].endswith(name_suffix):
180 url = item["browser_download_url"]
181 break
182 else:
183 raise ValueError(f"Failed to find geckodriver for platform {platform_name}")
185 logger.info(f"Installing geckodriver from {url}")
187 data = io.BytesIO(get(url).content)
188 data.seek(0)
189 decompress = unzip if ext == "zip" else untar
190 decompress(data, dest=dest)
192 exe_ext = ".exe" if is_windows else ""
193 path = os.path.join(dest, f"geckodriver{exe_ext}")
195 return path
197 def setup_device(self, command_context, kwargs):
198 if kwargs["platform"] != "android":
199 return
201 app = kwargs["package_name"]
202 device_serial = kwargs["device_serial"]
204 if not device_serial:
205 from mozrunner.devices.android_device import (
206 InstallIntent,
207 verify_android_device,
210 verify_android_device(
211 command_context, app=app, network=True, install=InstallIntent.YES
214 kwargs["device_serial"] = os.environ.get("DEVICE_SERIAL")
216 # GVE does not have the webcompat addon by default. Add it.
217 if app == GVE:
218 kwargs["addon"] = "/data/local/tmp/webcompat.xpi"
219 push_to_device(
220 command_context.substs["ADB"],
221 device_serial,
222 webcompat_addon(command_context),
223 kwargs["addon"],
226 def run(self, command_context, **kwargs):
227 import mozlog
228 import runner
230 mozlog.commandline.setup_logging(
231 "test-interventions", kwargs, {"mach": sys.stdout}
233 logger = mozlog.get_default_logger("test-interventions")
234 status_handler = mozlog.handlers.StatusHandler()
235 logger.add_handler(status_handler)
237 self.set_default_kwargs(logger, command_context, kwargs)
239 self.setup_device(command_context, kwargs)
241 if kwargs["interventions"] != "none":
242 interventions = (
243 ["enabled", "disabled"]
244 if kwargs["interventions"] == "both"
245 else [kwargs["interventions"]]
248 for interventions_setting in interventions:
249 runner.run(
250 logger,
251 os.path.join(here, "interventions"),
252 kwargs["webdriver_binary"],
253 kwargs["webdriver_port"],
254 kwargs["webdriver_ws_port"],
255 browser_binary=kwargs.get("binary"),
256 device_serial=kwargs.get("device_serial"),
257 package_name=kwargs.get("package_name"),
258 addon=kwargs.get("addon"),
259 bug=kwargs["bug"],
260 debug=kwargs["debug"],
261 interventions=interventions_setting,
262 config=kwargs["config"],
263 headless=kwargs["headless"],
264 do2fa=kwargs["do2fa"],
267 if kwargs["shims"] != "none":
268 shims = (
269 ["enabled", "disabled"]
270 if kwargs["shims"] == "both"
271 else [kwargs["shims"]]
274 for shims_setting in shims:
275 runner.run(
276 logger,
277 os.path.join(here, "shims"),
278 kwargs["webdriver_binary"],
279 kwargs["webdriver_port"],
280 kwargs["webdriver_ws_port"],
281 browser_binary=kwargs.get("binary"),
282 device_serial=kwargs.get("device_serial"),
283 package_name=kwargs.get("package_name"),
284 addon=kwargs.get("addon"),
285 bug=kwargs["bug"],
286 debug=kwargs["debug"],
287 shims=shims_setting,
288 config=kwargs["config"],
289 headless=kwargs["headless"],
290 do2fa=kwargs["do2fa"],
293 summary = status_handler.summarize()
294 passed = (
295 summary.unexpected_statuses == 0
296 and summary.log_level_counts.get("ERROR", 0) == 0
297 and summary.log_level_counts.get("CRITICAL", 0) == 0
299 return passed
302 def webcompat_addon(command_context):
303 import shutil
305 src = os.path.join(command_context.topsrcdir, "browser", "extensions", "webcompat")
306 dst = os.path.join(
307 command_context.virtualenv_manager.virtualenv_root, "webcompat.xpi"
309 shutil.make_archive(dst, "zip", src)
310 shutil.move(f"{dst}.zip", dst)
311 return dst
314 def push_to_device(adb_path, device_serial, local_path, remote_path):
315 from mozdevice import ADBDeviceFactory
317 device = ADBDeviceFactory(adb=adb_path, device=device_serial)
318 device.push(local_path, remote_path)
319 device.chmod(remote_path)
322 @Command(
323 "test-interventions",
324 category="testing",
325 description="Test the webcompat interventions",
326 parser=create_parser_interventions,
327 virtualenv_name="webcompat",
329 def test_interventions(command_context, **params):
330 here = os.path.abspath(os.path.dirname(__file__))
331 command_context.virtualenv_manager.activate()
332 command_context.virtualenv_manager.install_pip_requirements(
333 os.path.join(here, "requirements.txt"),
334 require_hashes=False,
337 intervention_test = command_context._spawn(InterventionTest)
338 return 0 if intervention_test.run(command_context, **params) else 1