Skip to content
Merged
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 .github/workflows/run-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ jobs:
# Keycloak realm that dev and prod share.
TEST_EMAIL_1: ${{ secrets.TEST_EMAIL_1 }}
TEST_PASSWORD_1: ${{ secrets.TEST_PASSWORD_1 }}
# test_con_usecase_dashboards looks up which use cases link dashboards.
# Same resolution as api-smoke, so a prod readonly run reads prod data.
API_BASE_URL: ${{ inputs.api_base_url || vars.API_BASE_URL || 'https://dev.api.civicdataspace.in' }}

steps:
- uses: actions/checkout@v4
Expand Down
6 changes: 6 additions & 0 deletions locators/consumer/usecase_locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ class UseCaseLocators:
# Scoped to /download/resource/ so it targets the dataset file, not the
# chart-image link (/download/chart/), which is a separate failing endpoint.
DOWNLOAD_LINK = (By.XPATH, "//a[contains(@href, '/download/resource/')]")

# Use case detail page (/usecases/<id>): dashboards embedded as iframes (DataSpaceFrontend #476)
DETAIL_DATASETS_HEADING = (By.XPATH, "//main//*[normalize-space(text())='Datasets in this Use Case']")
DETAIL_DASHBOARDS_HEADING = (By.XPATH, "//main//*[normalize-space(text())='Dashboards Linked to this Use Case']")
DETAIL_DASHBOARD_IFRAME = (By.XPATH, "//main//iframe")
DETAIL_DASHBOARD_OPEN_LINK = (By.XPATH, "//main//a[normalize-space()='Open dashboard in a new tab']")
37 changes: 37 additions & 0 deletions pages/consumer/usecase_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
import requests
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from pages.base_page import BasePage
from locators.consumer.usecase_locators import UseCaseLocators

Expand Down Expand Up @@ -47,5 +49,40 @@ def download_first_associated_dataset(self, usecase_index: int = 0, dataset_inde
status = requests.head(href, allow_redirects=True, timeout=10).status_code
return (href, status)

# ── Use case detail page ─────────────────────────────────────────────────

def open_detail(self, base_url: str, usecase_id):
"""Open /usecases/<id> and wait for the always-rendered datasets heading."""
self.visit(f"{base_url.rstrip('/')}/usecases/{usecase_id}")
self.find(UseCaseLocators.DETAIL_DATASETS_HEADING)
return self

def has_dashboards_section(self, timeout: int = 10) -> bool:
try:
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located(UseCaseLocators.DETAIL_DASHBOARDS_HEADING)
)
return True
except TimeoutException:
return False

def embedded_dashboards(self):
"""[{title, src}] for each dashboard iframe on the detail page."""
frames = self.finds(UseCaseLocators.DETAIL_DASHBOARD_IFRAME)
return [{"title": f.get_attribute("title"), "src": f.get_attribute("src")} for f in frames]

def dashboard_open_links(self):
"""[{href, target, rel}] for each 'Open dashboard in a new tab' link."""
links = self.finds(UseCaseLocators.DETAIL_DASHBOARD_OPEN_LINK)
return [
{"href": a.get_attribute("href"), "target": a.get_attribute("target"), "rel": a.get_attribute("rel")}
for a in links
]

def dashboards_render_before_datasets(self) -> bool:
dash = self.find(UseCaseLocators.DETAIL_DASHBOARDS_HEADING)
datasets = self.find(UseCaseLocators.DETAIL_DATASETS_HEADING)
return self.driver.execute_script(
"return !!(arguments[0].compareDocumentPosition(arguments[1]) & Node.DOCUMENT_POSITION_FOLLOWING);",
dash, datasets,
)
111 changes: 111 additions & 0 deletions tests/consumer/smoke/test_con_usecase_dashboards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# tests/consumer/smoke/test_con_usecase_dashboards.py
#
# Use case detail page dashboards (DataSpaceFrontend PR #476): linked
# dashboards are embedded as iframes above "Datasets in this Use Case",
# Superset links get standalone=1, and links that aren't http(s) are dropped
# (hiding the whole section when none are left).
#
# Use cases are picked from the backend at runtime rather than hardcoded,
# so the same tests work against dev and prod data.

import os
from urllib.parse import parse_qs, urlsplit

import pytest
import requests

