fix(tests): repair multi-user-basic Astrolabe integration suite
The Astrolabe PHP→Vue settings refactor dropped three stable element ids (#mcp-enable-background-button, #mcp-revoke-background-button, #mcp-revoke-background-form) that the multi-user-basic integration suite drives the background-sync enable/disable/revoke flows through. Their absence timed out the 5s Playwright locators and failed four tests: - test_astrolabe_multi_user_background_sync::test_multi_user_astrolabe_background_sync_enablement - test_astrolabe_multi_user_background_sync::test_revoke_background_sync_access - test_astrolabe_chunk_context::test_chunk_context_endpoint_uses_app_password - test_astrolabe_plotly_visualization::test_astrolabe_plotly_visualization_with_basic_auth (the latter two enable background sync via complete_astrolabe_authorization before exercising the app-password / indexed-search paths). Two-part fix: 1. Bump the astrolabe submodule to v0.20.1 (cbcoutinho/astrolabe#116), which restores the three element ids on the refactored NcButtons. 2. Defense-in-depth in the test helpers: resolve the enable/revoke buttons by their stable id first, falling back to the button's accessible name so a future id rename degrades to a slower-but-working lookup instead of a hard timeout. Avoids a combined `.or_()` locator, which would strict-mode-violate (the id button also matches by text). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
09e84783e5
commit
531607a1a1
@@ -18,17 +18,59 @@ password minted + forwarded to MCP → background sync active → DB verificatio
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import anyio
|
import anyio
|
||||||
import pytest
|
import pytest
|
||||||
from playwright.async_api import Page
|
from playwright.async_api import Locator, Page
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
pytestmark = [pytest.mark.integration, pytest.mark.multi_user_basic]
|
pytestmark = [pytest.mark.integration, pytest.mark.multi_user_basic]
|
||||||
|
|
||||||
|
# Accessible-name fallbacks for the background-sync buttons. The stable element
|
||||||
|
# ids (#mcp-enable-background-button / #mcp-revoke-background-button) are the
|
||||||
|
# primary, refactor-proof hook restored in the Astrolabe frontend; resolving by
|
||||||
|
# button text as well means a future id rename degrades to a slower-but-working
|
||||||
|
# lookup instead of a hard 5s Playwright timeout.
|
||||||
|
_ENABLE_NAME = re.compile("Enable background indexing", re.IGNORECASE)
|
||||||
|
_REVOKE_NAME = re.compile("Disable background indexing", re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
async def _resolve_settings_button(
|
||||||
|
page: Page, button_id: str, name: re.Pattern, *, timeout: int = 5000
|
||||||
|
) -> Locator:
|
||||||
|
"""Resolve a background-sync settings button, preferring its stable id.
|
||||||
|
|
||||||
|
Waits up to ``timeout`` for ``button_id`` to become visible; if it never
|
||||||
|
appears, falls back to the button's accessible name. Returning the id
|
||||||
|
locator unchanged when present keeps the match unambiguous (the id button
|
||||||
|
also matches by text, so a combined ``.or_()`` would strict-mode-violate).
|
||||||
|
"""
|
||||||
|
by_id = page.locator(button_id)
|
||||||
|
try:
|
||||||
|
await by_id.wait_for(timeout=timeout, state="visible")
|
||||||
|
return by_id
|
||||||
|
except Exception:
|
||||||
|
logger.warning(
|
||||||
|
"%s not found; falling back to accessible-name lookup", button_id
|
||||||
|
)
|
||||||
|
by_name = page.get_by_role("button", name=name)
|
||||||
|
await by_name.wait_for(timeout=timeout, state="visible")
|
||||||
|
return by_name
|
||||||
|
|
||||||
|
|
||||||
|
async def _background_sync_enabled(page: Page) -> bool:
|
||||||
|
"""True when the settings page is in the enabled state (revoke control shown).
|
||||||
|
|
||||||
|
Checks the stable id first, then the accessible name as a fallback.
|
||||||
|
"""
|
||||||
|
if await page.locator("#mcp-revoke-background-button").count() > 0:
|
||||||
|
return True
|
||||||
|
return await page.get_by_role("button", name=_REVOKE_NAME).count() > 0
|
||||||
|
|
||||||
|
|
||||||
async def login_to_nextcloud(page: Page, username: str, password: str):
|
async def login_to_nextcloud(page: Page, username: str, password: str):
|
||||||
"""Helper function to login to Nextcloud via Playwright.
|
"""Helper function to login to Nextcloud via Playwright.
|
||||||
@@ -113,19 +155,20 @@ async def enable_background_sync(page: Page, username: str) -> bool:
|
|||||||
)
|
)
|
||||||
await anyio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
if await page.locator("#mcp-revoke-background-button").count() > 0:
|
if await _background_sync_enabled(page):
|
||||||
logger.info("✓ Background indexing already enabled for %s", username)
|
logger.info("✓ Background indexing already enabled for %s", username)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
enable_button = page.locator("#mcp-enable-background-button")
|
enable_button = await _resolve_settings_button(
|
||||||
await enable_button.wait_for(timeout=5000, state="visible")
|
page, "#mcp-enable-background-button", _ENABLE_NAME
|
||||||
|
)
|
||||||
await enable_button.click()
|
await enable_button.click()
|
||||||
logger.info("Clicked 'Enable background indexing' for %s", username)
|
logger.info("Clicked 'Enable background indexing' for %s", username)
|
||||||
|
|
||||||
# On success the page JS reloads to the enabled state (revoke form shown).
|
# On success the page JS reloads to the enabled state (revoke form shown).
|
||||||
try:
|
try:
|
||||||
await page.locator("#mcp-revoke-background-button").wait_for(
|
await _resolve_settings_button(
|
||||||
timeout=15000, state="visible"
|
page, "#mcp-revoke-background-button", _REVOKE_NAME, timeout=15000
|
||||||
)
|
)
|
||||||
logger.info("✓ Background indexing enabled for %s", username)
|
logger.info("✓ Background indexing enabled for %s", username)
|
||||||
return True
|
return True
|
||||||
@@ -507,21 +550,19 @@ async def revoke_background_sync_access(page: Page, username: str) -> bool:
|
|||||||
# Wait for page to load
|
# Wait for page to load
|
||||||
await anyio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
# The revoke form (#mcp-revoke-background-form) is only rendered while
|
# The revoke control (#mcp-revoke-background-button, inside the
|
||||||
# background indexing is enabled.
|
# #mcp-revoke-background-form container) is only rendered while background
|
||||||
revoke_button = page.locator("#mcp-revoke-background-button")
|
# indexing is enabled.
|
||||||
try:
|
if not await _background_sync_enabled(page):
|
||||||
if await revoke_button.count() == 0:
|
logger.warning(
|
||||||
logger.warning(
|
"Background indexing not enabled for %s, nothing to revoke", username
|
||||||
"Background indexing not enabled for %s, nothing to revoke", username
|
)
|
||||||
)
|
|
||||||
return False
|
|
||||||
except Exception:
|
|
||||||
logger.warning("Could not find revoke button for %s", username)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await revoke_button.wait_for(timeout=5000, state="visible")
|
revoke_button = await _resolve_settings_button(
|
||||||
|
page, "#mcp-revoke-background-button", _REVOKE_NAME
|
||||||
|
)
|
||||||
logger.info("Found 'Disable background indexing' button")
|
logger.info("Found 'Disable background indexing' button")
|
||||||
except Exception:
|
except Exception:
|
||||||
screenshot_path = (
|
screenshot_path = (
|
||||||
@@ -586,9 +627,10 @@ async def revoke_background_sync_access(page: Page, username: str) -> bool:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# After revoke + reload the settings page returns to the un-provisioned
|
# After revoke + reload the settings page returns to the un-provisioned
|
||||||
# state: the revoke button is gone and the app-password input is shown again.
|
# state: the revoke button is gone and the enable button is shown again.
|
||||||
try:
|
try:
|
||||||
if await page.locator("#mcp-revoke-background-button").is_visible(timeout=2000):
|
await anyio.sleep(2)
|
||||||
|
if await _background_sync_enabled(page):
|
||||||
logger.error("Revoke button still visible for %s after revoke!", username)
|
logger.error("Revoke button still visible for %s after revoke!", username)
|
||||||
screenshot_path = (
|
screenshot_path = (
|
||||||
f"{tempfile.gettempdir()}/astrolabe_revoke_still_enabled_{username}.png"
|
f"{tempfile.gettempdir()}/astrolabe_revoke_still_enabled_{username}.png"
|
||||||
|
|||||||
Vendored
+1
-1
Submodule third_party/astrolabe updated: 0d9edc647d...af55853adf
Reference in New Issue
Block a user