Add browser coverage and tested inventory fixes
Some checks failed
Tests / postgres (push) Has been cancelled
Some checks failed
Tests / postgres (push) Has been cancelled
This commit is contained in:
155
scripts/test_server_browser.py
Normal file
155
scripts/test_server_browser.py
Normal 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()
|
||||
Reference in New Issue
Block a user