156 lines
8.3 KiB
Python
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()
|