Add browser coverage and tested inventory fixes
Some checks failed
Tests / postgres (push) Has been cancelled

This commit is contained in:
DenisToxic
2026-10-02 09:30:03 +02:00
parent b4ed13a709
commit 4a3a9aeefb
9 changed files with 569 additions and 3 deletions

View File

@@ -0,0 +1,155 @@
"""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()