from pages.consumer.usecase_page import UseCasePage

pytestmark = [pytest.mark.smoke, pytest.mark.readonly]

BASE_URL = os.getenv("HOME_URL_DEV", "https://dev.civicdataspace.in")


def _gql(api, query):
r = requests.post(f"{api.rstrip('/')}/api/graphql", json={"query": query}, timeout=20)
r.raise_for_status()
body = r.json()
assert not body.get("errors"), body["errors"]
return body["data"]


def _published_ids(api):
return [u["id"] for u in _gql(api, "{ useCases(pagination:{limit:500}){ id status } }")["useCases"]
if u["status"] == "PUBLISHED"]


def _is_web_url(link):
return urlsplit(link or "").scheme in ("http", "https")


@pytest.fixture(scope="module")
def dashboards_by_usecase():
api = os.getenv("API_BASE_URL")
if not api:
pytest.skip("API_BASE_URL not set: cannot find use cases with dashboards")
found = {}
for uc_id in _published_ids(api):
dash = _gql(api, "{ usecaseDashboards(usecaseId:%s){ id name link } }" % uc_id)["usecaseDashboards"]
if dash:
found[uc_id] = dash
return found


@pytest.fixture(scope="module")
def superset_usecase(dashboards_by_usecase):
superset = [(uc_id, d) for uc_id, dash in dashboards_by_usecase.items() for d in dash
if _is_web_url(d["link"]) and "/superset/" in urlsplit(d["link"]).path]
if not superset:
pytest.skip("No published use case links a Superset dashboard")
# Prefer a link without standalone=1 so the test proves the frontend adds it.
return next((s for s in superset if "standalone" not in parse_qs(urlsplit(s[1]["link"]).query)), superset[0])


@pytest.fixture
def uc_page(driver):
return UseCasePage(driver, timeout=30)


def test_superset_dashboard_is_embedded_standalone(uc_page, superset_usecase):
uc_id, dashboard = superset_usecase
uc_page.open_detail(BASE_URL, uc_id)

frames = {f["title"]: f["src"] for f in uc_page.embedded_dashboards()}
assert dashboard["name"] in frames, f"use case {uc_id}: no iframe titled {dashboard['name']!r}, got {list(frames)}"

src, link = urlsplit(frames[dashboard["name"]]), urlsplit(dashboard["link"])
assert (src.netloc, src.path) == (link.netloc, link.path), f"iframe src {src.geturl()} is not the dashboard {link.geturl()}"
params = parse_qs(src.query)
assert params.get("standalone") == ["1"], f"Superset iframe src lacks standalone=1: {src.geturl()}"
for key, value in parse_qs(link.query).items():
assert params.get(key) == value, f"iframe src dropped/changed ?{key}= from the dashboard link: {src.geturl()}"

opens = [o for o in uc_page.dashboard_open_links() if o["href"] == src.geturl()]
assert opens, f"no 'Open dashboard in a new tab' link pointing at {src.geturl()}"
assert opens[0]["target"] == "_blank"
assert "noreferrer" in (opens[0]["rel"] or "")


def test_dashboards_render_above_datasets(uc_page, superset_usecase):
uc_id, _ = superset_usecase
uc_page.open_detail(BASE_URL, uc_id)
assert uc_page.has_dashboards_section(), f"use case {uc_id}: dashboards section missing"
assert uc_page.dashboards_render_before_datasets(), (
f"use case {uc_id}: 'Dashboards Linked to this Use Case' renders below 'Datasets in this Use Case'"
)


@pytest.mark.parametrize("case", ["no_dashboards", "no_usable_link"])
def test_section_hidden_without_embeddable_dashboards(uc_page, dashboards_by_usecase, case):
if case == "no_dashboards":
ids = _published_ids(os.environ["API_BASE_URL"])
uc_id = next((u for u in ids if u not in dashboards_by_usecase), None)
else:
uc_id = next((u for u, dash in dashboards_by_usecase.items()
if not any(_is_web_url(d["link"]) for d in dash)), None)
if uc_id is None:
pytest.skip(f"No published use case for case {case!r}")
uc_page.open_detail(BASE_URL, uc_id)
assert not uc_page.has_dashboards_section(timeout=8), (
f"use case {uc_id} ({case}): dashboards section rendered with nothing embeddable to show"
)
Loading