145 lines
5.0 KiB
Python
145 lines
5.0 KiB
Python
"""Frontend E2E tests — asset list, search, filter, detail."""
|
|
|
|
import pytest
|
|
import requests
|
|
|
|
|
|
def _login(page):
|
|
"""Helper: login as admin."""
|
|
page.locator("#loginUsername").fill("admin")
|
|
page.locator("#loginPassword").fill("changeme")
|
|
page.locator("button:has-text('Sign In')").click()
|
|
page.wait_for_selector("#loginOverlay", state="hidden", timeout=5000)
|
|
|
|
|
|
@pytest.mark.frontend
|
|
def test_asset_list_shows_created_asset(page, live_server):
|
|
"""Assets created via API appear in the Assets tab."""
|
|
_login(page)
|
|
|
|
# Create an asset via API
|
|
resp = requests.post(
|
|
f"{live_server}/api/assets",
|
|
json={
|
|
"machine_id": "TEST-001",
|
|
"name": "Test Espresso Machine",
|
|
"category": "Appliances",
|
|
"status": "active",
|
|
},
|
|
)
|
|
assert resp.status_code == 201
|
|
|
|
# Navigate to Assets tab
|
|
page.locator(".tab-btn[data-tab='tabAssets']").click()
|
|
page.wait_for_selector("#tabAssets.active", timeout=3000)
|
|
|
|
# Wait for the asset list to render
|
|
page.wait_for_selector(".asset-item", timeout=5000)
|
|
assert page.locator(".asset-item").count() >= 1
|
|
assert page.locator(".ai-name:has-text('Test Espresso Machine')").is_visible()
|
|
|
|
|
|
@pytest.mark.frontend
|
|
def test_asset_list_empty_state(page, live_server):
|
|
"""Assets tab shows empty state when no assets exist."""
|
|
_login(page)
|
|
|
|
page.locator(".tab-btn[data-tab='tabAssets']").click()
|
|
page.wait_for_selector("#tabAssets.active", timeout=3000)
|
|
|
|
# Should show empty state (no assets seeded into fresh DB).
|
|
# loadAssets() runs async — give it time to fetch and render.
|
|
page.wait_for_timeout(2000)
|
|
has_empty = page.locator(".empty-state").count() > 0
|
|
has_items = page.locator(".asset-item").count() > 0
|
|
assert not has_items, f"Fresh DB has {page.locator('.asset-item').count()} assets unexpectedly"
|
|
assert has_empty, "Empty state should appear on fresh DB"
|
|
|
|
|
|
@pytest.mark.frontend
|
|
def test_asset_search_filters_by_name(page, live_server):
|
|
"""Search input filters assets by name."""
|
|
_login(page)
|
|
|
|
# Create two assets via API
|
|
for mid, name in [("SRCH-001", "Alpha Blender"), ("SRCH-002", "Beta Oven")]:
|
|
requests.post(
|
|
f"{live_server}/api/assets",
|
|
json={"machine_id": mid, "name": name, "category": "Appliances"},
|
|
)
|
|
|
|
# Navigate to Assets
|
|
page.locator(".tab-btn[data-tab='tabAssets']").click()
|
|
page.wait_for_selector("#tabAssets.active", timeout=3000)
|
|
page.wait_for_selector(".asset-item", timeout=5000)
|
|
|
|
# Search for "Alpha" — use #assetSearch to avoid ambiguity with
|
|
# customer and activity search inputs that share the .input-field class.
|
|
page.locator("#assetSearch").fill("Alpha")
|
|
page.wait_for_timeout(500) # debounce
|
|
|
|
items = page.locator(".asset-item")
|
|
assert items.count() == 1
|
|
assert page.locator(".ai-name:has-text('Alpha Blender')").is_visible()
|
|
|
|
|
|
@pytest.mark.frontend
|
|
def test_asset_category_filter(page, live_server):
|
|
"""Category filter pills filter assets."""
|
|
_login(page)
|
|
|
|
# Create assets in different categories
|
|
requests.post(
|
|
f"{live_server}/api/assets",
|
|
json={"machine_id": "FILT-001", "name": "Chair", "category": "Furniture"},
|
|
)
|
|
requests.post(
|
|
f"{live_server}/api/assets",
|
|
json={"machine_id": "FILT-002", "name": "Fridge", "category": "Appliances"},
|
|
)
|
|
|
|
# Navigate to Assets
|
|
page.locator(".tab-btn[data-tab='tabAssets']").click()
|
|
page.wait_for_selector("#tabAssets.active", timeout=3000)
|
|
page.wait_for_selector(".asset-item", timeout=5000)
|
|
# wait_for_selector returns on first match — give the list time to fully render
|
|
page.wait_for_timeout(500)
|
|
assert page.locator(".asset-item").count() == 2
|
|
|
|
# Click "Furniture" filter pill
|
|
page.locator(".pill:has-text('Furniture')").click()
|
|
page.wait_for_timeout(300)
|
|
|
|
assert page.locator(".asset-item").count() == 1
|
|
assert page.locator(".ai-name:has-text('Chair')").is_visible()
|
|
|
|
|
|
@pytest.mark.frontend
|
|
def test_asset_detail_view(page, live_server):
|
|
"""Clicking an asset opens detail panel with correct info."""
|
|
_login(page)
|
|
|
|
requests.post(
|
|
f"{live_server}/api/assets",
|
|
json={
|
|
"machine_id": "DETAIL-001",
|
|
"name": "Detail Test Asset",
|
|
"description": "A test asset for detail view",
|
|
"category": "Equipment",
|
|
"status": "active",
|
|
},
|
|
)
|
|
|
|
page.locator(".tab-btn[data-tab='tabAssets']").click()
|
|
page.wait_for_selector("#tabAssets.active", timeout=3000)
|
|
page.wait_for_selector(".asset-item", timeout=5000)
|
|
|
|
# Click the asset — viewAsset() calls showDetailView(), which
|
|
# makes #assetsDetailView visible (not .scan-result — that's for
|
|
# barcode scans).
|
|
page.locator(".ai-name:has-text('Detail Test Asset')").click()
|
|
page.wait_for_selector("#assetsDetailView", state="visible", timeout=5000)
|
|
|
|
# Verify detail content
|
|
assert page.locator("#detailName:has-text('Detail Test Asset')").is_visible()
|