xref: /aosp_15_r20/external/angle/build/fuchsia/test/browser_runner.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1*8975f5c5SAndroid Build Coastguard Worker# Copyright 2024 The Chromium Authors
2*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
3*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file.
4*8975f5c5SAndroid Build Coastguard Worker"""Executes a browser with devtools enabled on the target."""
5*8975f5c5SAndroid Build Coastguard Worker
6*8975f5c5SAndroid Build Coastguard Workerimport os
7*8975f5c5SAndroid Build Coastguard Workerimport re
8*8975f5c5SAndroid Build Coastguard Workerimport subprocess
9*8975f5c5SAndroid Build Coastguard Workerimport tempfile
10*8975f5c5SAndroid Build Coastguard Workerimport time
11*8975f5c5SAndroid Build Coastguard Workerfrom typing import List, Optional
12*8975f5c5SAndroid Build Coastguard Workerfrom urllib.parse import urlparse
13*8975f5c5SAndroid Build Coastguard Worker
14*8975f5c5SAndroid Build Coastguard Workerfrom common import run_continuous_ffx_command, ssh_run, REPO_ALIAS
15*8975f5c5SAndroid Build Coastguard Workerfrom ffx_integration import run_symbolizer
16*8975f5c5SAndroid Build Coastguard Worker
17*8975f5c5SAndroid Build Coastguard WorkerWEB_ENGINE_SHELL = 'web-engine-shell'
18*8975f5c5SAndroid Build Coastguard WorkerCAST_STREAMING_SHELL = 'cast-streaming-shell'
19*8975f5c5SAndroid Build Coastguard Worker
20*8975f5c5SAndroid Build Coastguard Worker
21*8975f5c5SAndroid Build Coastguard Workerclass BrowserRunner:
22*8975f5c5SAndroid Build Coastguard Worker    """Manages the browser process on the target."""
23*8975f5c5SAndroid Build Coastguard Worker
24*8975f5c5SAndroid Build Coastguard Worker    def __init__(self,
25*8975f5c5SAndroid Build Coastguard Worker                 browser_type: str,
26*8975f5c5SAndroid Build Coastguard Worker                 target_id: Optional[str] = None,
27*8975f5c5SAndroid Build Coastguard Worker                 output_dir: Optional[str] = None):
28*8975f5c5SAndroid Build Coastguard Worker        self._browser_type = browser_type
29*8975f5c5SAndroid Build Coastguard Worker        assert self._browser_type in [WEB_ENGINE_SHELL, CAST_STREAMING_SHELL]
30*8975f5c5SAndroid Build Coastguard Worker        self._target_id = target_id
31*8975f5c5SAndroid Build Coastguard Worker        self._output_dir = output_dir or os.environ['CHROMIUM_OUTPUT_DIR']
32*8975f5c5SAndroid Build Coastguard Worker        assert self._output_dir
33*8975f5c5SAndroid Build Coastguard Worker        self._browser_proc = None
34*8975f5c5SAndroid Build Coastguard Worker        self._symbolizer_proc = None
35*8975f5c5SAndroid Build Coastguard Worker        self._devtools_port = None
36*8975f5c5SAndroid Build Coastguard Worker        self._log_fs = None
37*8975f5c5SAndroid Build Coastguard Worker
38*8975f5c5SAndroid Build Coastguard Worker        output_root = os.path.join(self._output_dir, 'gen', 'fuchsia_web')
39*8975f5c5SAndroid Build Coastguard Worker        if self._browser_type == WEB_ENGINE_SHELL:
40*8975f5c5SAndroid Build Coastguard Worker            self._id_files = [
41*8975f5c5SAndroid Build Coastguard Worker                os.path.join(output_root, 'shell', 'web_engine_shell',
42*8975f5c5SAndroid Build Coastguard Worker                             'ids.txt'),
43*8975f5c5SAndroid Build Coastguard Worker                os.path.join(output_root, 'webengine', 'web_engine_with_webui',
44*8975f5c5SAndroid Build Coastguard Worker                             'ids.txt'),
45*8975f5c5SAndroid Build Coastguard Worker            ]
46*8975f5c5SAndroid Build Coastguard Worker        else:  # self._browser_type == CAST_STREAMING_SHELL:
47*8975f5c5SAndroid Build Coastguard Worker            self._id_files = [
48*8975f5c5SAndroid Build Coastguard Worker                os.path.join(output_root, 'shell', 'cast_streaming_shell',
49*8975f5c5SAndroid Build Coastguard Worker                             'ids.txt'),
50*8975f5c5SAndroid Build Coastguard Worker                os.path.join(output_root, 'webengine', 'web_engine',
51*8975f5c5SAndroid Build Coastguard Worker                             'ids.txt'),
52*8975f5c5SAndroid Build Coastguard Worker            ]
53*8975f5c5SAndroid Build Coastguard Worker
54*8975f5c5SAndroid Build Coastguard Worker    @property
55*8975f5c5SAndroid Build Coastguard Worker    def browser_type(self) -> str:
56*8975f5c5SAndroid Build Coastguard Worker        """Returns the type of the browser for the tests."""
57*8975f5c5SAndroid Build Coastguard Worker        return self._browser_type
58*8975f5c5SAndroid Build Coastguard Worker
59*8975f5c5SAndroid Build Coastguard Worker    @property
60*8975f5c5SAndroid Build Coastguard Worker    def devtools_port(self) -> int:
61*8975f5c5SAndroid Build Coastguard Worker        """Returns the randomly assigned devtools-port, shouldn't be called
62*8975f5c5SAndroid Build Coastguard Worker        before executing the start."""
63*8975f5c5SAndroid Build Coastguard Worker        assert self._devtools_port
64*8975f5c5SAndroid Build Coastguard Worker        return self._devtools_port
65*8975f5c5SAndroid Build Coastguard Worker
66*8975f5c5SAndroid Build Coastguard Worker    @property
67*8975f5c5SAndroid Build Coastguard Worker    def log_file(self) -> str:
68*8975f5c5SAndroid Build Coastguard Worker        """Returns the log file of the browser instance, shouldn't be called
69*8975f5c5SAndroid Build Coastguard Worker        before executing the start."""
70*8975f5c5SAndroid Build Coastguard Worker        assert self._log_fs
71*8975f5c5SAndroid Build Coastguard Worker        return self._log_fs.name
72*8975f5c5SAndroid Build Coastguard Worker
73*8975f5c5SAndroid Build Coastguard Worker    @property
74*8975f5c5SAndroid Build Coastguard Worker    def browser_pid(self) -> int:
75*8975f5c5SAndroid Build Coastguard Worker        """Returns the process id of the ffx instance which starts the browser
76*8975f5c5SAndroid Build Coastguard Worker        on the test device, shouldn't be called before executing the start."""
77*8975f5c5SAndroid Build Coastguard Worker        assert self._browser_proc
78*8975f5c5SAndroid Build Coastguard Worker        return self._browser_proc.pid
79*8975f5c5SAndroid Build Coastguard Worker
80*8975f5c5SAndroid Build Coastguard Worker    def _read_devtools_port(self):
81*8975f5c5SAndroid Build Coastguard Worker        search_regex = r'DevTools listening on (.+)'
82*8975f5c5SAndroid Build Coastguard Worker
83*8975f5c5SAndroid Build Coastguard Worker        # The ipaddress of the emulator or device is preferred over the address
84*8975f5c5SAndroid Build Coastguard Worker        # reported by the devtools, former one is usually more accurate.
85*8975f5c5SAndroid Build Coastguard Worker        def try_reading_port(log_file) -> int:
86*8975f5c5SAndroid Build Coastguard Worker            for line in log_file:
87*8975f5c5SAndroid Build Coastguard Worker                tokens = re.search(search_regex, line)
88*8975f5c5SAndroid Build Coastguard Worker                if tokens:
89*8975f5c5SAndroid Build Coastguard Worker                    url = urlparse(tokens.group(1))
90*8975f5c5SAndroid Build Coastguard Worker                    assert url.scheme == 'ws'
91*8975f5c5SAndroid Build Coastguard Worker                    assert url.port is not None
92*8975f5c5SAndroid Build Coastguard Worker                    return url.port
93*8975f5c5SAndroid Build Coastguard Worker            return None
94*8975f5c5SAndroid Build Coastguard Worker
95*8975f5c5SAndroid Build Coastguard Worker        with open(self.log_file, encoding='utf-8') as log_file:
96*8975f5c5SAndroid Build Coastguard Worker            start = time.time()
97*8975f5c5SAndroid Build Coastguard Worker            while time.time() - start < 180:
98*8975f5c5SAndroid Build Coastguard Worker                port = try_reading_port(log_file)
99*8975f5c5SAndroid Build Coastguard Worker                if port:
100*8975f5c5SAndroid Build Coastguard Worker                    return port
101*8975f5c5SAndroid Build Coastguard Worker                self._browser_proc.poll()
102*8975f5c5SAndroid Build Coastguard Worker                assert not self._browser_proc.returncode, 'Browser stopped.'
103*8975f5c5SAndroid Build Coastguard Worker                time.sleep(1)
104*8975f5c5SAndroid Build Coastguard Worker            assert False, 'Failed to wait for the devtools port.'
105*8975f5c5SAndroid Build Coastguard Worker
106*8975f5c5SAndroid Build Coastguard Worker    def start(self, extra_args: List[str] = None) -> None:
107*8975f5c5SAndroid Build Coastguard Worker        """Starts the selected browser, |extra_args| are attached to the command
108*8975f5c5SAndroid Build Coastguard Worker        line."""
109*8975f5c5SAndroid Build Coastguard Worker        browser_cmd = ['test', 'run']
110*8975f5c5SAndroid Build Coastguard Worker        if self.browser_type == WEB_ENGINE_SHELL:
111*8975f5c5SAndroid Build Coastguard Worker            browser_cmd.extend([
112*8975f5c5SAndroid Build Coastguard Worker                f'fuchsia-pkg://{REPO_ALIAS}/web_engine_shell#meta/'
113*8975f5c5SAndroid Build Coastguard Worker                f'web_engine_shell.cm',
114*8975f5c5SAndroid Build Coastguard Worker                '--',
115*8975f5c5SAndroid Build Coastguard Worker                '--web-engine-package-name=web_engine_with_webui',
116*8975f5c5SAndroid Build Coastguard Worker                '--remote-debugging-port=0',
117*8975f5c5SAndroid Build Coastguard Worker                '--enable-web-instance-tmp',
118*8975f5c5SAndroid Build Coastguard Worker                '--with-webui',
119*8975f5c5SAndroid Build Coastguard Worker                'about:blank',
120*8975f5c5SAndroid Build Coastguard Worker            ])
121*8975f5c5SAndroid Build Coastguard Worker        else:  # if self.browser_type == CAST_STREAMING_SHELL:
122*8975f5c5SAndroid Build Coastguard Worker            browser_cmd.extend([
123*8975f5c5SAndroid Build Coastguard Worker                f'fuchsia-pkg://{REPO_ALIAS}/cast_streaming_shell#meta/'
124*8975f5c5SAndroid Build Coastguard Worker                f'cast_streaming_shell.cm',
125*8975f5c5SAndroid Build Coastguard Worker                '--',
126*8975f5c5SAndroid Build Coastguard Worker                '--remote-debugging-port=0',
127*8975f5c5SAndroid Build Coastguard Worker            ])
128*8975f5c5SAndroid Build Coastguard Worker        # Use flags used on WebEngine in production devices.
129*8975f5c5SAndroid Build Coastguard Worker        browser_cmd.extend([
130*8975f5c5SAndroid Build Coastguard Worker            '--',
131*8975f5c5SAndroid Build Coastguard Worker            '--enable-low-end-device-mode',
132*8975f5c5SAndroid Build Coastguard Worker            '--force-gpu-mem-available-mb=64',
133*8975f5c5SAndroid Build Coastguard Worker            '--force-gpu-mem-discardable-limit-mb=32',
134*8975f5c5SAndroid Build Coastguard Worker            '--force-max-texture-size=2048',
135*8975f5c5SAndroid Build Coastguard Worker            '--gpu-rasterization-msaa-sample-count=0',
136*8975f5c5SAndroid Build Coastguard Worker            '--min-height-for-gpu-raster-tile=128',
137*8975f5c5SAndroid Build Coastguard Worker            '--webgl-msaa-sample-count=0',
138*8975f5c5SAndroid Build Coastguard Worker            '--max-decoded-image-size-mb=10',
139*8975f5c5SAndroid Build Coastguard Worker        ])
140*8975f5c5SAndroid Build Coastguard Worker        if extra_args:
141*8975f5c5SAndroid Build Coastguard Worker            browser_cmd.extend(extra_args)
142*8975f5c5SAndroid Build Coastguard Worker        self._browser_proc = run_continuous_ffx_command(
143*8975f5c5SAndroid Build Coastguard Worker            cmd=browser_cmd,
144*8975f5c5SAndroid Build Coastguard Worker            stdout=subprocess.PIPE,
145*8975f5c5SAndroid Build Coastguard Worker            stderr=subprocess.STDOUT,
146*8975f5c5SAndroid Build Coastguard Worker            target_id=self._target_id)
147*8975f5c5SAndroid Build Coastguard Worker        # The stdout will be forwarded to the symbolizer, then to the _log_fs.
148*8975f5c5SAndroid Build Coastguard Worker        self._log_fs = tempfile.NamedTemporaryFile()
149*8975f5c5SAndroid Build Coastguard Worker        self._symbolizer_proc = run_symbolizer(self._id_files,
150*8975f5c5SAndroid Build Coastguard Worker                                               self._browser_proc.stdout,
151*8975f5c5SAndroid Build Coastguard Worker                                               self._log_fs)
152*8975f5c5SAndroid Build Coastguard Worker        self._devtools_port = self._read_devtools_port()
153*8975f5c5SAndroid Build Coastguard Worker
154*8975f5c5SAndroid Build Coastguard Worker    def stop_browser(self) -> None:
155*8975f5c5SAndroid Build Coastguard Worker        """Stops the browser on the target, as well as the local symbolizer, the
156*8975f5c5SAndroid Build Coastguard Worker        _log_fs is preserved. Calling this function for a second time won't have
157*8975f5c5SAndroid Build Coastguard Worker        any effect."""
158*8975f5c5SAndroid Build Coastguard Worker        if not self.is_browser_running():
159*8975f5c5SAndroid Build Coastguard Worker            return
160*8975f5c5SAndroid Build Coastguard Worker        self._browser_proc.kill()
161*8975f5c5SAndroid Build Coastguard Worker        self._browser_proc = None
162*8975f5c5SAndroid Build Coastguard Worker        self._symbolizer_proc.kill()
163*8975f5c5SAndroid Build Coastguard Worker        self._symbolizer_proc = None
164*8975f5c5SAndroid Build Coastguard Worker        self._devtools_port = None
165*8975f5c5SAndroid Build Coastguard Worker        # The process may be stopped already, ignoring the no process found
166*8975f5c5SAndroid Build Coastguard Worker        # error.
167*8975f5c5SAndroid Build Coastguard Worker        ssh_run(['killall', 'web_instance.cmx'], self._target_id, check=False)
168*8975f5c5SAndroid Build Coastguard Worker
169*8975f5c5SAndroid Build Coastguard Worker    def is_browser_running(self) -> bool:
170*8975f5c5SAndroid Build Coastguard Worker        """Checks if the browser is still running."""
171*8975f5c5SAndroid Build Coastguard Worker        if self._browser_proc:
172*8975f5c5SAndroid Build Coastguard Worker            assert self._symbolizer_proc
173*8975f5c5SAndroid Build Coastguard Worker            assert self._devtools_port
174*8975f5c5SAndroid Build Coastguard Worker            return True
175*8975f5c5SAndroid Build Coastguard Worker        assert not self._symbolizer_proc
176*8975f5c5SAndroid Build Coastguard Worker        assert not self._devtools_port
177*8975f5c5SAndroid Build Coastguard Worker        return False
178*8975f5c5SAndroid Build Coastguard Worker
179*8975f5c5SAndroid Build Coastguard Worker    def close(self) -> None:
180*8975f5c5SAndroid Build Coastguard Worker        """Cleans up everything."""
181*8975f5c5SAndroid Build Coastguard Worker        self.stop_browser()
182*8975f5c5SAndroid Build Coastguard Worker        self._log_fs.close()
183*8975f5c5SAndroid Build Coastguard Worker        self._log_fs = None
184