From 6770ed9cd36fe68610b74826520f8c6effc121f6 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 19 Sep 2026 18:55:53 +0200 Subject: test(hub): the Firefox probe stops opening the developer's own profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `test_sticky_header.py[firefox]` failed with twelve setup errors whenever a Firefox was open, and the note in CLAUDE.md said to close it and trust the Chrome half meanwhile. The cause was not snap's single instance: the harness pointed HOME at a throwaway directory, snapd sets its own HOME inside the sandbox, and that directory came back empty on every run — so Firefox opened the real profile, which the open browser locks. `--profile` needs a path the sandbox can see: the snap has a private /tmp, and its home interface grants no hidden directory. Both refusals print "Firefox is already running", which it also prints when nothing is. 24 passed in 15s with a Firefox open throughout, against 12 errors in 60s. Co-Authored-By: Claude Opus 5 --- .../tests/harness/sticky_header_probe.py | 47 +++++++++++++++------- 1 file changed, 33 insertions(+), 14 deletions(-) (limited to 'packages/meshbay-hub/tests') diff --git a/packages/meshbay-hub/tests/harness/sticky_header_probe.py b/packages/meshbay-hub/tests/harness/sticky_header_probe.py index 7579bf9..3e75494 100755 --- a/packages/meshbay-hub/tests/harness/sticky_header_probe.py +++ b/packages/meshbay-hub/tests/harness/sticky_header_probe.py @@ -29,7 +29,6 @@ half of MeshBay's readers and has its own history with `position: sticky`. import argparse import http.server import json -import os import socketserver import subprocess import sys @@ -525,22 +524,41 @@ ENGINES = { # is driven through `--screenshot`: the picture is thrown away, the point # is that the page runs and the load event is what ends the process. # - # And it is isolated with `HOME`, not `--profile`: given `--profile` on a - # directory it has not initialised itself, this Firefox starts, prints its - # headless banner, and then never requests the URL at all — no error, no - # page, nothing to debug. Pointing HOME at a throwaway directory lets it - # create its own default profile there, which works and leaves the - # developer's real one alone. + # `--profile`, and the directory has to be one the browser can actually + # read — see PROFILE_PARENT. This used to point `HOME` at a throwaway + # directory instead, which isolated nothing at all: snapd sets its own HOME + # inside the sandbox, the throwaway one came back empty every time, and + # Firefox opened the developer's real profile. With their browser open that + # profile is locked, so every run printed "Firefox is already running" and + # measured nothing — twelve errors at setup that looked like a regression + # and were not. "firefox": lambda profile, size: [ - "firefox", "--headless", + "firefox", "--headless", "--profile", profile, "--screenshot", str(Path(profile) / "shot.png"), "--window-size", size], } -# The environment each engine is launched with, on top of the current one. -ENGINE_ENV = {"firefox": lambda profile: {"HOME": profile}} # Which engines need the load event held until the measurement is in. HOLDS_LOAD = {"firefox"} +# Where an engine's throwaway profile has to live. Chrome takes /tmp and is +# absent from this map. +# +# Firefox on this distribution is a snap, and two rules of that sandbox decide +# this between them: it has a private /tmp, so a directory made there is simply +# not the one it sees, and the `home` interface grants no *hidden* directory, so +# `~/.cache` is refused as well. Both failures are silent in their own way — +# "Could not find profile folder" for the first, and for an unreadable profile +# the same misleading "Firefox is already running" it prints when nothing is +# running at all. What is left is a plain directory under $HOME; the snap's own +# data directory is preferred where it exists, so a run leaves nothing in the +# developer's home even for the moment it takes. +def _profile_parent() -> Path | None: + snap_data = Path.home() / "snap" / "firefox" / "common" + return snap_data if snap_data.is_dir() else Path.home() + + +PROFILE_PARENT = {"firefox": _profile_parent} + def main() -> int: ap = argparse.ArgumentParser() @@ -555,12 +573,13 @@ def main() -> int: # ignore_cleanup_errors for the same reason group_tab_probe.py gives: # Chrome's children outlive terminate() by a moment and go on writing # into the profile, and a throwaway profile is not worth a failed run. - with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as profile: - env = dict(os.environ, - **ENGINE_ENV.get(args.engine, lambda _p: {})(profile)) + parent = PROFILE_PARENT.get(args.engine) + with tempfile.TemporaryDirectory( + ignore_cleanup_errors=True, prefix="meshbay-probe-", + dir=str(parent()) if parent else None) as profile: proc = subprocess.Popen( launcher(profile, "1200,900") + [f"http://127.0.0.1:{PORT}/"], - env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) for _ in range(900): if RECORDS: break -- cgit v1.2.3