Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions locators/ai_maker_locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class AIMakerLocators:
STAT_CARD = "[class*='stat'], [class*='card'], [class*='Card']"
STAT_CARD_VALUE = ".metric-card-value"

# ── Org selection page: org card logos (alt is "<org name> logo") ─────────
ORG_CARD_LOGO = "img[alt$=' logo']"

# ── Org selection page: Add Organisation button (Jun 2026) ───────────────
ADD_ORGANISATION_BUTTON = (
"button:has-text('Add Organisation'), a:has-text('Add Organisation')"
Expand Down
12 changes: 12 additions & 0 deletions pages/ai_maker_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ def go_to_landing(self) -> "AIMakerPage":
self.wait_for_app_ready()
return self

def org_card_logos(self) -> list[dict]:
"""src and load state of each org card logo on the landing page, once they finish loading."""
logos = self.page.locator(AIMakerLocators.ORG_CARD_LOGO)
logos.first.wait_for(state="attached")
self.page.wait_for_function(
"sel => [...document.querySelectorAll(sel)].every(i => i.complete)",
arg=AIMakerLocators.ORG_CARD_LOGO,
)
return logos.evaluate_all(
"imgs => imgs.map(i => ({alt: i.alt, src: i.getAttribute('src'), width: i.naturalWidth}))"
)

def go_to_models(self) -> None:
self.click(self.SIDEBAR_MODELS)
self.wait_for_app_ready()
Expand Down
23 changes: 23 additions & 0 deletions tests/e2e/test_ai_maker_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,29 @@ def test_text_generation_badge_is_present(self, page: Page):
# ── Add Organisation / Add A New Model buttons (Jun 2026) ─────────────────────


class TestOrgCardLogos:
"""Org logos are stored as absolute S3/CloudFront URLs and must be used as-is
(ParakhAI-frontend#470). The old code prefixed the DataSpace API host, which
turned an absolute URL into a broken one."""

def test_org_card_logos_load_from_their_own_url(self, page: Page, authenticated_graphql_client):
orgs = authenticated_graphql_client(
"query { myOrganizations { name logoUrl } }"
)["data"]["myOrganizations"]
uploaded = {f"{o['name']} logo": o["logoUrl"] for o in orgs if (o.get("logoUrl") or "").startswith("http")}
if not uploaded:
pytest.skip("No organisation of this account has an absolute logo URL")

shown = {} # alt -> srcs; the page header also has a "CivicDataLab logo"
for logo in AIMakerPage(page).go_to_landing().org_card_logos():
shown.setdefault(logo["alt"], []).append(logo["src"])
wrong = {alt: shown.get(alt) for alt, url in uploaded.items() if url not in shown.get(alt, [])}
assert not wrong, (
f"{len(wrong)} of {len(uploaded)} org logos do not use their stored URL as-is "
f"(a glued-on host, or the fallback after a failed load): {dict(list(wrong.items())[:3])}"
)


class TestAIMakerButtons:
"""New action buttons trigger AlertDialogs for external CivicDataSpace redirects."""

Expand Down
Loading