"""Optional Chrome scenarios exercise the CDP client against a real browser.""" from __future__ import annotations from pathlib import Path from unittest import mock import contextlib import os import shutil import sys import tempfile import time import unittest ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "priv" / "python")) from albedo_plugins.browser import spawn, render, one # noqa: E402 from albedo_plugins.browser.errors import NotActionable # noqa: E402 from albedo_plugins.browser import hooks, paths # noqa: E402 from albedo_plugins.browser.launch import OwnedChrome, _command # noqa: E402 class BrowserIntegrationTest(unittest.IsolatedAsyncioTestCase): """End-to-end integration tests using local Chromium if present.""" async def asyncSetUp(self): self.has_chrome = ( shutil.which("chromium") is not None or shutil.which("google-chrome") is not None or Path("/Applications/Chromium.app/Contents/MacOS/Chromium").exists() or Path( "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ).exists() ) async def test_spawn_navigate_observe_interact_close(self): if not self.has_chrome: self.skipTest("No Chrome or Chromium installed on this system") b = await spawn(headless=True) try: page = await b.new_page() html = ( "data:text/html," "" "Albedo Test" "" "

Header

" "" "" "" "" "" ) await page.goto(html) # Observe obs = await page.observe() self.assertEqual(obs["title"], "Albedo Test") text = render(obs) self.assertIn("Header", text) self.assertIn("Save", text) # Find and One btn = one(obs, role="button", name="Save") self.assertIsNotNone(btn) field = one(obs, role="textbox", name="Name") self.assertIsNotNone(field) # Fill and Click await page.fill(field, "new value") self.assertEqual( await page.evaluate("document.querySelector('#name').value"), "new value", ) await page.click(btn) self.assertTrue(await page.evaluate("window.clicked")) # Select takes an option's value or its label picker = one(obs, role="combobox", name="Pick") self.assertIsNotNone(picker) await page.select(picker, "b") self.assertEqual( await page.evaluate( "[window.picked, document.querySelector('#pick').value]" ), ["b", "b"], ) await page.select(picker, "Alpha") self.assertEqual( await page.evaluate("document.querySelector('#pick').value"), "a" ) with self.assertRaises(NotActionable) as refused: await page.select(picker, "Gamma") self.assertEqual(refused.exception.details["found"], False) # A screenshot nobody gave a path goes to the cache, not the working tree with ( tempfile.TemporaryDirectory() as home, mock.patch.dict(os.environ, {"ALBEDO_HOME": home}), ): shot = await page.screenshot() self.assertEqual( shot.parent, (Path(home) / "cache" / "browser-screenshots").resolve(), ) self.assertTrue(shot.read_bytes().startswith(b"\x89PNG")) # Close page await page.close() finally: await b.close() PROFILE = Path("/tmp/profile") class LaunchCommandTest(unittest.TestCase): """The argv albedo hands Chrome, read without starting one.""" def test_macos_launches_stay_out_of_the_login_keychain(self): # Chrome raises a blocking "Keychain Not Found" panel when it cannot # resolve a default keychain for its own process. command = _command( "/opt/chrome", PROFILE, headless=False, sandbox=True, args=[] ) self.assertEqual("--use-mock-keychain" in command, sys.platform == "darwin") def test_the_owned_options_are_the_ones_asked_for(self): command = _command( "/opt/chrome", PROFILE, headless=False, sandbox=False, args=["--lang=en"] ) self.assertEqual(command[0], "/opt/chrome") self.assertIn("--user-data-dir=/tmp/profile", command) self.assertIn("--remote-debugging-port=0", command) self.assertNotIn("--headless=new", command) self.assertIn("--no-sandbox", command) self.assertEqual(command[-2:], ["--lang=en", "about:blank"]) headless = _command( "/opt/chrome", PROFILE, headless=True, sandbox=True, args=[] ) self.assertIn("--headless=new", headless) class ScreenshotCacheTest(unittest.TestCase): """Default screenshots live under the albedo home and are not kept forever.""" def test_they_go_to_the_cache_under_the_albedo_home(self): with ( tempfile.TemporaryDirectory() as home, mock.patch.dict(os.environ, {"ALBEDO_HOME": home}), ): self.assertEqual( paths.screenshots(), Path(home) / "cache" / "browser-screenshots" ) def test_only_old_screenshots_are_pruned(self): with tempfile.TemporaryDirectory() as directory: folder = Path(directory) old, fresh, other = ( folder / "old.png", folder / "fresh.png", folder / "n.txt", ) for file in (old, fresh, other): file.write_bytes(b"x") week_ago = time.time() - paths.SCREENSHOT_TTL_SECONDS - 60 for file in (old, other): os.utime(file, (week_ago, week_ago)) paths.prune(folder) self.assertEqual( sorted(p.name for p in folder.iterdir()), ["fresh.png", "n.txt"] ) paths.prune(folder / "missing") class BookkeepingTest(unittest.TestCase): """The browser's own files stay out of a cell's file-change trace.""" def test_profile_setup_runs_unobserved(self): entered: list[bool] = [] original = hooks.unobserved @contextlib.contextmanager def unobserved(): entered.append(True) yield hooks.unobserved = unobserved try: chrome = OwnedChrome(None) try: self.assertTrue(entered) self.assertTrue((chrome.profile / ".albedo-browser-profile").exists()) finally: chrome._release() finally: hooks.unobserved = original if __name__ == "__main__": unittest.main()