"""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()