Skip to content

Commit 6b05e1d

Browse files
[wptrunner] Collect per-test trace events from Chromium (#54366)
When `wpt run --trace-categories` is specified, the trace events [0] are retrieved from an internal ChromeDriver buffer and logged as an `extra` test result field. The trace will be consumed by an event listener on the Chromium side (https://crrev.com/c/6820247) and can be viewed in https://ui.perfetto.dev/. Partially fulfills crbug.com/434017035 [0]: https://developer.chrome.com/docs/chromedriver/logging/performance-log
1 parent 5c03a36 commit 6b05e1d

File tree

3 files changed

+82
-4
lines changed

3 files changed

+82
-4
lines changed

tools/wptrunner/wptrunner/browsers/chrome.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ def executor_kwargs(logger, test_type, test_environment, run_info_data, subsuite
8686
# fail to create a session if they don't recognize this capability.
8787
chrome_options["quitGracefully"] = True
8888

89+
if trace_categories := kwargs.get("trace_categories"):
90+
executor_kwargs["enable_tracing"] = True
91+
capabilities["goog:loggingPrefs"] = {
92+
"performance": "INFO",
93+
}
94+
chrome_options["perfLoggingPrefs"] = {
95+
"traceCategories": trace_categories,
96+
}
97+
8998
# Here we set a few Chrome flags that are always passed.
9099
# ChromeDriver's "acceptInsecureCerts" capability only controls the current
91100
# browsing context, whereas the CLI flag works for workers, too.

tools/wptrunner/wptrunner/executors/executorchrome.py

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,46 @@ def execute_cdp_command(self, command, params=None):
185185
body=body)
186186

187187

188+
class ChromeDriverTracingProtocolPart(ProtocolPart):
189+
name = "tracing"
190+
191+
def setup(self):
192+
self.webdriver = self.parent.webdriver
193+
194+
def get_trace(self):
195+
"""Retrieve trace events accumulated by ChromeDriver.
196+
197+
This also clears ChromeDriver's internal buffer of logged events.
198+
199+
Returns:
200+
JSON in the trace array format [0].
201+
202+
[0]: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview?tab=t.0#heading=h.f2f0yd51wi15
203+
"""
204+
# Not a standard WebDriver method.
205+
perf_data = self.webdriver.send_session_command("POST", "se/log", {
206+
"type": "performance",
207+
})
208+
events = []
209+
for entry in perf_data:
210+
# Unwrap the inner trace event and discard the unnecessary
211+
# ChromeDriver-added fields.
212+
data_collected_event = json.loads(entry["message"]).get("message", {})
213+
if data_collected_event.get("method") != "Tracing.dataCollected":
214+
continue
215+
if trace_event := data_collected_event.get("params"):
216+
events.append(trace_event)
217+
return events
218+
219+
188220
class ChromeDriverProtocol(WebDriverProtocol):
189221
implements = [
190222
ChromeDriverBaseProtocolPart,
191223
ChromeDriverDevToolsProtocolPart,
192224
ChromeDriverFedCMProtocolPart,
193225
ChromeDriverTestDriverProtocolPart,
194226
ChromeDriverTestharnessProtocolPart,
227+
ChromeDriverTracingProtocolPart,
195228
]
196229
for base_part in WebDriverProtocol.implements:
197230
if base_part.name not in {part.name for part in implements}:
@@ -213,6 +246,7 @@ class ChromeDriverBidiProtocol(WebDriverBidiProtocol):
213246
ChromeDriverDevToolsProtocolPart,
214247
ChromeDriverFedCMProtocolPart,
215248
ChromeDriverTestharnessProtocolPart,
249+
ChromeDriverTracingProtocolPart,
216250
]
217251
for base_part in WebDriverBidiProtocol.implements:
218252
if base_part.name not in {part.name for part in implements}:
@@ -258,24 +292,39 @@ def convert_result(self, test, result, **kwargs):
258292
class ChromeDriverCrashTestExecutor(WebDriverCrashtestExecutor):
259293
protocol_cls = ChromeDriverProtocol
260294

261-
def __init__(self, *args, sanitizer_enabled=False, **kwargs):
295+
def __init__(self, *args, sanitizer_enabled=False, enable_tracing=False, **kwargs):
262296
super().__init__(*args, **kwargs)
263297
self.sanitizer_enabled = sanitizer_enabled
298+
self.enable_tracing = enable_tracing
299+
300+
def do_test(self, test):
301+
file_result, subtest_results = super().do_test(test)
302+
if self.enable_tracing:
303+
file_result.extra["trace"] = self.protocol.tracing.get_trace()
304+
return file_result, subtest_results
264305

265306

266307
@_evaluate_sanitized_result
267308
class ChromeDriverRefTestExecutor(WebDriverRefTestExecutor):
268309
protocol_cls = ChromeDriverProtocol
269310

270-
def __init__(self, *args, sanitizer_enabled=False, **kwargs):
311+
def __init__(self, *args, sanitizer_enabled=False, enable_tracing=False, **kwargs):
271312
super().__init__(*args, **kwargs)
272313
self.sanitizer_enabled = sanitizer_enabled
314+
self.enable_tracing = enable_tracing
315+
316+
def do_test(self, test):
317+
file_result, subtest_results = super().do_test(test)
318+
if self.enable_tracing:
319+
file_result.extra["trace"] = self.protocol.tracing.get_trace()
320+
return file_result, subtest_results
273321

274322

275323
@_evaluate_sanitized_result
276324
class ChromeDriverTestharnessExecutor(WebDriverTestharnessExecutor):
277325

278-
def __init__(self, *args, sanitizer_enabled=False, reuse_window=False, **kwargs):
326+
def __init__(self, *args, sanitizer_enabled=False, enable_tracing=False, reuse_window=False,
327+
**kwargs):
279328
require_webdriver_bidi = kwargs.get("browser_settings", {}).get(
280329
"require_webdriver_bidi", None)
281330
if require_webdriver_bidi:
@@ -285,6 +334,7 @@ def __init__(self, *args, sanitizer_enabled=False, reuse_window=False, **kwargs)
285334

286335
super().__init__(*args, **kwargs)
287336
self.sanitizer_enabled = sanitizer_enabled
337+
self.enable_tracing = enable_tracing
288338
self.reuse_window = reuse_window
289339

290340
def create_test_window(self, protocol):
@@ -312,11 +362,24 @@ def create_test_window(self, protocol):
312362
self.protocol.testharness.persistent_test_window = test_window
313363
return test_window
314364

365+
def do_test(self, test):
366+
file_result, subtest_results = super().do_test(test)
367+
if self.enable_tracing:
368+
file_result.extra["trace"] = self.protocol.tracing.get_trace()
369+
return file_result, subtest_results
370+
315371

316372
@_evaluate_sanitized_result
317373
class ChromeDriverPrintRefTestExecutor(WebDriverPrintRefTestExecutor):
318374
protocol_cls = ChromeDriverProtocol
319375

320-
def __init__(self, *args, sanitizer_enabled=False, **kwargs):
376+
def __init__(self, *args, sanitizer_enabled=False, enable_tracing=False, **kwargs):
321377
super().__init__(*args, **kwargs)
322378
self.sanitizer_enabled = sanitizer_enabled
379+
self.enable_tracing = enable_tracing
380+
381+
def do_test(self, test):
382+
file_result, subtest_results = super().do_test(test)
383+
if self.enable_tracing:
384+
file_result.extra["trace"] = self.protocol.tracing.get_trace()
385+
return file_result, subtest_results

tools/wptrunner/wptrunner/wptcommandline.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,12 @@ def create_parser(product_choices=None):
381381
help=("Reuse a window across `testharness.js` tests where possible, "
382382
"which can speed up testing. Also useful for ensuring that the "
383383
"renderer process has a stable PID for a debugger to attach to."))
384+
chrome_group.add_argument(
385+
"--trace-categories",
386+
metavar="CATEGORIES",
387+
nargs="?",
388+
const="blink,blink.bindings",
389+
help="Record traces under the given categories for each test.")
384390

385391
sauce_group = parser.add_argument_group("Sauce Labs-specific")
386392
sauce_group.add_argument("--sauce-browser", help="Sauce Labs browser name")

0 commit comments

Comments
 (0)