Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ services:
- "5672:5672" # AMQP protocol port
- "15672:15672" # Management UI port
- "1883:1883" #MQTT port
post_start:
- command: rabbitmq-plugins enable rabbitmq_mqtt
user: root
environment:
RABBITMQ_DEFAULT_USER: reitti
RABBITMQ_DEFAULT_PASS: reitti
volumes:
- rabbitmq-data:/var/lib/rabbitmq
- ./docker/rabbitmq/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]
interval: 30s
Expand All @@ -50,15 +48,15 @@ services:
interval: 10s
timeout: 5s
retries: 5
photon:
image: rtuszik/photon-docker:1.3.0
environment:
- UPDATE_STRATEGY=PARALLEL
- REGION=de
ports:
- "2322:2322"
volumes:
- photon-data:/photon/data
# photon:
# image: rtuszik/photon-docker:1.3.0
# environment:
# - UPDATE_STRATEGY=PARALLEL
# - REGION=de
# ports:
# - "2322:2322"
# volumes:
# - photon-data:/photon/data
ot-recorder:
image: owntracks/recorder:latest
depends_on:
Expand Down
1 change: 1 addition & 0 deletions docker/rabbitmq/enabled_plugins
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[rabbitmq_management,rabbitmq_mqtt].
1 change: 1 addition & 0 deletions scripts/release-testing/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
Empty file.
24 changes: 24 additions & 0 deletions scripts/release-testing/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[project]
name = "release-testing"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"playwright>=1.62.0",
"python-on-whales>=0.81.0",
]

[project.scripts]
test-suite = "tests:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src"]

# 2. Strips `src/` prefix so files like `src/tests.py` become top-level `tests.py`
[tool.hatch.build.targets.wheel.sources]
"src" = ""
52 changes: 52 additions & 0 deletions scripts/release-testing/src/actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from playwright.sync_api import Page, APIRequestContext
import subprocess

class Actions:
"""Library of reusable UI, API, and DB pre-actions."""

# --- UI ACTIONS ---
@staticmethod
def ui_login(page: Page, email: str = "admin@example.com", password: str = "secret"):
def _action():
print(f"--> [UI] Logging in as {email}...")
page.goto("http://localhost:3000/login")
page.fill("#email", email)
page.fill("#password", password)
page.click("button[type=submit]")
page.wait_for_url("**/dashboard")
return _action

@staticmethod
def ui_create_user(page: Page, username: str, role: str):
def _action():
print(f"--> [UI] Creating user: {username} ({role})...")
page.goto("http://localhost:3000/admin/users/new")
page.fill("input[name='username']", username)
page.select_option("select[name='role']", role)
page.click("button:has-text('Save User')")
page.wait_for_selector(".success-message")
return _action

# --- API ACTIONS (Fast Data Seeding) ---
@staticmethod
def api_create_user(api: APIRequestContext, username: str, role: str):
"""Creates user via API directly — 10x faster than doing it through the UI!"""
def _action():
print(f"--> [API] Fast-creating user: {username}")
res = api.post("http://localhost:3000/api/v1/users", data={
"username": username,
"role": role
})
assert res.ok, f"Failed to create user via API: {res.status_text}"
return _action

# --- DB / DOCKER ACTIONS ---
@staticmethod
def reset_db(sql_dump_file: str):
def _action():
print(f"--> [DB] Restoring clean state from {sql_dump_file}...")
subprocess.run([
"docker", "exec", "-i", "my_postgres_db",
"psql", "-U", "postgres", "-d", "myapp"
], stdin=open(sql_dump_file, "r"), check=True)
return _action
85 changes: 85 additions & 0 deletions scripts/release-testing/src/checkpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from contextlib import contextmanager
from typing import Callable, List, Optional
from playwright.sync_api import Page, BrowserContext

class CheckpointManager:
def __init__(self, context: BrowserContext, page: Page):
self.context = context
self.page = page

def _ensure_page_alive(self) -> Page:
if self.page.is_closed():
print("--> Browser page was closed/disconnected. Creating a fresh page...")
self.page = self.context.new_page()
return self.page

def _print_checklist_box(self, name: str, checklist: Optional[List[str]] = None, error: Optional[Exception] = None):
"""Prints a high-visibility box in the console when execution pauses."""
width = 68
print("\n" + "═" * width)
if error:
print(f" ❌ STEP ERROR: {name}".ljust(width - 1))
print("─" * width)
print(f" Error details: {error}")
else:
print(f" 🔍 MANUAL VERIFICATION REQUIRED: {name}".ljust(width - 1))

print("═" * width)
if checklist:
for item in checklist:
print(f" [ ] {item}")
else:
print(" (No checklist items specified - inspect current browser state)")
print("─" * width)
print(" ⏸️ Execution PAUSED. Resume or step through in Playwright Inspector.")
print("═" * width + "\n")

def _safe_pause(self, page: Page):
"""Pauses execution safely if the page is still open."""
if not page.is_closed():
page.pause()

@contextmanager
def step(
self,
name: str,
target_url: Optional[str] = None,
pre_actions: Optional[List[Callable]] = None,
checklist: Optional[List[str]] = None,
verifier: Optional[Callable[[Page], None]] = None
):
print(f"\n==========================================")
print(f" RUNNING STEP: {name}")
print(f"==========================================")

if pre_actions:
for action in pre_actions:
action()

page = self._ensure_page_alive()

if target_url:
page.goto(target_url)

# 1. CATCH ERRORS OCCURRING INSIDE THE 'with' BLOCK
try:
yield page
except Exception as step_error:
print(f"\n[!] Error during execution of step '{name}'")
self._print_checklist_box(name, checklist, error=step_error)
self._safe_pause(page)
raise step_error # Re-raise error after pausing so runner handles failure

# 2. RUN VERIFIER OR PAUSE FOR MANUAL INSPECTION IF STEP SUCCEEDED
if verifier:
print(f"--> Running auto-verification for [{name}]...")
try:
verifier(page)
print(f"--> [AUTO-VERIFIED] Passed!")
except Exception as verify_error:
print(f"\n[!] AUTO-VERIFICATION FAILED for [{name}]")
self._print_checklist_box(name, checklist, error=verify_error)
self._safe_pause(page)
else:
self._print_checklist_box(name, checklist)
self._safe_pause(page)
Loading
Loading