blob: bb8ca6b5bee79035004c548c6d30ecf75d5c114a [file] [log] [blame]
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import annotations
import datetime as dt
from typing import TYPE_CHECKING, List, Optional
from crossbench.probes.probe import (Probe, ProbeConfigParser, ProbeContext,
ProbeMissingDataError)
from crossbench.probes.result_location import ResultLocation
from crossbench.probes.results import EmptyProbeResult, ProbeResult
if TYPE_CHECKING:
from crossbench.browsers.browser import Viewport
from crossbench.env import HostEnvironment
from crossbench.path import RemotePath
from crossbench.runner.groups import BrowsersRunGroup, RepetitionsRunGroup
from crossbench.runner.run import Run
class ScreenshotProbe(Probe):
"""
General-purpose Probe that collects screenshots.
"""
NAME = "screenshot"
RESULT_LOCATION = ResultLocation.BROWSER
IMAGE_FORMAT = "png"
@classmethod
def config_parser(cls) -> ProbeConfigParser:
parser = super().config_parser()
# TODO: support interval-based screenshots
return parser
def _pre_check_viewport_size(self, env: HostEnvironment) -> None:
for browser in env.runner.browsers:
viewport: Viewport = browser.viewport
if viewport.is_headless:
env.handle_warning(
f"Cannot take screenshots for headless browser: {browser}")
if viewport.x < 10 or viewport.y < 50:
env.handle_warning(
f"Viewport for '{browser}' might include toolbar: {viewport}")
def get_context(self, run: Run) -> ScreenshotProbeContext:
return ScreenshotProbeContext(self, run)
def merge_repetitions(self, group: RepetitionsRunGroup) -> ProbeResult:
# TODO: implement
return EmptyProbeResult()
def merge_browsers(self, group: BrowsersRunGroup) -> ProbeResult:
# TODO: implement
return EmptyProbeResult()
class ScreenshotProbeContext(ProbeContext[ScreenshotProbe]):
def __init__(self, probe: ScreenshotProbe, run: Run) -> None:
super().__init__(probe, run)
self._results: List[RemotePath] = []
def get_default_result_path(self) -> RemotePath:
screenshot_dir = super().get_default_result_path()
self.browser_platform.mkdir(screenshot_dir)
return screenshot_dir
def start(self) -> None:
self.screenshot("start")
def start_story_run(self) -> None:
self.screenshot("start_story")
def stop_story_run(self) -> None:
self.screenshot("stop_story")
def stop(self) -> None:
self.screenshot("stop")
def screenshot(self, label: Optional[str] = None) -> None:
# TODO: support screen coordinates
if not label:
label = str(dt.datetime.now().strftime("%Y-%m-%d_%H%M%S"))
path = self.result_path / f"{label}.{ScreenshotProbe.IMAGE_FORMAT}"
# TODO: use the browser's implementation first which might be more portable
self.browser_platform.screenshot(path)
self._results.append(path)
def teardown(self) -> ProbeResult:
if not self.browser_platform.is_dir(self.result_path):
raise ProbeMissingDataError(
f"No screen shot found at: {self.result_path}")
return self.browser_result(file=tuple(self._results))