Files
Inventory/scripts/test_server_browser.py
DenisToxic 4a3a9aeefb
Some checks failed
Tests / postgres (push) Has been cancelled
Add browser coverage and tested inventory fixes
2026-10-02 09:30:03 +02:00

156 lines
8.3 KiB
Python

"""Run representative write flows against a disposable deployed test server.
Requires CSR_TEST_BASE_URL, CSR_TEST_USER, and CSR_TEST_PASSWORD. Prints the
unique record prefix so created test data can be removed after inspection.
"""
import os
import time
from pathlib import Path
from tempfile import TemporaryDirectory
from openpyxl import Workbook
from playwright.sync_api import expect, sync_playwright
def run():
base = os.environ["CSR_TEST_BASE_URL"].rstrip("/")
username = os.environ["CSR_TEST_USER"]
password = os.environ["CSR_TEST_PASSWORD"]
marker = f"PW{int(time.time())}"
print(f"TEST_MARKER={marker}", flush=True)
errors = []
with sync_playwright() as playwright, TemporaryDirectory(prefix="csr-server-browser-") as temporary:
browser = playwright.chromium.launch(headless=True)
context = browser.new_context(ignore_https_errors=True, accept_downloads=True)
page = context.new_page()
page.set_default_timeout(10000)
page.on("pageerror", lambda error: errors.append(str(error)))
page.goto(f"{base}/accounts/login/")
page.locator('[name="username"]').fill(username)
page.locator('[name="password"]').fill(password)
page.locator('form button').click()
assert page.url.rstrip("/") == base, f"Login failed: {page.url}"
for route in ("/", "/uebersicht/", "/ablauf/", "/aufgaben/", "/configuration/", "/users/", "/archive/", "/history/", "/import/"):
response = page.goto(f"{base}{route}")
assert response.status == 200, f"{route}: {response.status}"
print("PASS login and main routes", flush=True)
page.goto(f"{base}/devices/new/")
page.locator('[name="device_type"]').select_option(label="Laptop")
assert page.locator('[data-hardware-field="ram_gb"]').is_visible()
page.locator('[name="model"]').fill(f"{marker}-Laptop")
page.locator('[name="employee"]').select_option(index=1)
page.locator('[name="ram_gb"]').fill("16")
page.locator('[name="ssd_gb"]').fill("512")
page.locator('form[data-device-type-form] button').click()
page.goto(f"{base}/?model_contains={marker}")
expect(page.locator('tr[data-device-id]')).to_have_count(1)
row = page.locator('tr[data-device-id]').first
device_id = row.get_attribute('data-device-id')
assert page.locator('[data-filter-panel]').is_hidden()
row.locator('td[data-field="model"]').dblclick()
field = row.locator('td[data-field="model"] input')
field.fill(f"{marker}-Laptop-Edited")
field.press('Enter')
expect(row.locator('td[data-field="model"]')).to_have_text(f"{marker}-Laptop-Edited")
print(f"PASS create, filter, inline edit: device {device_id}", flush=True)
page.goto(f"{base}/devices/{device_id}/")
task = page.locator(f'form[action="/devices/{device_id}/tasks/"]')
task.locator('[name="title"]').fill(f"{marker}-Akku prüfen")
task.locator('button').click()
stage = page.locator(f'form[action="/devices/{device_id}/stage/"]')
stage.locator('[name="stage"]').select_option('INSPECTION')
stage.locator('button').click()
page.goto(f"{base}/ablauf/")
card = page.locator('.workflow-card').filter(has=page.locator(f'a[href="/devices/{device_id}/"]'))
inspection = card.locator(f'form[action="/devices/{device_id}/inspection/"]')
inspection.locator('[name="defects"]').fill(f"{marker}-Akku schwach")
inspection.locator('[name="ram_gb"]').fill('16')
inspection.locator('button').click()
expect(card).to_contain_text(f"{marker}-Akku schwach")
stage = card.locator(f'form[action="/devices/{device_id}/stage/"]')
stage.locator('[name="stage"]').select_option('REPAIR')
stage.locator('button').click()
repair = card.locator(f'form[action="/devices/{device_id}/repairs/"]')
repair.locator('[name="work_done"]').fill(f"{marker}-Akku ersetzt")
repair.locator('button').click()
stage = card.locator(f'form[action="/devices/{device_id}/stage/"]')
stage.locator('[name="stage"]').select_option('STORAGE')
stage.locator('button').click()
expect(repair).to_be_visible() # An open task blocks storage.
page.goto(f"{base}/aufgaben/")
page.locator(f'form[action^="/devices/{device_id}/tasks/"][action$="/complete/"] button').click()
page.goto(f"{base}/ablauf/")
stage = card.locator(f'form[action="/devices/{device_id}/stage/"]')
stage.locator('[name="stage"]').select_option('STORAGE')
stage.locator('button').click()
storage = card.locator(f'form[action="/devices/{device_id}/recipient/"]')
expect(storage).to_be_visible()
storage.locator('[name="recipient"]').fill(f"{marker}-Recipient")
storage.locator('button').click()
stage = card.locator(f'form[action="/devices/{device_id}/stage/"]')
stage.locator('[name="stage"]').select_option('ISSUED')
stage.locator('button').click()
expect(page.locator('#stage-ISSUED').locator('../..')).to_contain_text(f"{marker}-Laptop-Edited")
print("PASS PostgreSQL workflow, records, task gating, recipient, issuance", flush=True)
page.goto(f"{base}/devices/{device_id}/")
page.get_by_role('button', name='Gerät archivieren').click()
page.locator('.confirm-box button').last.click()
page.goto(f"{base}/archive/")
expect(page.locator('tbody')).to_contain_text(f"{marker}-Laptop-Edited")
page.locator(f'form[action="/archive/{device_id}/restore/"] button').click()
page.goto(f"{base}/devices/{device_id}/")
assert page.url.rstrip('/').endswith(f'/devices/{device_id}')
expect(page.locator('h1')).to_contain_text('Gerät #')
print("PASS archive and restore", flush=True)
page.goto(f"{base}/import/")
for label, suffix in (("XLSX herunterladen", '.xlsx'), ("ODS für LibreOffice", '.ods')):
with page.expect_download() as event:
page.get_by_role('link', name=label).click()
assert event.value.suggested_filename.endswith(suffix)
workbook = Workbook()
workbook.active.append(['Modell', 'Typ', 'Status', 'Druckverfahren', 'Anschlüsse'])
workbook.active.append([f"{marker}-Printer", 'Drucker', 'Eingang', 'Laser', 'USB-C'])
import_path = Path(temporary) / f"{marker}.xlsx"
workbook.save(import_path)
page.locator('input[type="file"]').set_input_files(import_path)
page.get_by_role('button', name='Datei analysieren').click()
assert [page.locator(f'[name="column_{i}"]').input_value() for i in range(5)] == [
'model', 'device_type', 'status', 'print_technology', 'ports',
]
page.get_by_role('button', name='Import bestätigen').click()
page.goto(f"{base}/?model_contains={marker}-Printer")
expect(page.locator('tr[data-device-id]')).to_have_count(1)
imported_id = page.locator('tr[data-device-id]').get_attribute('data-device-id')
page.goto(f"{base}/devices/{imported_id}/")
expect(page.locator('[name="print_technology"]')).to_have_value('Laser')
expect(page.locator('[name="ports"]')).to_have_value('USB-C')
print(f"PASS PostgreSQL import/export with printer fields: device {imported_id}", flush=True)
page.goto(f"{base}/")
page.locator('.sidebar-theme').click()
assert page.locator('html').get_attribute('data-theme') == 'dark'
page.reload()
assert page.locator('html').get_attribute('data-theme') == 'dark'
mobile = browser.new_context(ignore_https_errors=True, viewport={'width': 390, 'height': 844})
mobile_page = mobile.new_page()
mobile_page.goto(f"{base}/accounts/login/")
mobile_page.locator('[name="username"]').fill(username)
mobile_page.locator('[name="password"]').fill(password)
mobile_page.locator('form button').click()
mobile_page.locator('[data-sidebar-toggle]').click()
expect(mobile_page.locator('#sidebar')).to_have_class('sidebar open')
mobile.close()
print("PASS dark mode and mobile navigation", flush=True)
assert not errors, f"JavaScript errors: {errors}"
context.close()
browser.close()
if __name__ == '__main__':
run()