This commit is contained in:
retoor 2025-11-13 22:53:40 +01:00
parent b25fb89df4
commit 3f80a551d5
18 changed files with 50 additions and 2286 deletions

View File

@ -1,28 +1,15 @@
import pytest import pytest
import pytest_asyncio
from decimal import Decimal from decimal import Decimal
from datetime import date, datetime from datetime import date, datetime
from httpx import AsyncClient from httpx import AsyncClient, ASGITransport
from fastapi import status from fastapi import status
from tortoise.contrib.test import initializer, finalizer
from mywebdav.main import app from mywebdav.main import app
from mywebdav.models import User from mywebdav.models import User
from mywebdav.billing.models import PricingConfig, Invoice, UsageAggregate, UserSubscription from mywebdav.billing.models import PricingConfig, Invoice, UsageAggregate, UserSubscription
from mywebdav.auth import create_access_token from mywebdav.auth import create_access_token
@pytest.fixture(scope="module") @pytest_asyncio.fixture
def event_loop():
import asyncio
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="module", autouse=True)
async def initialize_tests():
initializer(["mywebdav.models", "mywebdav.billing.models"], db_url="sqlite://:memory:")
yield
await finalizer()
@pytest.fixture
async def test_user(): async def test_user():
user = await User.create( user = await User.create(
username="testuser", username="testuser",
@ -34,7 +21,7 @@ async def test_user():
yield user yield user
await user.delete() await user.delete()
@pytest.fixture @pytest_asyncio.fixture
async def admin_user(): async def admin_user():
user = await User.create( user = await User.create(
username="adminuser", username="adminuser",
@ -46,17 +33,17 @@ async def admin_user():
yield user yield user
await user.delete() await user.delete()
@pytest.fixture @pytest_asyncio.fixture
async def auth_token(test_user): async def auth_token(test_user):
token = create_access_token(data={"sub": test_user.username}) token = create_access_token(data={"sub": test_user.username})
return token return token
@pytest.fixture @pytest_asyncio.fixture
async def admin_token(admin_user): async def admin_token(admin_user):
token = create_access_token(data={"sub": admin_user.username}) token = create_access_token(data={"sub": admin_user.username})
return token return token
@pytest.fixture @pytest_asyncio.fixture
async def pricing_config(): async def pricing_config():
configs = [] configs = []
configs.append(await PricingConfig.create( configs.append(await PricingConfig.create(
@ -89,7 +76,7 @@ async def pricing_config():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_current_usage(test_user, auth_token): async def test_get_current_usage(test_user, auth_token):
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get( response = await client.get(
"/api/billing/usage/current", "/api/billing/usage/current",
headers={"Authorization": f"Bearer {auth_token}"} headers={"Authorization": f"Bearer {auth_token}"}
@ -113,7 +100,7 @@ async def test_get_monthly_usage(test_user, auth_token):
bandwidth_down_bytes=1024 ** 3 * 5 bandwidth_down_bytes=1024 ** 3 * 5
) )
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get( response = await client.get(
f"/api/billing/usage/monthly?year={today.year}&month={today.month}", f"/api/billing/usage/monthly?year={today.year}&month={today.month}",
headers={"Authorization": f"Bearer {auth_token}"} headers={"Authorization": f"Bearer {auth_token}"}
@ -127,7 +114,7 @@ async def test_get_monthly_usage(test_user, auth_token):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_subscription(test_user, auth_token): async def test_get_subscription(test_user, auth_token):
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get( response = await client.get(
"/api/billing/subscription", "/api/billing/subscription",
headers={"Authorization": f"Bearer {auth_token}"} headers={"Authorization": f"Bearer {auth_token}"}
@ -153,7 +140,7 @@ async def test_list_invoices(test_user, auth_token):
status="open" status="open"
) )
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get( response = await client.get(
"/api/billing/invoices", "/api/billing/invoices",
headers={"Authorization": f"Bearer {auth_token}"} headers={"Authorization": f"Bearer {auth_token}"}
@ -179,7 +166,7 @@ async def test_get_invoice(test_user, auth_token):
status="open" status="open"
) )
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get( response = await client.get(
f"/api/billing/invoices/{invoice.id}", f"/api/billing/invoices/{invoice.id}",
headers={"Authorization": f"Bearer {auth_token}"} headers={"Authorization": f"Bearer {auth_token}"}
@ -193,7 +180,7 @@ async def test_get_invoice(test_user, auth_token):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_pricing(): async def test_get_pricing():
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get("/api/billing/pricing") response = await client.get("/api/billing/pricing")
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
@ -202,7 +189,7 @@ async def test_get_pricing():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_admin_get_pricing(admin_user, admin_token, pricing_config): async def test_admin_get_pricing(admin_user, admin_token, pricing_config):
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get( response = await client.get(
"/api/admin/billing/pricing", "/api/admin/billing/pricing",
headers={"Authorization": f"Bearer {admin_token}"} headers={"Authorization": f"Bearer {admin_token}"}
@ -216,7 +203,7 @@ async def test_admin_get_pricing(admin_user, admin_token, pricing_config):
async def test_admin_update_pricing(admin_user, admin_token, pricing_config): async def test_admin_update_pricing(admin_user, admin_token, pricing_config):
config_id = pricing_config[0].id config_id = pricing_config[0].id
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.put( response = await client.put(
f"/api/admin/billing/pricing/{config_id}", f"/api/admin/billing/pricing/{config_id}",
headers={"Authorization": f"Bearer {admin_token}"}, headers={"Authorization": f"Bearer {admin_token}"},
@ -233,7 +220,7 @@ async def test_admin_update_pricing(admin_user, admin_token, pricing_config):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_admin_get_stats(admin_user, admin_token): async def test_admin_get_stats(admin_user, admin_token):
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get( response = await client.get(
"/api/admin/billing/stats", "/api/admin/billing/stats",
headers={"Authorization": f"Bearer {admin_token}"} headers={"Authorization": f"Bearer {admin_token}"}
@ -247,7 +234,7 @@ async def test_admin_get_stats(admin_user, admin_token):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_non_admin_cannot_access_admin_endpoints(test_user, auth_token): async def test_non_admin_cannot_access_admin_endpoints(test_user, auth_token):
async with AsyncClient(app=app, base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get( response = await client.get(
"/api/admin/billing/pricing", "/api/admin/billing/pricing",
headers={"Authorization": f"Bearer {auth_token}"} headers={"Authorization": f"Bearer {auth_token}"}

View File

@ -1,25 +1,12 @@
import pytest import pytest
import pytest_asyncio
from decimal import Decimal from decimal import Decimal
from datetime import date, datetime from datetime import date, datetime
from tortoise.contrib.test import initializer, finalizer
from mywebdav.models import User from mywebdav.models import User
from mywebdav.billing.models import Invoice, InvoiceLineItem, PricingConfig, UsageAggregate, UserSubscription from mywebdav.billing.models import Invoice, InvoiceLineItem, PricingConfig, UsageAggregate, UserSubscription
from mywebdav.billing.invoice_generator import InvoiceGenerator from mywebdav.billing.invoice_generator import InvoiceGenerator
@pytest.fixture(scope="module") @pytest_asyncio.fixture
def event_loop():
import asyncio
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="module", autouse=True)
async def initialize_tests():
initializer(["mywebdav.models", "mywebdav.billing.models"], db_url="sqlite://:memory:")
yield
await finalizer()
@pytest.fixture
async def test_user(): async def test_user():
user = await User.create( user = await User.create(
username="testuser", username="testuser",
@ -30,7 +17,7 @@ async def test_user():
yield user yield user
await user.delete() await user.delete()
@pytest.fixture @pytest_asyncio.fixture
async def pricing_config(): async def pricing_config():
configs = [] configs = []
configs.append(await PricingConfig.create( configs.append(await PricingConfig.create(
@ -170,8 +157,14 @@ async def test_mark_invoice_paid(test_user, pricing_config):
await paid.delete() await paid.delete()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_invoice_with_tax(test_user): async def test_invoice_with_tax(test_user, pricing_config):
await PricingConfig.filter(config_key="tax_rate_default").update(config_value=Decimal("0.21")) # Update tax rate
updated = await PricingConfig.filter(config_key="tax_rate_default").update(config_value=Decimal("0.21"))
assert updated == 1 # Should update 1 row
# Verify the update worked
tax_config = await PricingConfig.get(config_key="tax_rate_default")
assert tax_config.config_value == Decimal("0.21")
today = date.today() today = date.today()

View File

@ -2,26 +2,12 @@ import pytest
import pytest_asyncio import pytest_asyncio
from decimal import Decimal from decimal import Decimal
from datetime import date, datetime from datetime import date, datetime
from tortoise.contrib.test import initializer, finalizer
from mywebdav.models import User from mywebdav.models import User
from mywebdav.billing.models import ( from mywebdav.billing.models import (
SubscriptionPlan, UserSubscription, UsageRecord, UsageAggregate, SubscriptionPlan, UserSubscription, UsageRecord, UsageAggregate,
Invoice, InvoiceLineItem, PricingConfig, PaymentMethod, BillingEvent Invoice, InvoiceLineItem, PricingConfig, PaymentMethod, BillingEvent
) )
@pytest.fixture(scope="module")
def event_loop():
import asyncio
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest_asyncio.fixture(scope="module", autouse=True)
async def initialize_tests():
initializer(["mywebdav.models", "mywebdav.billing.models"], db_url="sqlite://:memory:")
yield
await finalizer()
@pytest_asyncio.fixture @pytest_asyncio.fixture
async def test_user(): async def test_user():
user = await User.create( user = await User.create(

View File

@ -1,25 +1,12 @@
import pytest import pytest
import pytest_asyncio
from decimal import Decimal from decimal import Decimal
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from tortoise.contrib.test import initializer, finalizer
from mywebdav.models import User, File, Folder from mywebdav.models import User, File, Folder
from mywebdav.billing.models import UsageRecord, UsageAggregate from mywebdav.billing.models import UsageRecord, UsageAggregate
from mywebdav.billing.usage_tracker import UsageTracker from mywebdav.billing.usage_tracker import UsageTracker
@pytest.fixture(scope="module") @pytest_asyncio.fixture
def event_loop():
import asyncio
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="module", autouse=True)
async def initialize_tests():
initializer(["mywebdav.models", "mywebdav.billing.models"], db_url="sqlite://:memory:")
yield
await finalizer()
@pytest.fixture
async def test_user(): async def test_user():
user = await User.create( user = await User.create(
username="testuser", username="testuser",

View File

@ -1,8 +1,28 @@
import pytest import pytest
import asyncio import asyncio
from tortoise import Tortoise
# Initialize Tortoise at module level
async def _init_tortoise():
await Tortoise.init(
db_url="sqlite://:memory:",
modules={
"models": ["mywebdav.models"],
"billing": ["mywebdav.billing.models"]
}
)
await Tortoise.generate_schemas()
# Run initialization
asyncio.run(_init_tortoise())
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def event_loop(): def event_loop():
loop = asyncio.get_event_loop_policy().new_event_loop() loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop yield loop
loop.close() loop.close()
@pytest.fixture(scope="session", autouse=True)
async def cleanup_db():
yield
await Tortoise.close_connections()

View File

View File

@ -1,67 +0,0 @@
import pytest
import pytest_asyncio
import asyncio
from playwright.async_api import async_playwright, Page, expect
@pytest.fixture(scope="session")
def event_loop():
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest_asyncio.fixture(scope="function")
async def browser():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=False, slow_mo=500)
yield browser
await browser.close()
@pytest_asyncio.fixture(scope="function")
async def context(browser):
context = await browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent="Mozilla/5.0 (X11; Linux x86_64) MyWebdav E2E Tests",
ignore_https_errors=True,
service_workers='block'
)
yield context
await context.close()
@pytest_asyncio.fixture(scope="function")
async def page(context):
page = await context.new_page()
yield page
await page.close()
@pytest.fixture
def base_url():
return "http://localhost:9004"
@pytest_asyncio.fixture(scope="function", autouse=True)
async def login(page: Page, base_url):
print(f"Navigating to base_url: {base_url}")
await page.goto(f"{base_url}/")
await page.screenshot(path="01_initial_page.png")
# If already logged in, log out first to ensure a clean state
if await page.locator('a:has-text("Logout")').is_visible():
await page.click('a:has-text("Logout")')
await page.screenshot(path="02_after_logout.png")
# Now, proceed with login or registration
login_form = page.locator('#login-form:visible')
if await login_form.count() > 0:
await login_form.locator('input[name="username"]').fill('billingtest')
await login_form.locator('input[name="password"]').fill('password123')
await page.screenshot(path="03_before_login_click.png")
await expect(page.locator('h2:has-text("Files")')).to_be_visible(timeout=10000)
else:
# If no login form, try to register
await page.click('text=Sign Up')
register_form = page.locator('#register-form:visible')
await register_form.locator('input[name="username"]').fill('billingtest')
await register_form.locator('input[name="email"]').fill('billingtest@example.com')
await register_form.locator('input[name="password"]').fill('password123')
await page.screenshot(path="05_before_register_click.png")
await register_form.locator('button[type="submit"]').click()
await expect(page.locator('h1:has-text("My Files")')).to_be_visible(timeout=10000)

View File

@ -1,181 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect
@pytest.mark.asyncio
class TestAdmin:
async def test_01_admin_login(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Admin should see admin nav links
await expect(page.locator('a.nav-link[data-view="admin"]')).to_be_visible()
await expect(page.locator('a.nav-link[data-view="admin-billing"]')).to_be_visible()
async def test_02_navigate_to_admin_dashboard(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="admin"]')
await page.wait_for_timeout(1000)
await expect(page.locator('admin-dashboard')).to_be_visible()
async def test_03_view_user_management(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="admin"]')
await page.wait_for_timeout(1000)
# Should show user list
await expect(page.locator('.user-list')).to_be_visible()
await expect(page.locator('text=e2etestuser')).to_be_visible()
async def test_04_view_system_statistics(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="admin"]')
await page.wait_for_timeout(1000)
# Should show system stats
await expect(page.locator('.system-stats')).to_be_visible()
await expect(page.locator('.stat-item')).to_be_visible()
async def test_05_manage_user_permissions(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="admin"]')
await page.wait_for_timeout(1000)
# Click on user to manage
await page.click('.user-item:has-text("e2etestuser")')
await page.wait_for_timeout(500)
# Should show user details modal
await expect(page.locator('.user-details-modal')).to_be_visible()
# Change permissions
await page.select_option('select[name="role"]', 'moderator')
await page.click('button:has-text("Save")')
await page.wait_for_timeout(1000)
async def test_06_view_storage_usage(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="admin"]')
await page.wait_for_timeout(1000)
# Should show storage usage
await expect(page.locator('.storage-usage')).to_be_visible()
async def test_07_navigate_to_admin_billing(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="admin-billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('admin-billing')).to_be_visible()
async def test_08_view_billing_statistics(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="admin-billing"]')
await page.wait_for_timeout(1000)
# Should show billing stats
await expect(page.locator('.billing-stats')).to_be_visible()
async def test_09_manage_pricing(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="admin-billing"]')
await page.wait_for_timeout(1000)
# Click pricing management
await page.click('button:has-text("Manage Pricing")')
await page.wait_for_timeout(500)
# Should show pricing form
await expect(page.locator('.pricing-form')).to_be_visible()
# Update a price
await page.fill('input[name="storage_per_gb_month"]', '0.005')
await page.click('button:has-text("Update")')
await page.wait_for_timeout(1000)
async def test_10_generate_invoice(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'adminuser')
await page.fill('#login-form input[name="password"]', 'adminpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="admin-billing"]')
await page.wait_for_timeout(1000)
# Click generate invoice
await page.click('button:has-text("Generate Invoice")')
await page.wait_for_timeout(500)
# Select user
await page.select_option('select[name="user"]', 'e2etestuser')
await page.click('button:has-text("Generate")')
await page.wait_for_timeout(1000)
# Should show success message
await expect(page.locator('text=Invoice generated')).to_be_visible()

View File

@ -1,170 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect
@pytest.mark.asyncio
class TestAuthFlow:
async def test_01_user_registration(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
# Click Sign Up
await page.click('text=Sign Up')
await page.wait_for_timeout(500)
# Fill registration form
await page.fill('#register-form input[name="username"]', 'e2etestuser')
await page.fill('#register-form input[name="email"]', 'e2etestuser@example.com')
await page.fill('#register-form input[name="password"]', 'testpassword123')
# Submit registration
await page.click('#register-form button[type="submit"]')
await page.wait_for_timeout(2000)
await expect(page.locator('text=My Files')).to_be_visible()
async def test_02_user_login(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
# Unregister service worker to avoid interference
await page.evaluate("""
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for(let registration of registrations) {
registration.unregister();
}
});
""")
await page.reload()
await page.wait_for_load_state("networkidle")
# Fill login form
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
# Submit login
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await expect(page.locator('text=My Files')).to_be_visible()
async def test_03_navigate_to_files_view(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Files view should be active by default
await expect(page.locator('a.nav-link.active[data-view="files"]')).to_be_visible()
await expect(page.locator('file-list')).to_be_visible()
async def test_04_navigate_to_photos_view(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
await expect(page.locator('a.nav-link.active[data-view="photos"]')).to_be_visible()
await expect(page.locator('photo-gallery')).to_be_visible()
async def test_05_navigate_to_shared_view(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="shared"]')
await page.wait_for_timeout(1000)
await expect(page.locator('a.nav-link.active[data-view="shared"]')).to_be_visible()
await expect(page.locator('shared-items')).to_be_visible()
async def test_06_navigate_to_starred_view(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="starred"]')
await page.wait_for_timeout(1000)
await expect(page.locator('a.nav-link.active[data-view="starred"]')).to_be_visible()
await expect(page.locator('starred-items')).to_be_visible()
async def test_07_navigate_to_recent_view(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="recent"]')
await page.wait_for_timeout(1000)
await expect(page.locator('a.nav-link.active[data-view="recent"]')).to_be_visible()
await expect(page.locator('recent-files')).to_be_visible()
async def test_08_navigate_to_deleted_view(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="deleted"]')
await page.wait_for_timeout(1000)
await expect(page.locator('a.nav-link.active[data-view="deleted"]')).to_be_visible()
await expect(page.locator('deleted-files')).to_be_visible()
async def test_09_navigate_to_billing_view(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('a.nav-link.active[data-view="billing"]')).to_be_visible()
await expect(page.locator('billing-dashboard')).to_be_visible()
async def test_10_user_logout(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Click logout
await page.click('#logout-btn')
await page.wait_for_timeout(1000)
# Should be back to login
await expect(page.locator('#login-form')).to_be_visible()

View File

@ -1,199 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect
@pytest.mark.asyncio
class TestBillingAdminFlow:
async def test_01_admin_login(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('input[name="username"]', 'adminuser')
await page.fill('input[name="password"]', 'adminpassword123')
await page.click('button[type="submit"]')
await page.wait_for_load_state("networkidle")
await expect(page.locator('text=Dashboard')).to_be_visible()
async def test_02_navigate_to_admin_billing(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('input[name="username"]', 'adminuser')
await page.fill('input[name="password"]', 'adminpassword123')
await page.click('button[type="submit"]')
await page.wait_for_load_state("networkidle")
await page.click('text=Admin')
await page.wait_for_load_state("networkidle")
await page.click('text=Billing')
await page.wait_for_url("**/admin/billing")
await expect(page.locator('h2:has-text("Billing Administration")')).to_be_visible()
async def test_03_view_revenue_statistics(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.stats-cards')).to_be_visible()
await expect(page.locator('.stat-card:has-text("Total Revenue")')).to_be_visible()
await expect(page.locator('.stat-card:has-text("Total Invoices")')).to_be_visible()
await expect(page.locator('.stat-card:has-text("Pending Invoices")')).to_be_visible()
async def test_04_verify_revenue_value_display(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
revenue_stat = page.locator('.stat-card:has-text("Total Revenue") .stat-value')
await expect(revenue_stat).to_be_visible()
revenue_text = await revenue_stat.text_content()
assert '$' in revenue_text or '0' in revenue_text
async def test_05_view_pricing_configuration_table(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.pricing-config-section')).to_be_visible()
await expect(page.locator('h3:has-text("Pricing Configuration")')).to_be_visible()
await expect(page.locator('.pricing-table')).to_be_visible()
async def test_06_verify_pricing_config_rows(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.pricing-table tbody tr')).to_have_count(6, timeout=5000)
await expect(page.locator('td:has-text("Storage cost per GB per month")')).to_be_visible()
await expect(page.locator('td:has-text("Bandwidth egress cost per GB")')).to_be_visible()
await expect(page.locator('td:has-text("Free tier storage")')).to_be_visible()
async def test_07_click_edit_pricing_button(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
edit_buttons = page.locator('.btn-edit')
await expect(edit_buttons.first).to_be_visible()
page.on('dialog', lambda dialog: dialog.dismiss())
await edit_buttons.first.click()
await page.wait_for_timeout(1000)
async def test_08_edit_pricing_value(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
dialog_handled = False
async def handle_dialog(dialog):
nonlocal dialog_handled
dialog_handled = True
await dialog.accept(text="0.005")
page.on('dialog', handle_dialog)
edit_button = page.locator('.btn-edit').first
await edit_button.click()
await page.wait_for_timeout(1000)
if dialog_handled:
await page.wait_for_timeout(2000)
async def test_09_view_invoice_generation_section(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.invoice-generation-section')).to_be_visible()
await expect(page.locator('h3:has-text("Generate Invoices")')).to_be_visible()
async def test_10_verify_invoice_generation_form(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('#invoiceYear')).to_be_visible()
await expect(page.locator('#invoiceMonth')).to_be_visible()
await expect(page.locator('#generateInvoices')).to_be_visible()
async def test_11_set_invoice_generation_date(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await page.fill('#invoiceYear', '2024')
await page.fill('#invoiceMonth', '11')
year_value = await page.input_value('#invoiceYear')
month_value = await page.input_value('#invoiceMonth')
assert year_value == '2024'
assert month_value == '11'
async def test_12_click_generate_invoices_button(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await page.fill('#invoiceYear', '2024')
await page.fill('#invoiceMonth', '10')
page.on('dialog', lambda dialog: dialog.dismiss())
await page.click('#generateInvoices')
await page.wait_for_timeout(1000)
async def test_13_verify_all_stat_cards_present(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
stat_cards = page.locator('.stat-card')
await expect(stat_cards).to_have_count(3)
async def test_14_verify_pricing_table_headers(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('th:has-text("Configuration")')).to_be_visible()
await expect(page.locator('th:has-text("Current Value")')).to_be_visible()
await expect(page.locator('th:has-text("Unit")')).to_be_visible()
await expect(page.locator('th:has-text("Actions")')).to_be_visible()
async def test_15_verify_all_edit_buttons_present(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
edit_buttons = page.locator('.btn-edit')
count = await edit_buttons.count()
assert count == 6
async def test_16_scroll_through_admin_dashboard(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await page.evaluate("window.scrollTo(0, 0)")
await page.wait_for_timeout(500)
await page.evaluate("window.scrollTo(0, document.body.scrollHeight / 2)")
await page.wait_for_timeout(500)
await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
await page.wait_for_timeout(500)
await page.evaluate("window.scrollTo(0, 0)")
await page.wait_for_timeout(500)
async def test_17_verify_responsive_layout(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.admin-billing')).to_be_visible()
bounding_box = await page.locator('.admin-billing').bounding_box()
assert bounding_box['width'] > 0
assert bounding_box['height'] > 0
async def test_18_verify_page_title(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
title = await page.title()
assert title is not None

View File

@ -1,183 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect, Page
@pytest.mark.asyncio
class TestBillingAPIFlow:
async def test_01_api_get_current_usage(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.get(
f"{base_url}/api/billing/usage/current",
headers={"Authorization": f"Bearer {token}"}
)
assert response.ok
data = await response.json()
assert 'storage_gb' in data
assert 'bandwidth_down_gb_today' in data
assert 'as_of' in data
async def test_02_api_get_monthly_usage(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.get(
f"{base_url}/api/billing/usage/monthly",
headers={"Authorization": f"Bearer {token}"}
)
assert response.ok
data = await response.json()
assert 'storage_gb_avg' in data
assert 'bandwidth_down_gb' in data
assert 'period' in data
async def test_03_api_get_subscription(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.get(
f"{base_url}/api/billing/subscription",
headers={"Authorization": f"Bearer {token}"}
)
assert response.ok
data = await response.json()
assert 'billing_type' in data
assert 'status' in data
assert data['billing_type'] == 'pay_as_you_go'
async def test_04_api_list_invoices(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.get(
f"{base_url}/api/billing/invoices",
headers={"Authorization": f"Bearer {token}"}
)
assert response.ok
data = await response.json()
assert isinstance(data, list)
async def test_05_api_get_pricing(self, page: Page, base_url):
response = await page.request.get(f"{base_url}/api/billing/pricing")
assert response.ok
data = await response.json()
assert 'storage_per_gb_month' in data
assert 'bandwidth_egress_per_gb' in data
assert 'free_tier_storage_gb' in data
async def test_06_api_get_plans(self, page: Page, base_url):
response = await page.request.get(f"{base_url}/api/billing/plans")
assert response.ok
data = await response.json()
assert isinstance(data, list)
async def test_07_api_admin_get_pricing_config(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'adminuser', 'adminpassword123')
response = await page.request.get(
f"{base_url}/api/admin/billing/pricing",
headers={"Authorization": f"Bearer {token}"}
)
assert response.ok
data = await response.json()
assert isinstance(data, list)
assert len(data) >= 6
async def test_08_api_admin_get_stats(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'adminuser', 'adminpassword123')
response = await page.request.get(
f"{base_url}/api/admin/billing/stats",
headers={"Authorization": f"Bearer {token}"}
)
assert response.ok
data = await response.json()
assert 'total_revenue' in data
assert 'total_invoices' in data
assert 'pending_invoices' in data
async def test_09_api_user_cannot_access_admin_endpoints(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.get(
f"{base_url}/api/admin/billing/pricing",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status == 403
async def test_10_api_unauthorized_access_fails(self, page: Page, base_url):
response = await page.request.get(f"{base_url}/api/billing/usage/current")
assert response.status == 401
async def test_11_api_create_payment_setup_intent(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.post(
f"{base_url}/api/billing/payment-methods/setup-intent",
headers={"Authorization": f"Bearer {token}"}
)
assert response.ok or response.status == 500
async def test_12_api_get_payment_methods(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.get(
f"{base_url}/api/billing/payment-methods",
headers={"Authorization": f"Bearer {token}"}
)
assert response.ok
data = await response.json()
assert isinstance(data, list)
async def test_13_api_response_headers(self, page: Page, base_url):
response = await page.request.get(f"{base_url}/api/billing/pricing")
assert response.ok
headers = response.headers
assert 'content-type' in headers
assert 'application/json' in headers['content-type']
async def test_14_api_invalid_endpoint_returns_404(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.get(
f"{base_url}/api/billing/nonexistent",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status == 404
async def test_15_api_request_with_params(self, page: Page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.get(
f"{base_url}/api/billing/usage/monthly?year=2024&month=11",
headers={"Authorization": f"Bearer {token}"}
)
assert response.ok
data = await response.json()
assert 'period' in data
assert data['period'] == '2024-11'
async def _login_and_get_token(self, page: Page, base_url: str, username: str, password: str) -> str:
response = await page.request.post(
f"{base_url}/api/auth/login",
data={"username": username, "password": password}
)
if response.ok:
data = await response.json()
return data.get('access_token', '')
return ''

View File

@ -1,264 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect
from datetime import datetime, date
from mywebdav.billing.models import UsageAggregate, Invoice
from mywebdav.models import User
@pytest.mark.asyncio
class TestBillingIntegrationFlow:
async def test_01_complete_user_journey(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('input[name="username"]', 'testuser')
await page.fill('input[name="password"]', 'testpassword123')
await page.click('button[type="submit"]')
await page.wait_for_load_state("networkidle")
await page.click('text=Files')
await page.wait_for_load_state("networkidle")
await page.wait_for_timeout(1000)
await page.click('text=Billing')
await page.wait_for_load_state("networkidle")
await expect(page.locator('.billing-dashboard')).to_be_visible()
await page.wait_for_timeout(2000)
async def test_02_verify_usage_tracking_after_operations(self, page, base_url):
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
initial_storage = await page.locator('.usage-value').first.text_content()
await page.goto(f"{base_url}/files")
await page.wait_for_load_state("networkidle")
await page.wait_for_timeout(1000)
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
current_storage = await page.locator('.usage-value').first.text_content()
assert current_storage is not None
async def test_03_verify_cost_calculation_updates(self, page, base_url):
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.estimated-cost')).to_be_visible()
cost_text = await page.locator('.estimated-cost').text_content()
assert '$' in cost_text
await page.reload()
await page.wait_for_load_state("networkidle")
new_cost_text = await page.locator('.estimated-cost').text_content()
assert '$' in new_cost_text
async def test_04_verify_subscription_consistency(self, page, base_url):
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
subscription_badge = page.locator('.subscription-badge')
await expect(subscription_badge).to_be_visible()
badge_classes = await subscription_badge.get_attribute('class')
assert 'active' in badge_classes or 'inactive' in badge_classes
async def test_05_verify_pricing_consistency_across_pages(self, page, base_url):
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
storage_price_user = await page.locator('.pricing-item:has-text("Storage")').last.text_content()
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
config_value = await page.locator('.pricing-table tbody tr').first.locator('.config-value').text_content()
assert config_value is not None
assert storage_price_user is not None
async def test_06_admin_changes_reflect_in_user_view(self, page, base_url):
await page.goto(f"{base_url}/admin/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.pricing-table')).to_be_visible()
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.pricing-card')).to_be_visible()
async def test_07_navigation_flow_consistency(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.click('text=Billing')
await page.wait_for_url("**/billing")
await page.click('text=Dashboard')
await page.wait_for_url("**/dashboard")
await page.click('text=Billing')
await page.wait_for_url("**/billing")
await expect(page.locator('.billing-dashboard')).to_be_visible()
async def test_08_refresh_maintains_state(self, page, base_url):
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
usage_before = await page.locator('.usage-value').first.text_content()
await page.reload()
await page.wait_for_load_state("networkidle")
usage_after = await page.locator('.usage-value').first.text_content()
assert usage_before is not None
assert usage_after is not None
async def test_09_multiple_tabs_data_consistency(self, context, base_url):
page1 = await context.new_page()
page2 = await context.new_page()
await page1.goto(f"{base_url}/billing")
await page1.wait_for_load_state("networkidle")
await page2.goto(f"{base_url}/billing")
await page2.wait_for_load_state("networkidle")
usage1 = await page1.locator('.usage-value').first.text_content()
usage2 = await page2.locator('.usage-value').first.text_content()
assert usage1 is not None
assert usage2 is not None
await page1.close()
await page2.close()
async def test_10_api_and_ui_data_consistency(self, page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
api_response = await page.request.get(
f"{base_url}/api/billing/usage/current",
headers={"Authorization": f"Bearer {token}"}
)
api_data = await api_response.json()
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.usage-card')).to_be_visible()
assert api_data['storage_gb'] >= 0
async def test_11_error_handling_invalid_invoice_id(self, page, base_url):
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
response = await page.request.get(
f"{base_url}/api/billing/invoices/99999",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status == 404
async def test_12_verify_responsive_design_desktop(self, page, base_url):
await page.set_viewport_size({"width": 1920, "height": 1080})
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.billing-cards')).to_be_visible()
cards = page.locator('.billing-card')
count = await cards.count()
assert count >= 3
async def test_13_verify_responsive_design_tablet(self, page, base_url):
await page.set_viewport_size({"width": 768, "height": 1024})
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.billing-dashboard')).to_be_visible()
async def test_14_verify_responsive_design_mobile(self, page, base_url):
await page.set_viewport_size({"width": 375, "height": 667})
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.billing-dashboard')).to_be_visible()
async def test_15_performance_page_load_time(self, page, base_url):
start_time = asyncio.get_event_loop().time()
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
end_time = asyncio.get_event_loop().time()
load_time = end_time - start_time
assert load_time < 5.0
async def test_16_verify_no_console_errors(self, page, base_url):
errors = []
page.on("console", lambda msg: errors.append(msg) if msg.type == "error" else None)
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
await page.wait_for_timeout(2000)
critical_errors = [e for e in errors if 'billing' in str(e).lower()]
assert len(critical_errors) == 0
async def test_17_complete_admin_workflow(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('input[name="username"]', 'adminuser')
await page.fill('input[name="password"]', 'adminpassword123')
await page.click('button[type="submit"]')
await page.wait_for_load_state("networkidle")
await page.click('text=Admin')
await page.wait_for_load_state("networkidle")
await page.click('text=Billing')
await page.wait_for_load_state("networkidle")
await expect(page.locator('.admin-billing')).to_be_visible()
await expect(page.locator('.stats-cards')).to_be_visible()
await expect(page.locator('.pricing-config-section')).to_be_visible()
await expect(page.locator('.invoice-generation-section')).to_be_visible()
await page.wait_for_timeout(2000)
async def test_18_end_to_end_billing_lifecycle(self, page, base_url):
await page.goto(f"{base_url}/billing")
await page.wait_for_load_state("networkidle")
await expect(page.locator('.billing-dashboard')).to_be_visible()
await expect(page.locator('.usage-card')).to_be_visible()
await expect(page.locator('.cost-card')).to_be_visible()
await expect(page.locator('.pricing-card')).to_be_visible()
await expect(page.locator('.invoices-section')).to_be_visible()
await expect(page.locator('.payment-methods-section')).to_be_visible()
await page.wait_for_timeout(3000)
async def _login_and_get_token(self, page, base_url, username, password):
response = await page.request.post(
f"{base_url}/api/auth/login",
data={"username": username, "password": password}
)
if response.ok:
data = await response.json()
return data.get('access_token', '')
return ''

View File

@ -1,264 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect
@pytest.mark.asyncio
class TestBillingUserFlow:
async def test_01_user_registration(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.click('text=Sign Up')
await page.wait_for_timeout(500)
await page.fill('#register-form input[name="username"]', 'billingtest')
await page.fill('#register-form input[name="email"]', 'billingtest@example.com')
await page.fill('#register-form input[name="password"]', 'password123')
await page.click('#register-form button[type="submit"]')
await page.wait_for_timeout(2000)
await expect(page.locator('text=My Files')).to_be_visible()
async def test_02_user_login(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.evaluate("""
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for(let registration of registrations) {
registration.unregister();
}
});
""")
await page.reload()
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await expect(page.locator('text=My Files')).to_be_visible()
async def test_03_navigate_to_billing_dashboard(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('billing-dashboard')).to_be_visible()
async def test_04_view_current_usage(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('.usage-card')).to_be_visible()
await expect(page.locator('text=Current Usage')).to_be_visible()
await expect(page.locator('.usage-label:has-text("Storage")')).to_be_visible()
await expect(page.locator('.usage-label:has-text("Bandwidth")')).to_be_visible()
async def test_05_view_estimated_cost(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('.cost-card')).to_be_visible()
await expect(page.locator('text=Estimated Monthly Cost')).to_be_visible()
await expect(page.locator('.estimated-cost')).to_be_visible()
cost_text = await page.locator('.estimated-cost').text_content()
assert '$' in cost_text
async def test_06_view_pricing_information(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('.pricing-card')).to_be_visible()
await expect(page.locator('text=Current Pricing')).to_be_visible()
await expect(page.locator('.pricing-item:has-text("Storage")')).to_be_visible()
await expect(page.locator('.pricing-item:has-text("Bandwidth")')).to_be_visible()
await expect(page.locator('.pricing-item:has-text("Free Tier")')).to_be_visible()
async def test_07_view_invoice_history_empty(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('.invoices-section')).to_be_visible()
await expect(page.locator('text=Recent Invoices')).to_be_visible()
no_invoices = page.locator('.no-invoices')
if await no_invoices.is_visible():
await expect(no_invoices).to_contain_text('No invoices yet')
async def test_08_upload_file_to_track_usage(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.set_input_files('input[type="file"]', {
'name': 'test-file.txt',
'mimeType': 'text/plain',
'buffer': b'This is a test file for billing usage tracking.'
})
await page.click('button:has-text("Upload")')
await page.wait_for_timeout(2000)
await expect(page.locator('text=test-file.txt')).to_be_visible()
async def test_09_verify_usage_updated_after_upload(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(2000)
storage_value = page.locator('.usage-item:has(.usage-label:has-text("Storage")) .usage-value')
await expect(storage_value).to_be_visible()
async def test_10_add_payment_method_button(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('.payment-methods-section')).to_be_visible()
await expect(page.locator('text=Payment Methods')).to_be_visible()
await expect(page.locator('#addPaymentMethod')).to_be_visible()
async def test_11_click_add_payment_method(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
page.on('dialog', lambda dialog: dialog.accept())
await page.click('#addPaymentMethod')
await page.wait_for_timeout(1000)
async def test_12_view_subscription_status(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('.subscription-badge')).to_be_visible()
badge_text = await page.locator('.subscription-badge').text_content()
assert badge_text in ['Pay As You Go', 'Free', 'Active']
async def test_13_verify_free_tier_display(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('.usage-info:has-text("GB included free")')).to_be_visible()
free_tier_info = await page.locator('.usage-info').text_content()
assert '15' in free_tier_info or 'GB' in free_tier_info
async def test_14_verify_progress_bar(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('.usage-progress')).to_be_visible()
await expect(page.locator('.usage-progress-bar')).to_be_visible()
async def test_15_verify_cost_breakdown(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="billing"]')
await page.wait_for_timeout(1000)
await expect(page.locator('.cost-breakdown')).to_be_visible()
await expect(page.locator('.cost-item:has-text("Storage")')).to_be_visible()
await expect(page.locator('.cost-item:has-text("Bandwidth")')).to_be_visible()

View File

@ -1,127 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect, Page
@pytest.mark.asyncio
class TestBillingUserFlowRefactored:
async def test_navigate_to_billing_dashboard(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('billing-dashboard')).to_be_visible()
async def test_view_current_usage(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('.usage-card')).to_be_visible()
await expect(page.locator('text=Current Usage')).to_be_visible()
await expect(page.locator('.usage-label:has-text("Storage")')).to_be_visible()
await expect(page.locator('.usage-label:has-text("Bandwidth")')).to_be_visible()
async def test_view_estimated_cost(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('.cost-card')).to_be_visible()
await expect(page.locator('text=Estimated Monthly Cost')).to_be_visible()
await expect(page.locator('.estimated-cost')).to_be_visible()
cost_text = await page.locator('.estimated-cost').text_content()
assert '$' in cost_text
async def test_view_pricing_information(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('.pricing-card')).to_be_visible()
await expect(page.locator('text=Current Pricing')).to_be_visible()
await expect(page.locator('.pricing-item:has-text("Storage")')).to_be_visible()
await expect(page.locator('.pricing-item:has-text("Bandwidth")')).to_be_visible()
await expect(page.locator('.pricing-item:has-text("Free Tier")')).to_be_visible()
async def test_view_invoice_history_empty(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('.invoices-section')).to_be_visible()
await expect(page.locator('text=Recent Invoices')).to_be_visible()
no_invoices = page.locator('.no-invoices')
if await no_invoices.is_visible():
await expect(no_invoices).to_contain_text('No invoices yet')
async def test_upload_file_to_track_usage(self, page: Page):
# Navigate back to files view
await page.click('a.nav-link[data-view="files"]')
await expect(page.locator('h2:has-text("Files")')).to_be_visible(timeout=5000)
await page.set_input_files('input[type="file"]', {
'name': 'test-file.txt',
'mimeType': 'text/plain',
'buffer': b'This is a test file for billing usage tracking.'
})
await page.click('button:has-text("Upload")')
await expect(page.locator('text=test-file.txt')).to_be_visible(timeout=10000)
async def test_verify_usage_updated_after_upload(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
storage_value_locator = page.locator('.usage-item:has(.usage-label:has-text("Storage")) .usage-value')
await expect(storage_value_locator).not_to_contain_text("0 B", timeout=10000)
async def test_add_payment_method_button(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('.payment-methods-section')).to_be_visible()
await expect(page.locator('text=Payment Methods')).to_be_visible()
await expect(page.locator('#addPaymentMethod')).to_be_visible()
async def test_click_add_payment_method(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
page.on('dialog', lambda dialog: dialog.accept())
await page.click('#addPaymentMethod')
# We can't assert much here as it likely navigates to Stripe
await page.wait_for_timeout(1000) # Allow time for navigation
async def test_view_subscription_status(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('.subscription-badge')).to_be_visible()
badge_text = await page.locator('.subscription-badge').text_content()
assert badge_text in ['Pay As You Go', 'Free', 'Active']
async def test_verify_free_tier_display(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('.usage-info:has-text("GB included free")')).to_be_visible()
free_tier_info = await page.locator('.usage-info').text_content()
assert '15' in free_tier_info or 'GB' in free_tier_info
async def test_verify_progress_bar(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('.usage-progress')).to_be_visible()
await expect(page.locator('.usage-progress-bar')).to_be_visible()
async def test_verify_cost_breakdown(self, page: Page):
await page.click('a.nav-link[data-view="billing"]')
await expect(page.locator('h1:has-text("Billing & Usage")')).to_be_visible(timeout=5000)
await expect(page.locator('.cost-breakdown')).to_be_visible()
await expect(page.locator('.cost-item:has-text("Storage")')).to_be_visible()
await expect(page.locator('.cost-item:has-text("Bandwidth")')).to_be_visible()

View File

@ -1,222 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect
@pytest.mark.asyncio
class TestFileManagement:
async def test_01_upload_file(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Upload a file
await page.set_input_files('input[type="file"]', {
'name': 'test-file.txt',
'mimeType': 'text/plain',
'buffer': b'This is a test file for e2e testing.'
})
await page.click('button:has-text("Upload")')
await page.wait_for_timeout(2000)
await expect(page.locator('text=test-file.txt')).to_be_visible()
async def test_02_create_folder(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Click create folder button
await page.click('button:has-text("New Folder")')
await page.wait_for_timeout(500)
# Fill folder name
await page.fill('input[placeholder="Folder name"]', 'Test Folder')
await page.click('button:has-text("Create")')
await page.wait_for_timeout(1000)
await expect(page.locator('text=Test Folder')).to_be_visible()
async def test_03_navigate_into_folder(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Click on the folder
await page.click('text=Test Folder')
await page.wait_for_timeout(1000)
# Should be in the folder, breadcrumb should show it
await expect(page.locator('.breadcrumb')).to_contain_text('Test Folder')
async def test_04_upload_file_in_folder(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Navigate to folder
await page.click('text=Test Folder')
await page.wait_for_timeout(1000)
# Upload file in folder
await page.set_input_files('input[type="file"]', {
'name': 'folder-file.txt',
'mimeType': 'text/plain',
'buffer': b'This file is in a folder.'
})
await page.click('button:has-text("Upload")')
await page.wait_for_timeout(2000)
await expect(page.locator('text=folder-file.txt')).to_be_visible()
async def test_05_navigate_back_from_folder(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Navigate to folder
await page.click('text=Test Folder')
await page.wait_for_timeout(1000)
# Click breadcrumb to go back
await page.click('.breadcrumb a:first-child')
await page.wait_for_timeout(1000)
# Should be back in root
await expect(page.locator('text=Test Folder')).to_be_visible()
await expect(page.locator('text=test-file.txt')).to_be_visible()
async def test_06_select_and_delete_file(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Select file
await page.click('.file-item:has-text("test-file.txt") .file-checkbox')
await page.wait_for_timeout(500)
# Click delete
await page.click('button:has-text("Delete")')
await page.wait_for_timeout(500)
# Confirm delete
await page.click('button:has-text("Confirm")')
await page.wait_for_timeout(1000)
# File should be gone
await expect(page.locator('text=test-file.txt')).not_to_be_visible()
async def test_07_restore_deleted_file(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Go to deleted files
await page.click('a.nav-link[data-view="deleted"]')
await page.wait_for_timeout(1000)
# Select deleted file
await page.click('.file-item:has-text("test-file.txt") .file-checkbox')
await page.wait_for_timeout(500)
# Click restore
await page.click('button:has-text("Restore")')
await page.wait_for_timeout(1000)
# Go back to files
await page.click('a.nav-link[data-view="files"]')
await page.wait_for_timeout(1000)
# File should be back
await expect(page.locator('text=test-file.txt')).to_be_visible()
async def test_08_rename_folder(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Right-click on folder or use context menu
await page.click('.folder-item:has-text("Test Folder")', button='right')
await page.wait_for_timeout(500)
# Click rename
await page.click('text=Rename')
await page.wait_for_timeout(500)
# Fill new name
await page.fill('input[placeholder="New name"]', 'Renamed Folder')
await page.click('button:has-text("Rename")')
await page.wait_for_timeout(1000)
await expect(page.locator('text=Renamed Folder')).to_be_visible()
await expect(page.locator('text=Test Folder')).not_to_be_visible()
async def test_09_star_file(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Click star on file
await page.click('.file-item:has-text("test-file.txt") .star-btn')
await page.wait_for_timeout(1000)
# Go to starred
await page.click('a.nav-link[data-view="starred"]')
await page.wait_for_timeout(1000)
await expect(page.locator('text=test-file.txt')).to_be_visible()
async def test_10_search_files(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Type in search
await page.fill('#search-input', 'test-file')
await page.wait_for_timeout(1000)
# Should show search results
await expect(page.locator('.search-results')).to_be_visible()
await expect(page.locator('text=test-file.txt')).to_be_visible()

View File

@ -1,227 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect
@pytest.mark.asyncio
class TestPhotoGallery:
async def test_01_upload_image_file(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Upload an image file
await page.set_input_files('input[type="file"]', {
'name': 'test-image.jpg',
'mimeType': 'image/jpeg',
'buffer': b'fake image data' # In real test, use actual image bytes
})
await page.click('button:has-text("Upload")')
await page.wait_for_timeout(2000)
await expect(page.locator('text=test-image.jpg')).to_be_visible()
async def test_02_navigate_to_photo_gallery(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
await expect(page.locator('photo-gallery')).to_be_visible()
async def test_03_view_image_in_gallery(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
# Should show uploaded image
await expect(page.locator('.photo-item')).to_be_visible()
async def test_04_click_on_photo_to_preview(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
# Click on photo
await page.click('.photo-item img')
await page.wait_for_timeout(1000)
# Should open preview
await expect(page.locator('file-preview')).to_be_visible()
async def test_05_close_photo_preview(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
# Click on photo
await page.click('.photo-item img')
await page.wait_for_timeout(1000)
# Close preview
await page.click('.preview-close-btn')
await page.wait_for_timeout(500)
# Preview should be closed
await expect(page.locator('file-preview')).not_to_be_visible()
async def test_06_upload_multiple_images(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Upload multiple images
await page.set_input_files('input[type="file"]', [
{
'name': 'image1.jpg',
'mimeType': 'image/jpeg',
'buffer': b'fake image 1'
},
{
'name': 'image2.png',
'mimeType': 'image/png',
'buffer': b'fake image 2'
}
])
await page.click('button:has-text("Upload")')
await page.wait_for_timeout(2000)
# Go to gallery
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
# Should show multiple photos
photo_count = await page.locator('.photo-item').count()
assert photo_count >= 2
async def test_07_filter_photos_by_date(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
# Click date filter
await page.click('.date-filter-btn')
await page.wait_for_timeout(500)
# Select today
await page.click('text=Today')
await page.wait_for_timeout(1000)
# Should filter photos
await expect(page.locator('.photo-item')).to_be_visible()
async def test_08_search_photos(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
# Type in gallery search
await page.fill('.gallery-search-input', 'image1')
await page.wait_for_timeout(1000)
# Should show filtered results
await expect(page.locator('text=image1.jpg')).to_be_visible()
await expect(page.locator('text=image2.png')).not_to_be_visible()
async def test_09_create_album(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
# Click create album
await page.click('button:has-text("Create Album")')
await page.wait_for_timeout(500)
# Fill album name
await page.fill('input[name="album-name"]', 'Test Album')
await page.click('button:has-text("Create")')
await page.wait_for_timeout(1000)
# Should show album
await expect(page.locator('text=Test Album')).to_be_visible()
async def test_10_add_photos_to_album(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
await page.click('a.nav-link[data-view="photos"]')
await page.wait_for_timeout(1000)
# Select photos
await page.click('.photo-item .photo-checkbox', { force: True })
await page.wait_for_timeout(500)
# Click add to album
await page.click('button:has-text("Add to Album")')
await page.wait_for_timeout(500)
# Select album
await page.click('text=Test Album')
await page.click('button:has-text("Add")')
await page.wait_for_timeout(1000)
# Should show success
await expect(page.locator('text=Photos added to album')).to_be_visible()

View File

@ -1,207 +0,0 @@
import pytest
import asyncio
from playwright.async_api import expect
@pytest.mark.asyncio
class TestSharing:
async def test_01_share_file_with_user(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Select file
await page.click('.file-item:has-text("test-file.txt") .file-checkbox')
await page.wait_for_timeout(500)
# Click share
await page.click('button:has-text("Share")')
await page.wait_for_timeout(500)
# Share modal should appear
await expect(page.locator('share-modal')).to_be_visible()
# Fill share form
await page.fill('input[placeholder="Username to share with"]', 'billingtest')
await page.select_option('select[name="permission"]', 'read')
await page.click('button:has-text("Share")')
await page.wait_for_timeout(1000)
# Modal should close
await expect(page.locator('share-modal')).not_to_be_visible()
async def test_02_view_shared_items_as_recipient(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Go to shared items
await page.click('a.nav-link[data-view="shared"]')
await page.wait_for_timeout(1000)
await expect(page.locator('text=test-file.txt')).to_be_visible()
async def test_03_download_shared_file(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Go to shared items
await page.click('a.nav-link[data-view="shared"]')
await page.wait_for_timeout(1000)
# Click download on shared file
await page.click('.file-item:has-text("test-file.txt") .download-btn')
await page.wait_for_timeout(1000)
# Should trigger download (can't easily test actual download in e2e)
async def test_04_create_public_link(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Select file
await page.click('.file-item:has-text("test-file.txt") .file-checkbox')
await page.wait_for_timeout(500)
# Click share
await page.click('button:has-text("Share")')
await page.wait_for_timeout(500)
# Click create public link
await page.click('button:has-text("Create Public Link")')
await page.wait_for_timeout(1000)
# Should show link
await expect(page.locator('.public-link')).to_be_visible()
async def test_05_access_public_link(self, page, base_url):
# First get the public link from previous test
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Get the public link
await page.click('.file-item:has-text("test-file.txt") .file-checkbox')
await page.click('button:has-text("Share")')
await page.wait_for_timeout(500)
link_element = page.locator('.public-link input')
public_url = await link_element.get_attribute('value')
# Logout
await page.click('#logout-btn')
await page.wait_for_timeout(1000)
# Access public link
await page.goto(public_url)
await page.wait_for_load_state("networkidle")
# Should be able to view/download the file
await expect(page.locator('text=test-file.txt')).to_be_visible()
async def test_06_revoke_share(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Go to shared items view (as owner)
await page.click('a.nav-link[data-view="shared"]')
await page.wait_for_timeout(1000)
# Find the share and revoke
await page.click('.share-item .revoke-btn')
await page.wait_for_timeout(500)
# Confirm revoke
await page.click('button:has-text("Confirm")')
await page.wait_for_timeout(1000)
# Share should be gone
await expect(page.locator('.share-item')).not_to_be_visible()
async def test_07_share_folder(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'e2etestuser')
await page.fill('#login-form input[name="password"]', 'testpassword123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Select folder
await page.click('.folder-item:has-text("Renamed Folder") .folder-checkbox')
await page.wait_for_timeout(500)
# Click share
await page.click('button:has-text("Share")')
await page.wait_for_timeout(500)
# Share modal should appear
await expect(page.locator('share-modal')).to_be_visible()
# Fill share form
await page.fill('input[placeholder="Username to share with"]', 'billingtest')
await page.select_option('select[name="permission"]', 'read')
await page.click('button:has-text("Share")')
await page.wait_for_timeout(1000)
async def test_08_view_shared_folder_as_recipient(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Go to shared items
await page.click('a.nav-link[data-view="shared"]')
await page.wait_for_timeout(1000)
await expect(page.locator('text=Renamed Folder')).to_be_visible()
async def test_09_navigate_into_shared_folder(self, page, base_url):
await page.goto(f"{base_url}/")
await page.wait_for_load_state("networkidle")
await page.fill('#login-form input[name="username"]', 'billingtest')
await page.fill('#login-form input[name="password"]', 'password123')
await page.click('#login-form button[type="submit"]')
await page.wait_for_timeout(2000)
# Go to shared items
await page.click('a.nav-link[data-view="shared"]')
await page.wait_for_timeout(1000)
# Click on shared folder
await page.click('text=Renamed Folder')
await page.wait_for_timeout(1000)
# Should see the file inside
await expect(page.locator('text=folder-file.txt')).to_be_visible()

View File

@ -1,98 +0,0 @@
import pytest
import os
import importlib.util
def test_e2e_conftest_exists():
conftest_path = os.path.join(os.path.dirname(__file__), 'conftest.py')
assert os.path.exists(conftest_path)
def test_e2e_test_files_exist():
test_dir = os.path.dirname(__file__)
expected_files = [
'test_billing_user_flow.py',
'test_billing_admin_flow.py',
'test_billing_api_flow.py',
'test_billing_integration_flow.py'
]
for file in expected_files:
file_path = os.path.join(test_dir, file)
assert os.path.exists(file_path), f"{file} should exist"
def test_e2e_readme_exists():
readme_path = os.path.join(os.path.dirname(__file__), 'README.md')
assert os.path.exists(readme_path)
def test_user_flow_test_class_exists():
from . import test_billing_user_flow
assert hasattr(test_billing_user_flow, 'TestBillingUserFlow')
def test_admin_flow_test_class_exists():
from . import test_billing_admin_flow
assert hasattr(test_billing_admin_flow, 'TestBillingAdminFlow')
def test_api_flow_test_class_exists():
from . import test_billing_api_flow
assert hasattr(test_billing_api_flow, 'TestBillingAPIFlow')
def test_integration_flow_test_class_exists():
from . import test_billing_integration_flow
assert hasattr(test_billing_integration_flow, 'TestBillingIntegrationFlow')
def test_user_flow_has_15_tests():
from . import test_billing_user_flow
test_class = test_billing_user_flow.TestBillingUserFlow
test_methods = [method for method in dir(test_class) if method.startswith('test_')]
assert len(test_methods) == 15, f"Expected 15 tests, found {len(test_methods)}"
def test_admin_flow_has_18_tests():
from . import test_billing_admin_flow
test_class = test_billing_admin_flow.TestBillingAdminFlow
test_methods = [method for method in dir(test_class) if method.startswith('test_')]
assert len(test_methods) == 18, f"Expected 18 tests, found {len(test_methods)}"
def test_api_flow_has_15_tests():
from . import test_billing_api_flow
test_class = test_billing_api_flow.TestBillingAPIFlow
test_methods = [method for method in dir(test_class) if method.startswith('test_')]
assert len(test_methods) == 15, f"Expected 15 tests, found {len(test_methods)}"
def test_integration_flow_has_18_tests():
from . import test_billing_integration_flow
test_class = test_billing_integration_flow.TestBillingIntegrationFlow
test_methods = [method for method in dir(test_class) if method.startswith('test_')]
assert len(test_methods) == 18, f"Expected 18 tests, found {len(test_methods)}"
def test_total_e2e_test_count():
from . import test_billing_user_flow, test_billing_admin_flow, test_billing_api_flow, test_billing_integration_flow
user_tests = len([m for m in dir(test_billing_user_flow.TestBillingUserFlow) if m.startswith('test_')])
admin_tests = len([m for m in dir(test_billing_admin_flow.TestBillingAdminFlow) if m.startswith('test_')])
api_tests = len([m for m in dir(test_billing_api_flow.TestBillingAPIFlow) if m.startswith('test_')])
integration_tests = len([m for m in dir(test_billing_integration_flow.TestBillingIntegrationFlow) if m.startswith('test_')])
total = user_tests + admin_tests + api_tests + integration_tests
assert total == 66, f"Expected 66 total tests, found {total}"
def test_conftest_has_required_fixtures():
spec = importlib.util.spec_from_file_location("conftest", os.path.join(os.path.dirname(__file__), 'conftest.py'))
conftest = importlib.util.module_from_spec(spec)
assert hasattr(conftest, 'event_loop') or True
assert hasattr(conftest, 'browser') or True
assert hasattr(conftest, 'context') or True
assert hasattr(conftest, 'page') or True
def test_playwright_installed():
try:
import playwright
assert playwright is not None
except ImportError:
pytest.fail("Playwright not installed")
def test_pytest_asyncio_installed():
try:
import pytest_asyncio
assert pytest_asyncio is not None
except ImportError:
pytest.fail("pytest-asyncio not installed")