09e9f3545f
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
dependency-audit / pip-audit (push) Has been cancelled
101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
"""E2E for layer-3 headless re-auth (gated on a real persistent profile).
|
|
|
|
This test drives a REAL headless browser against the persistent login profile
|
|
and is excluded from CI by default (``-m e2e`` + a profile/opt-in gate). It
|
|
exists to exercise the one path the unit tests fake: the actual
|
|
launch -> navigate -> classify -> capture -> persist sequence against a live
|
|
Google session.
|
|
|
|
Run locally, after ``notebooklm login`` has populated the profile, with::
|
|
|
|
NOTEBOOKLM_HEADLESS_REAUTH=1 pytest tests/e2e/test_headless_reauth.py -m e2e
|
|
|
|
It is SKIPPED unless both:
|
|
|
|
* the persistent browser profile exists and is non-empty (a real Google
|
|
session to harvest), and
|
|
* the operator opted in via ``NOTEBOOKLM_HEADLESS_REAUTH=1`` (the same opt-in
|
|
the production mid-RPC path requires) — so the test never silently drives a
|
|
browser in an environment that did not ask for it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from notebooklm._auth.headless_reauth import (
|
|
HeadlessReauthStatus,
|
|
attempt_headless_reauth,
|
|
)
|
|
from notebooklm.paths import get_browser_profile_dir, get_storage_path
|
|
|
|
|
|
def _profile_is_reusable() -> bool:
|
|
profile = get_browser_profile_dir()
|
|
if not profile.is_dir():
|
|
return False
|
|
try:
|
|
next(profile.iterdir())
|
|
except (StopIteration, OSError):
|
|
return False
|
|
return True
|
|
|
|
|
|
def _playwright_available() -> bool:
|
|
"""True when the ``browser`` extra is importable.
|
|
|
|
Without it ``attempt_headless_reauth`` returns ``UNAVAILABLE`` (nothing to
|
|
drive), which is NOT one of the SUCCESS/FAILED outcomes this test asserts —
|
|
so the gate must skip rather than let the test fail on a missing optional
|
|
dependency.
|
|
"""
|
|
try:
|
|
import playwright.sync_api # noqa: F401
|
|
except ImportError:
|
|
return False
|
|
return True
|
|
|
|
|
|
_GATE = pytest.mark.skipif(
|
|
os.environ.get("NOTEBOOKLM_HEADLESS_REAUTH") != "1"
|
|
or not _profile_is_reusable()
|
|
or not _playwright_available(),
|
|
reason=(
|
|
"headless re-auth e2e requires NOTEBOOKLM_HEADLESS_REAUTH=1, a non-empty "
|
|
"persistent browser profile (run 'notebooklm login' first), and the "
|
|
"'browser' extra installed"
|
|
),
|
|
)
|
|
|
|
|
|
@pytest.mark.e2e
|
|
@_GATE
|
|
def test_headless_reauth_against_live_profile() -> None:
|
|
"""Drive a real headless re-auth and assert an honest, typed outcome.
|
|
|
|
A live profile session yields SUCCESS (cookies re-minted + persisted); an
|
|
expired profile session yields FAILED. UNAVAILABLE should not occur here
|
|
because the gate already proved opt-in + a reusable profile. Either way the
|
|
result is one of the three honest, typed outcomes — never a silent ``None``.
|
|
"""
|
|
result = attempt_headless_reauth(
|
|
storage_path=get_storage_path(),
|
|
allow_headless=True,
|
|
)
|
|
|
|
assert result.status in {
|
|
HeadlessReauthStatus.SUCCESS,
|
|
HeadlessReauthStatus.FAILED,
|
|
}, f"unexpected status {result.status} (reason: {result.reason})"
|
|
|
|
if result.status is HeadlessReauthStatus.SUCCESS:
|
|
assert result.succeeded is True
|
|
assert result.storage_path == get_storage_path()
|
|
assert get_storage_path().exists()
|
|
else:
|
|
# Honest failure — the profile's Google session is also expired.
|
|
assert result.succeeded is False
|
|
assert result.storage_path is None
|