drop widget_ prefix from e2e test fixtures

This commit is contained in:
rash
2026-02-18 17:12:55 +01:00
parent 97857e7a67
commit 55299b1eae
20 changed files with 834 additions and 944 deletions

View File

@@ -145,11 +145,8 @@ The `widget_page` fixture provides convenient methods:
```python
def test_example(widget_page, live_server_url, widget_event, widget_items):
# Navigate to event
widget_page.goto_event(live_server_url, 'testorg', 'testevent')
# Wait for widget to load
widget_page.wait_for_widget_load()
# Navigate to event and wait for widget to load
widget_page.goto(live_server_url, 'testorg', 'testevent')
# Select item quantity
widget_page.select_item_quantity('General Admission', 2)
@@ -177,8 +174,7 @@ def test_example(widget_page, live_server_url, widget_event, widget_items):
```python
@pytest.mark.django_db
def test_widget_loads(page, live_server_url, widget_organizer, widget_event, widget_items, widget_page):
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
# Verify content
expect(page.locator(f'text="{widget_event.name}"')).to_be_visible()
@@ -191,8 +187,7 @@ def test_widget_loads(page, live_server_url, widget_organizer, widget_event, wid
def test_variations(page, live_server_url, widget_organizer, widget_event, widget_item_with_variations, widget_page):
item, variations = widget_item_with_variations
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
# Expand variations
widget_page.expand_variations(item.name)
@@ -207,8 +202,7 @@ def test_variations(page, live_server_url, widget_organizer, widget_event, widge
```python
@pytest.mark.django_db
def test_checkout(page, context, live_server_url, widget_organizer, widget_event, widget_items, widget_page):
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
# Add items
widget_page.select_item_quantity(widget_items[0].name, 2)
@@ -447,8 +441,7 @@ class TestFeatureName:
Then: expected outcome
"""
# Arrange
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
# Act
widget_page.select_item_quantity(widget_items[0].name, 1)

View File

@@ -84,131 +84,13 @@ def live_server_url(live_server, settings):
return live_server.url
@pytest.fixture(scope='session', autouse=True)
def _register_widget_test_view():
"""
Register a test view that serves an HTML page with widget embedded.
This allows E2E tests to navigate to a real URL instead of using
set_content, which causes CORS issues.
"""
from django.http import HttpResponse
from django.views import View
from django.urls import path
from pretix.multidomain import maindomain_urlconf as urls
class WidgetTestView(View):
"""Serve HTML page with widget embedded for E2E testing."""
# Widget attributes that can be passed as query params
WIDGET_ATTRS = [
'items', 'categories', 'voucher', 'disable-vouchers',
'disable-iframe', 'subevent', 'list-type',
'display-event-info', 'skip-ssl-check',
]
def get(self, request, organizer, event):
# Build URLs exactly as in working index.html
base_url = f"{request.scheme}://{request.get_host()}"
event_url = f"{base_url}/{organizer}/{event}/"
# CSS is scoped to event, JS is at root
widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css"
widget_js = f"{base_url}/widget/v2.en.js"
# Build extra attributes from query params
extra_attrs = ''
for attr in self.WIDGET_ATTRS:
val = request.GET.get(attr)
if val is not None:
if val == '':
# Boolean attribute (e.g., disable-vouchers)
extra_attrs += f' {attr}'
else:
extra_attrs += f' {attr}="{val}"'
# Always add skip-ssl-check so iframe checkout works on HTTP
if 'skip-ssl-check' not in extra_attrs:
extra_attrs += ' skip-ssl-check'
html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Widget Test</title>
<link rel="stylesheet" type="text/css" href="{widget_css}" crossorigin>
</head>
<body>
<pretix-widget event="{event_url}"{extra_attrs}></pretix-widget>
<script type="text/javascript" src="{widget_js}" async crossorigin></script>
</body>
</html>"""
resp = HttpResponse(html, content_type='text/html')
resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'"
return resp
class ButtonTestView(View):
"""Serve HTML page with pretix-button element for E2E testing."""
def get(self, request, organizer, event):
base_url = f"{request.scheme}://{request.get_host()}"
event_url = f"{base_url}/{organizer}/{event}/"
widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css"
widget_js = f"{base_url}/widget/v2.en.js"
# Build extra attributes from query params
extra_attrs = ''
for attr in ['items', 'voucher', 'subevent', 'disable-iframe']:
val = request.GET.get(attr)
if val is not None:
if val == '':
extra_attrs += f' {attr}'
else:
extra_attrs += f' {attr}="{val}"'
button_text = request.GET.get('button-text', 'Buy tickets!')
html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Button Test</title>
<link rel="stylesheet" type="text/css" href="{widget_css}" crossorigin>
</head>
<body>
<pretix-button event="{event_url}"{extra_attrs}>{button_text}</pretix-button>
<script type="text/javascript" src="{widget_js}" async crossorigin></script>
</body>
</html>"""
resp = HttpResponse(html, content_type='text/html')
resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'"
return resp
# Add URL patterns
test_pattern = path(
'widget-test/<str:organizer>/<str:event>/',
WidgetTestView.as_view()
)
button_pattern = path(
'button-test/<str:organizer>/<str:event>/',
ButtonTestView.as_view()
)
# Insert at beginning of URL patterns
if hasattr(urls, 'urlpatterns'):
urls.urlpatterns.insert(0, test_pattern)
urls.urlpatterns.insert(0, button_pattern)
# ============================================================================
# Test Data Fixtures - Organizers and Events
# ============================================================================
@pytest.fixture
@scopes_disabled()
def widget_organizer(db):
def organizer(db):
"""
Create an organizer for widget tests.
Reuses the same pattern as existing API tests.
@@ -222,15 +104,15 @@ def widget_organizer(db):
@pytest.fixture
@scopes_disabled()
def widget_event(widget_organizer):
def event(organizer):
"""Create a basic event for widget tests."""
event = Event.objects.create(
organizer=widget_organizer,
organizer=organizer,
name='Test Event',
slug='testevent',
date_from=datetime(2026, 6, 1, 10, 0, 0, tzinfo=timezone.utc),
date_to=datetime(2026, 6, 1, 18, 0, 0, tzinfo=timezone.utc),
currency='USD',
currency='EUR',
live=True,
testmode=False,
plugins='pretix.plugins.banktransfer',
@@ -243,7 +125,7 @@ def widget_event(widget_organizer):
@pytest.fixture
@scopes_disabled()
def widget_items(widget_event):
def items(event):
"""Create basic test items/products."""
from pretix.base.models import ItemCategory
@@ -251,14 +133,14 @@ def widget_items(widget_event):
# Create a proper category
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Tickets',
position=0
)
# General Admission ticket
item1 = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='General Admission',
default_price=Decimal('50.00'),
@@ -269,7 +151,7 @@ def widget_items(widget_event):
# VIP ticket
item2 = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='VIP Ticket',
default_price=Decimal('150.00'),
@@ -281,7 +163,7 @@ def widget_items(widget_event):
# Create quotas for each item
for item in items:
quota = Quota.objects.create(
event=widget_event,
event=event,
name=f'{item.name} Quota',
size=100,
)
@@ -296,19 +178,19 @@ def widget_items(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_with_variations(widget_event):
def item_with_variations(event):
"""Create an item with size variations (S, M, L, XL)."""
from pretix.base.models import ItemCategory
# Create category for the item
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Merchandise',
position=1
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Event T-Shirt',
default_price=Decimal('25.00'),
@@ -334,7 +216,7 @@ def widget_item_with_variations(widget_event):
# Create quota for all variations
quota = Quota.objects.create(
event=widget_event,
event=event,
name='T-Shirt Quota',
size=50,
)
@@ -348,18 +230,18 @@ def widget_item_with_variations(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_single_select(widget_event):
def item_single_select(event):
"""Create an item with max_per_order=1 (should show checkbox)."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='VIP',
position=2
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='VIP Pass',
default_price=Decimal('500.00'),
@@ -369,7 +251,7 @@ def widget_item_single_select(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='VIP Quota',
size=10,
)
@@ -380,18 +262,18 @@ def widget_item_single_select(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_free_price(widget_event):
def item_free_price(event):
"""Create an item with pay-what-you-want pricing."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Donations',
position=3
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Donation',
default_price=Decimal('10.00'),
@@ -401,7 +283,7 @@ def widget_item_free_price(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Donation Quota',
size=999,
)
@@ -412,18 +294,18 @@ def widget_item_free_price(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_sold_out(widget_event):
def item_sold_out(event):
"""Create a sold out item."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Early Bird',
position=4
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Early Bird Ticket',
default_price=Decimal('30.00'),
@@ -433,7 +315,7 @@ def widget_item_sold_out(widget_event):
# Create quota with size=0 (sold out)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Early Bird Quota',
size=0,
)
@@ -444,18 +326,18 @@ def widget_item_sold_out(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_free(widget_event):
def item_free(event):
"""Create a free item (price = 0.00)."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Free Stuff',
position=10
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Free Gift',
default_price=Decimal('0.00'),
@@ -463,7 +345,7 @@ def widget_item_free(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Free Gift Quota',
size=100,
)
@@ -474,18 +356,18 @@ def widget_item_free(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_with_decimals(widget_event):
def item_with_decimals(event):
"""Create an item with non-zero decimal price."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Test Category',
position=11
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Half Price Item',
default_price=Decimal('12.50'),
@@ -493,7 +375,7 @@ def widget_item_with_decimals(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Half Price Quota',
size=50,
)
@@ -504,25 +386,25 @@ def widget_item_with_decimals(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_with_tax(widget_event):
def item_with_tax(event):
"""Create an item with tax rule."""
from pretix.base.models import ItemCategory, TaxRule
# Create tax rule
tax_rule = TaxRule.objects.create(
event=widget_event,
event=event,
name='VAT',
rate=Decimal('19.00'), # 19% VAT
)
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Taxed Items',
position=12
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Taxed Product',
default_price=Decimal('100.00'),
@@ -531,7 +413,7 @@ def widget_item_with_tax(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Taxed Product Quota',
size=50,
)
@@ -546,18 +428,18 @@ def widget_item_with_tax(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_min_order(widget_event):
def item_min_order(event):
"""Create an item with min_per_order=2."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Group Tickets',
position=13
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Group Pass',
default_price=Decimal('40.00'),
@@ -566,7 +448,7 @@ def widget_item_min_order(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Group Pass Quota',
size=50,
)
@@ -577,18 +459,18 @@ def widget_item_min_order(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_special_chars(widget_event):
def item_special_chars(event):
"""Create an item with special characters in the name."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Spezial',
position=14
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Böhm & Söhne Konzert',
default_price=Decimal('55.00'),
@@ -596,7 +478,7 @@ def widget_item_special_chars(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Special Quota',
size=50,
)
@@ -611,19 +493,19 @@ def widget_item_special_chars(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_items_with_category_description(widget_event):
def items_with_category_description(event):
"""Create items with a category that has a description."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Tickets',
description='Early bird tickets available',
position=0
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Early Bird',
default_price=Decimal('35.00'),
@@ -631,7 +513,7 @@ def widget_items_with_category_description(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Early Bird Quota',
size=100,
)
@@ -642,24 +524,24 @@ def widget_items_with_category_description(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_items_multiple_categories(widget_event):
def items_multiple_categories(event):
"""Create items in multiple categories to test grouping and ordering."""
from pretix.base.models import ItemCategory
cat_music = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Music',
position=0
)
cat_food = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Food & Drink',
position=1
)
item1 = Item.objects.create(
event=widget_event,
event=event,
category=cat_music,
name='Concert Ticket',
default_price=Decimal('75.00'),
@@ -667,7 +549,7 @@ def widget_items_multiple_categories(widget_event):
)
item2 = Item.objects.create(
event=widget_event,
event=event,
category=cat_food,
name='Food Pass',
default_price=Decimal('25.00'),
@@ -676,7 +558,7 @@ def widget_items_multiple_categories(widget_event):
for item in [item1, item2]:
quota = Quota.objects.create(
event=widget_event,
event=event,
name=f'{item.name} Quota',
size=100,
)
@@ -691,33 +573,33 @@ def widget_items_multiple_categories(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_voucher(widget_event, widget_items):
def voucher(event, items):
"""Create a voucher for the event."""
voucher = Voucher.objects.create(
event=widget_event,
event=event,
code='TESTCODE2024',
max_usages=10,
price_mode='none',
)
# Clear the vouchers_exist cache so the widget picks it up
widget_event.get_cache().delete('vouchers_exist')
event.get_cache().delete('vouchers_exist')
return voucher
@pytest.fixture
@scopes_disabled()
def widget_voucher_with_item(widget_event, widget_items):
def voucher_with_item(event, items):
"""Create a voucher tied to a specific item."""
item = widget_items[0]
item = items[0]
voucher = Voucher.objects.create(
event=widget_event,
event=event,
code='ITEMVOUCHER',
max_usages=5,
price_mode='percent',
value=Decimal('20.00'), # 20% off
item=item,
)
widget_event.get_cache().delete('vouchers_exist')
event.get_cache().delete('vouchers_exist')
return voucher
@@ -727,21 +609,21 @@ def widget_voucher_with_item(widget_event, widget_items):
@pytest.fixture
@scopes_disabled()
def widget_item_sold_out_with_waitinglist(widget_event):
def item_sold_out_with_waitinglist(event):
"""Create a sold out item with waiting list enabled."""
from pretix.base.models import ItemCategory
# Enable waiting list on the event
widget_event.settings.set('waiting_list_enabled', True)
event.settings.set('waiting_list_enabled', True)
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Sold Out',
position=20
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Sold Out Concert',
default_price=Decimal('80.00'),
@@ -751,7 +633,7 @@ def widget_item_sold_out_with_waitinglist(widget_event):
# Create quota with size=0 (sold out)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Sold Out Quota',
size=0,
)
@@ -766,17 +648,17 @@ def widget_item_sold_out_with_waitinglist(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_event_series(widget_organizer):
def event_series(organizer):
"""Create an event series with multiple subevents, items, and quotas."""
from pretix.base.models import ItemCategory
event = Event.objects.create(
organizer=widget_organizer,
organizer=organizer,
name='Concert Series',
slug='concert-series',
date_from=datetime(2026, 6, 1, 19, 0, 0, tzinfo=timezone.utc),
has_subevents=True,
currency='USD',
currency='EUR',
live=True,
plugins='pretix.plugins.banktransfer',
)
@@ -839,15 +721,16 @@ def widget_page(page):
def __init__(self, page: Page):
self.page = page
def goto_widget_test_page(
def goto(
self,
live_server_url: str,
org_slug: str,
event_slug: str,
wait=True,
**widget_attrs
):
"""
Navigate to a test page with widget embedded.
Navigate to a test page with widget embedded and wait for it to load.
Uses a Django view that serves an HTML page with the pretix
widget embedded, simulating how it would be used on a customer's
@@ -856,6 +739,9 @@ def widget_page(page):
Extra keyword arguments are passed as query params to the view,
which converts them to widget attributes. For boolean attributes
(like disable-vouchers), pass an empty string as value.
Set wait=False to skip waiting for the widget to load (useful for
tests that need to observe loading/error states).
"""
# Navigate to the test view URL
test_url = f"{live_server_url}/widget-test/{org_slug}/{event_slug}/"
@@ -863,11 +749,8 @@ def widget_page(page):
from urllib.parse import urlencode
test_url += '?' + urlencode(widget_attrs)
self.page.goto(test_url)
return self
def goto_event(self, live_server_url: str, org_slug: str, event_slug: str):
"""Navigate to an event page."""
self.page.goto(f"{live_server_url}/{org_slug}/{event_slug}/")
if wait:
self.wait_for_widget_load()
return self
def wait_for_widget_load(self):
@@ -975,18 +858,18 @@ def widget_page(page):
@pytest.fixture
@scopes_disabled()
def widget_item_require_voucher(widget_event):
def item_require_voucher(event):
"""Create an item that requires a voucher to purchase."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Voucher Only',
position=30
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Exclusive Pass',
default_price=Decimal('200.00'),
@@ -995,7 +878,7 @@ def widget_item_require_voucher(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Exclusive Quota',
size=50,
)
@@ -1006,18 +889,18 @@ def widget_item_require_voucher(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_low_stock(widget_event):
def item_low_stock(event):
"""Create an item with low stock (quota_left visible)."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Limited',
position=31
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Last Chance Ticket',
default_price=Decimal('65.00'),
@@ -1025,32 +908,32 @@ def widget_item_low_stock(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Limited Quota',
size=3,
)
quota.items.add(item)
# Enable "show quota left" on the event
widget_event.settings.set('show_quota_left', True)
event.settings.set('show_quota_left', True)
return item
@pytest.fixture
@scopes_disabled()
def widget_item_not_yet_available(widget_event):
def item_not_yet_available(event):
"""Create an item that is not yet available (future available_from)."""
from pretix.base.models import ItemCategory
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Coming Soon',
position=32
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Future Ticket',
default_price=Decimal('45.00'),
@@ -1060,7 +943,7 @@ def widget_item_not_yet_available(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Future Quota',
size=100,
)
@@ -1075,7 +958,7 @@ def widget_item_not_yet_available(widget_event):
@pytest.fixture
@scopes_disabled()
def widget_item_with_picture(widget_event):
def item_with_picture(event):
"""Create an item with a product picture."""
from pretix.base.models import ItemCategory
from django.core.files.uploadedfile import SimpleUploadedFile
@@ -1083,7 +966,7 @@ def widget_item_with_picture(widget_event):
from PIL import Image as PILImage
category = ItemCategory.objects.create(
event=widget_event,
event=event,
name='Gallery Items',
position=40
)
@@ -1101,7 +984,7 @@ def widget_item_with_picture(widget_event):
)
item = Item.objects.create(
event=widget_event,
event=event,
category=category,
name='Art Print',
default_price=Decimal('35.00'),
@@ -1111,7 +994,7 @@ def widget_item_with_picture(widget_event):
)
quota = Quota.objects.create(
event=widget_event,
event=event,
name='Art Print Quota',
size=50,
)
@@ -1144,3 +1027,118 @@ def cross_browser_page(request, playwright):
page.close()
context.close()
browser.close()
@pytest.fixture(scope='session', autouse=True)
def _register_widget_test_view():
"""
Register a test view that serves an HTML page with widget embedded.
This allows E2E tests to navigate to a real URL instead of using
set_content, which causes CORS issues.
"""
from django.http import HttpResponse
from django.views import View
from django.urls import path
from pretix.multidomain import maindomain_urlconf as urls
class WidgetTestView(View):
"""Serve HTML page with widget embedded for E2E testing."""
# Widget attributes that can be passed as query params
WIDGET_ATTRS = [
'items', 'categories', 'voucher', 'disable-vouchers',
'disable-iframe', 'subevent', 'list-type',
'display-event-info', 'skip-ssl-check',
]
def get(self, request, organizer, event):
base_url = f"{request.scheme}://{request.get_host()}"
event_url = f"{base_url}/{organizer}/{event}/"
widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css"
widget_js = f"{base_url}/widget/v2.en.js"
# Build extra attributes from query params
extra_attrs = ''
for attr in self.WIDGET_ATTRS:
val = request.GET.get(attr)
if val is not None:
if val == '':
# Boolean attribute (e.g., disable-vouchers)
extra_attrs += f' {attr}'
else:
extra_attrs += f' {attr}="{val}"'
# Always add skip-ssl-check so iframe checkout works on HTTP
if 'skip-ssl-check' not in extra_attrs:
extra_attrs += ' skip-ssl-check'
html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Widget Test</title>
<link rel="stylesheet" type="text/css" href="{widget_css}" crossorigin>
</head>
<body>
<pretix-widget event="{event_url}"{extra_attrs}></pretix-widget>
<script type="text/javascript" src="{widget_js}" async crossorigin></script>
</body>
</html>"""
resp = HttpResponse(html, content_type='text/html')
resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'"
return resp
class ButtonTestView(View):
"""Serve HTML page with pretix-button element for E2E testing."""
def get(self, request, organizer, event):
base_url = f"{request.scheme}://{request.get_host()}"
event_url = f"{base_url}/{organizer}/{event}/"
widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css"
widget_js = f"{base_url}/widget/v2.en.js"
# Build extra attributes from query params
extra_attrs = ''
for attr in ['items', 'voucher', 'subevent', 'disable-iframe']:
val = request.GET.get(attr)
if val is not None:
if val == '':
extra_attrs += f' {attr}'
else:
extra_attrs += f' {attr}="{val}"'
button_text = request.GET.get('button-text', 'Buy tickets!')
html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Button Test</title>
<link rel="stylesheet" type="text/css" href="{widget_css}" crossorigin>
</head>
<body>
<pretix-button event="{event_url}"{extra_attrs}>{button_text}</pretix-button>
<script type="text/javascript" src="{widget_js}" async crossorigin></script>
</body>
</html>"""
resp = HttpResponse(html, content_type='text/html')
resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'"
return resp
# Add URL patterns
test_pattern = path(
'widget-test/<str:organizer>/<str:event>/',
WidgetTestView.as_view()
)
button_pattern = path(
'button-test/<str:organizer>/<str:event>/',
ButtonTestView.as_view()
)
# Insert at beginning of URL patterns
if hasattr(urls, 'urlpatterns'):
urls.urlpatterns.insert(0, test_pattern)
urls.urlpatterns.insert(0, button_pattern)

View File

@@ -9,13 +9,13 @@ from playwright.sync_api import Page
def test_css_contains_no_scss_syntax(
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items
organizer,
event,
items
):
"""Verify CSS is compiled and doesn't contain SCSS syntax."""
# Fetch the CSS directly
css_url = f"{live_server_url}/{widget_organizer.slug}/{widget_event.slug}/widget/v2.css"
css_url = f"{live_server_url}/{organizer.slug}/{event.slug}/widget/v2.css"
print(f"\nFetching CSS from: {css_url}")
response = page.request.get(css_url)

View File

@@ -23,38 +23,36 @@ class TestWidgetAriaLabels:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Main widget wrapper should have role="article"
and aria-label with event name.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
wrapper = page.locator('.pretix-widget-wrapper')
expect(wrapper).to_have_attribute('role', 'article')
expect(wrapper).to_have_attribute('aria-label', widget_event.name)
expect(wrapper).to_have_attribute('aria-label', event.name)
def test_widget_wrapper_is_focusable(
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Widget wrapper should have tabindex="0" for keyboard access.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
wrapper = page.locator('.pretix-widget-wrapper')
expect(wrapper).to_have_attribute('tabindex', '0')
@@ -63,9 +61,9 @@ class TestWidgetAriaLabels:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -76,13 +74,12 @@ class TestWidgetAriaLabels:
is explicitly enabled for single events (auto mode hides it).
We use the display-event-info attribute to force it on.
"""
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
widget_event.slug,
organizer.slug,
event.slug,
**{'display-event-info': 'true'}
)
widget_page.wait_for_widget_load()
heading = page.locator(
'.pretix-widget-event-header strong[role="heading"]')
@@ -98,21 +95,20 @@ class TestQuantityControlAccessibility:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Plus/minus buttons should have descriptive aria-labels.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Find increment button for first item
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_items[0].name}")')
f'.pretix-widget-item:has-text("{items[0].name}")')
inc_btn = item_elem.locator('button[aria-label]').last
dec_btn = item_elem.locator('button[aria-label]').first
@@ -126,20 +122,19 @@ class TestQuantityControlAccessibility:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Quantity input should be connected to a label via aria-labelledby.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_items[0].name}")')
f'.pretix-widget-item:has-text("{items[0].name}")')
qty_input = item_elem.locator('input[type="number"]')
labelledby = qty_input.get_attribute('aria-labelledby')
@@ -154,17 +149,16 @@ class TestVoucherAccessibility:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_voucher,
organizer,
event,
voucher,
widget_page
):
"""
Voucher input should reference the headline via aria-labelledby.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
voucher_input = page.locator('.pretix-widget-voucher-input')
headline = page.locator('.pretix-widget-voucher-headline')
@@ -186,20 +180,19 @@ class TestVariationAccessibility:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""
Variations toggle button should have aria-expanded
and aria-controls attributes.
"""
item, _ = widget_item_with_variations
item, _ = item_with_variations
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{item.name}")')
@@ -226,22 +219,21 @@ class TestCalendarAccessibility:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event_series,
organizer,
event_series,
widget_page
):
"""
Calendar table should have tabindex="0" and aria-labelledby.
"""
event, _ = widget_event_series
event, _ = event_series
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
organizer.slug,
event.slug,
**{'list-type': 'calendar'}
)
widget_page.wait_for_widget_load()
table = page.locator('.pretix-widget-event-calendar-table')
expect(table).to_have_attribute('tabindex', '0')
@@ -254,23 +246,22 @@ class TestCalendarAccessibility:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event_series,
organizer,
event_series,
widget_page
):
"""
Calendar day-of-week headers should have full day names
as aria-labels (Mo -> Monday, Tu -> Tuesday, etc.).
"""
event, _ = widget_event_series
event, _ = event_series
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
organizer.slug,
event.slug,
**{'list-type': 'calendar'}
)
widget_page.wait_for_widget_load()
# Check first day header has aria-label
first_header = page.locator(
@@ -289,18 +280,17 @@ class TestKeyboardNavigation:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Pressing Tab should cycle through interactive elements
(inputs, buttons) within the widget.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Tab through several elements
focused_tags = set()

View File

@@ -20,18 +20,17 @@ class TestSoldOutState:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_sold_out,
organizer,
event,
item_sold_out,
widget_page
):
"""Sold out item should show 'Sold out' text."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_sold_out.name}")')
f'.pretix-widget-item:has-text("{item_sold_out.name}")')
expect(item_elem).to_be_visible()
# Should show "Sold out" in the availability area
@@ -42,18 +41,17 @@ class TestSoldOutState:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_sold_out,
organizer,
event,
item_sold_out,
widget_page
):
"""Sold out item should not show any input controls."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_sold_out.name}")')
f'.pretix-widget-item:has-text("{item_sold_out.name}")')
expect(item_elem).to_be_visible()
# Should not have any input controls
@@ -68,21 +66,20 @@ class TestQuotaLeftDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_low_stock,
organizer,
event,
item_low_stock,
widget_page
):
"""
Item with low stock and show_quota_left should display
the number of remaining tickets.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_low_stock.name}")')
f'.pretix-widget-item:has-text("{item_low_stock.name}")')
expect(item_elem).to_be_visible()
# Should show quota left text containing "3"
@@ -93,18 +90,17 @@ class TestQuotaLeftDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_low_stock,
organizer,
event,
item_low_stock,
widget_page
):
"""Low stock items should still be purchasable."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_low_stock.name}")')
f'.pretix-widget-item:has-text("{item_low_stock.name}")')
expect(item_elem).to_be_visible()
# Should have quantity input
@@ -119,18 +115,17 @@ class TestRequireVoucherState:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_require_voucher,
organizer,
event,
item_require_voucher,
widget_page
):
"""Item requiring voucher should show 'Only available with a voucher'."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_require_voucher.name}")')
f'.pretix-widget-item:has-text("{item_require_voucher.name}")')
expect(item_elem).to_be_visible()
# Should show unavailability message with voucher link
@@ -142,19 +137,18 @@ class TestRequireVoucherState:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_require_voucher,
widget_voucher,
organizer,
event,
item_require_voucher,
voucher,
widget_page
):
"""Voucher-required message should link to the voucher input field."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_require_voucher.name}")')
f'.pretix-widget-item:has-text("{item_require_voucher.name}")')
unavail = item_elem.locator('.pretix-widget-availability-unavailable')
# Should have a link (to jump to voucher input)
@@ -170,18 +164,17 @@ class TestNotYetAvailable:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_not_yet_available,
organizer,
event,
item_not_yet_available,
widget_page
):
"""Item with future available_from should show 'Not yet available'."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_not_yet_available.name}")')
f'.pretix-widget-item:has-text("{item_not_yet_available.name}")')
expect(item_elem).to_be_visible()
# Should show unavailability message
@@ -192,18 +185,17 @@ class TestNotYetAvailable:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_not_yet_available,
organizer,
event,
item_not_yet_available,
widget_page
):
"""Not-yet-available items should not have quantity controls."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_not_yet_available.name}")')
f'.pretix-widget-item:has-text("{item_not_yet_available.name}")')
expect(item_elem).to_be_visible()
# Should not have any input controls

View File

@@ -21,17 +21,16 @@ class TestCartBasics:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""Selecting items and clicking Buy should open iframe checkout."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
widget_page.select_item_quantity(widget_items[0].name, 2)
widget_page.select_item_quantity(items[0].name, 2)
widget_page.click_buy_button()
# Iframe checkout should open
@@ -48,15 +47,14 @@ class TestCartBasics:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""Clicking Buy without selecting items should not open checkout."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Don't select any items, just click buy
widget_page.click_buy_button()
@@ -70,7 +68,7 @@ class TestCartBasics:
# Items should still be there
expect(page.locator(
f'.pretix-widget-item:has-text("{widget_items[0].name}")'
f'.pretix-widget-item:has-text("{items[0].name}")'
)).to_be_visible()
@@ -83,17 +81,16 @@ class TestCartPersistence:
page: Page,
context: BrowserContext,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""Adding items to cart should create a pretix_widget cookie."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
widget_page.select_item_quantity(widget_items[0].name, 1)
widget_page.select_item_quantity(items[0].name, 1)
widget_page.click_buy_button()
# Wait for iframe checkout to open (cookie is set during cart creation)
@@ -111,18 +108,17 @@ class TestCartPersistence:
page: Page,
context: BrowserContext,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""After creating a cart and reloading, widget should show resume option."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Create a real cart by adding items and opening checkout
widget_page.select_item_quantity(widget_items[0].name, 1)
widget_page.select_item_quantity(items[0].name, 1)
widget_page.click_buy_button()
widget_page.wait_for_iframe_checkout()
@@ -150,17 +146,16 @@ class TestIframeCheckout:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""With skip-ssl-check, checkout should open in iframe on HTTP."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
widget_page.select_item_quantity(widget_items[0].name, 1)
widget_page.select_item_quantity(items[0].name, 1)
widget_page.click_buy_button()
widget_page.wait_for_iframe_checkout()
@@ -168,23 +163,22 @@ class TestIframeCheckout:
iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
src = iframe_elem.get_attribute('src')
assert 'iframe=1' in src
assert widget_organizer.slug in src
assert organizer.slug in src
def test_close_iframe_button(
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""User should be able to close checkout iframe."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
widget_page.select_item_quantity(widget_items[0].name, 1)
widget_page.select_item_quantity(items[0].name, 1)
widget_page.click_buy_button()
widget_page.wait_for_iframe_checkout()
@@ -198,17 +192,16 @@ class TestIframeCheckout:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""Iframe checkout URL should include take_cart_id parameter."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
widget_page.select_item_quantity(widget_items[0].name, 2)
widget_page.select_item_quantity(items[0].name, 2)
widget_page.click_buy_button()
widget_page.wait_for_iframe_checkout()
@@ -221,17 +214,16 @@ class TestIframeCheckout:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""Iframe checkout overlay container should be visible during checkout."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
widget_page.select_item_quantity(widget_items[0].name, 1)
widget_page.select_item_quantity(items[0].name, 1)
widget_page.click_buy_button()
widget_page.wait_for_iframe_checkout()
@@ -248,19 +240,18 @@ class TestMultipleItemSelection:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""Selecting multiple items should open checkout with all of them."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Select multiple items
widget_page.select_item_quantity(widget_items[0].name, 2)
widget_page.select_item_quantity(widget_items[1].name, 1)
widget_page.select_item_quantity(items[0].name, 2)
widget_page.select_item_quantity(items[1].name, 1)
widget_page.click_buy_button()
@@ -275,22 +266,21 @@ class TestMultipleItemSelection:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
widget_item_single_select,
organizer,
event,
items,
item_single_select,
widget_page
):
"""Selecting both checkbox and quantity items should open checkout."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Select quantity item
widget_page.select_item_quantity(widget_items[0].name, 3)
widget_page.select_item_quantity(items[0].name, 3)
# Select checkbox item
widget_page.select_item_quantity(widget_item_single_select.name, 1)
widget_page.select_item_quantity(item_single_select.name, 1)
widget_page.click_buy_button()

View File

@@ -19,19 +19,18 @@ class TestCategoryDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Category names should be shown as h3 headers.
The widget_items fixture creates a 'Tickets' category.
The items fixture creates a 'Tickets' category.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Category header should be visible
category_header = page.locator(
@@ -42,17 +41,16 @@ class TestCategoryDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items_with_category_description,
organizer,
event,
items_with_category_description,
widget_page
):
"""
Category descriptions should be displayed below category name.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Category description should be visible
desc = page.locator('.pretix-widget-category-description')
@@ -63,18 +61,17 @@ class TestCategoryDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items_multiple_categories,
organizer,
event,
items_multiple_categories,
widget_page
):
"""
Items should be grouped under respective categories
and maintain category sort order.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Both categories should be visible
expect(page.locator(

View File

@@ -19,25 +19,24 @@ class TestItemsFilter:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
When items="<id>" is set, only that item should be shown.
"""
# Get the first item's ID
target_item = widget_items[0]
other_item = widget_items[1]
target_item = items[0]
other_item = items[1]
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
widget_event.slug,
organizer.slug,
event.slug,
items=str(target_item.pk)
)
widget_page.wait_for_widget_load()
# Target item should be visible
expect(page.locator(
@@ -53,26 +52,25 @@ class TestItemsFilter:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
When items="<id1>,<id2>", both items should be shown.
"""
ids = ','.join(str(item.pk) for item in widget_items)
ids = ','.join(str(item.pk) for item in items)
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
widget_event.slug,
organizer.slug,
event.slug,
items=ids
)
widget_page.wait_for_widget_load()
# Both items should be visible
for item in widget_items:
for item in items:
expect(page.locator(
f'.pretix-widget-item:has-text("{item.name}")'
)).to_be_visible()
@@ -86,26 +84,25 @@ class TestCategoriesFilter:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items_multiple_categories,
organizer,
event,
items_multiple_categories,
widget_page
):
"""
When categories="<id>" is set, only items from that category
should be shown.
"""
items = widget_items_multiple_categories
items = items_multiple_categories
# Get the category of the first item (Music)
target_category = items[0].category
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
widget_event.slug,
organizer.slug,
event.slug,
categories=str(target_category.pk)
)
widget_page.wait_for_widget_load()
# Music item should be visible
expect(page.locator(
@@ -126,9 +123,9 @@ class TestDisableVouchers:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_voucher,
organizer,
event,
voucher,
widget_page
):
"""
@@ -136,13 +133,12 @@ class TestDisableVouchers:
even when vouchers exist.
"""
# Navigate with disable-vouchers attribute
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
widget_event.slug,
organizer.slug,
event.slug,
**{'disable-vouchers': ''}
)
widget_page.wait_for_widget_load()
# Voucher section should NOT be visible
voucher_section = page.locator('.pretix-widget-voucher')
@@ -157,22 +153,21 @@ class TestDisableIframe:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
When disable-iframe is set, checkout should open in a new tab
instead of an iframe overlay.
"""
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
widget_event.slug,
organizer.slug,
event.slug,
**{'disable-iframe': ''}
)
widget_page.wait_for_widget_load()
# Select quantity for an item
widget_page.select_item_quantity('General Admission', 1)
@@ -183,5 +178,5 @@ class TestDisableIframe:
popup = popup_info.value
# New tab should navigate to checkout URL
assert widget_organizer.slug in popup.url
assert organizer.slug in popup.url
popup.close()

View File

@@ -21,21 +21,20 @@ class TestWidgetMode:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Default widget mode should show full product listing
with categories, items, and a buy button.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Should show items
for item in widget_items:
for item in items:
expect(page.locator(
f'.pretix-widget-item:has-text("{item.name}")'
)).to_be_visible()
@@ -54,22 +53,21 @@ class TestCalendarView:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event_series,
organizer,
event_series,
widget_page
):
"""
Calendar view should show a monthly grid with event dates.
"""
event, subevents = widget_event_series
event, subevents = event_series
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
organizer.slug,
event.slug,
**{'list-type': 'calendar'}
)
widget_page.wait_for_widget_load()
# Should show calendar table
expect(page.locator(
@@ -84,22 +82,21 @@ class TestCalendarView:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event_series,
organizer,
event_series,
widget_page
):
"""
Clicking next month button should navigate to the next month.
"""
event, subevents = widget_event_series
event, subevents = event_series
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
organizer.slug,
event.slug,
**{'list-type': 'calendar'}
)
widget_page.wait_for_widget_load()
# Get current month heading text
header = page.locator('.pretix-widget-event-calendar-head')
@@ -121,8 +118,8 @@ class TestCalendarView:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event_series,
organizer,
event_series,
widget_page
):
"""
@@ -133,15 +130,14 @@ class TestCalendarView:
(target_url resolves to configured domain, not live_server).
We verify the links exist and have correct structure.
"""
event, _ = widget_event_series
event, _ = event_series
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
organizer.slug,
event.slug,
**{'list-type': 'calendar'}
)
widget_page.wait_for_widget_load()
# Event links should exist and show event info
event_link = page.locator(
@@ -172,22 +168,21 @@ class TestListView:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event_series,
organizer,
event_series,
widget_page
):
"""
List view should display events as a linear list.
"""
event, subevents = widget_event_series
event, subevents = event_series
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
organizer.slug,
event.slug,
**{'list-type': 'list'}
)
widget_page.wait_for_widget_load()
# Should show event list entries
entries = page.locator('.pretix-widget-event-list-entry')
@@ -200,22 +195,21 @@ class TestListView:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event_series,
organizer,
event_series,
widget_page
):
"""
List view should show subevent names.
"""
event, subevents = widget_event_series
event, subevents = event_series
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
organizer.slug,
event.slug,
**{'list-type': 'list'}
)
widget_page.wait_for_widget_load()
# At least the first subevent name should appear
expect(page.locator(
@@ -226,8 +220,8 @@ class TestListView:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event_series,
organizer,
event_series,
widget_page
):
"""
@@ -237,15 +231,14 @@ class TestListView:
(target_url resolves to configured domain, not live_server).
We verify the entries have correct structure.
"""
event, _ = widget_event_series
event, _ = event_series
widget_page.goto_widget_test_page(
widget_page.goto(
live_server_url,
widget_organizer.slug,
organizer.slug,
event.slug,
**{'list-type': 'list'}
)
widget_page.wait_for_widget_load()
# Each entry should show availability info
first_entry = page.locator(
@@ -266,9 +259,9 @@ class TestButtonMode:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -276,8 +269,8 @@ class TestButtonMode:
"""
widget_page.goto_button_test_page(
live_server_url,
widget_organizer.slug,
widget_event.slug
organizer.slug,
event.slug
)
# Wait for script to load and initialize
@@ -292,9 +285,9 @@ class TestButtonMode:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -302,8 +295,8 @@ class TestButtonMode:
"""
widget_page.goto_button_test_page(
live_server_url,
widget_organizer.slug,
widget_event.slug
organizer.slug,
event.slug
)
page.wait_for_timeout(2000)
@@ -313,5 +306,5 @@ class TestButtonMode:
page.locator('.pretix-button').click()
popup = popup_info.value
assert widget_organizer.slug in popup.url
assert organizer.slug in popup.url
popup.close()

View File

@@ -19,8 +19,8 @@ class TestEmptyStates:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
organizer,
event,
widget_page
):
"""
@@ -28,9 +28,8 @@ class TestEmptyStates:
Should show the widget container but no item rows.
"""
# Navigate without creating any items
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Widget should still be present
expect(page.locator('.pretix-widget')).to_be_visible()
@@ -43,18 +42,17 @@ class TestEmptyStates:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Submitting with zero quantity should not navigate away.
The widget should remain visible without opening checkout.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Don't select any items, just click buy
widget_page.click_buy_button()
@@ -65,7 +63,7 @@ class TestEmptyStates:
# Items should still be visible
expect(page.locator(
f'.pretix-widget-item:has-text("{widget_items[0].name}")'
f'.pretix-widget-item:has-text("{items[0].name}")'
)).to_be_visible()
@@ -77,20 +75,19 @@ class TestMinPerOrder:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_min_order,
organizer,
event,
item_min_order,
widget_page
):
"""
Items with min_per_order should display a text message
indicating the minimum quantity (e.g. "minimum amount to order: 2").
"""
item = widget_item_min_order
item = item_min_order
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{item.name}")')
@@ -110,20 +107,19 @@ class TestSpecialCharacters:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_special_chars,
organizer,
event,
item_special_chars,
widget_page
):
"""
Items with special characters (umlauts, ampersands, etc.)
should display correctly.
"""
item = widget_item_special_chars
item = item_special_chars
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Item with special characters should be visible
item_elem = page.locator(

View File

@@ -16,9 +16,9 @@ class TestWidgetEmbedding:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -29,25 +29,24 @@ class TestWidgetEmbedding:
- All configured items
"""
# Navigate to test page with widget embedded
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug
widget_page.goto(
live_server_url, organizer.slug, event.slug
)
widget_page.wait_for_widget_load()
# Verify widget container exists with event aria-label
widget = page.locator('.pretix-widget-wrapper')
expect(widget).to_have_attribute('aria-label', widget_event.name)
expect(widget).to_have_attribute('aria-label', event.name)
# Verify items are listed
for item in widget_items:
for item in items:
expect(page.locator(f'text="{item.name}"')).to_be_visible()
def test_widget_displays_loading_state(
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
organizer,
event,
widget_page
):
"""
@@ -56,8 +55,8 @@ class TestWidgetEmbedding:
The loading spinner should eventually disappear when data is loaded.
"""
# Navigate to widget test page
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug
widget_page.goto(
live_server_url, organizer.slug, event.slug, wait=False
)
# Wait for widget element
@@ -74,8 +73,8 @@ class TestWidgetEmbedding:
widget_page
):
"""Widget should display error message for invalid event."""
widget_page.goto_widget_test_page(
live_server_url, 'invalid-org', 'invalid-event'
widget_page.goto(
live_server_url, 'invalid-org', 'invalid-event', wait=False
)
# Should show widget container
@@ -90,9 +89,9 @@ class TestWidgetEmbedding:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -100,13 +99,12 @@ class TestWidgetEmbedding:
Item descriptions should be visible for items that have them.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug
widget_page.goto(
live_server_url, organizer.slug, event.slug
)
widget_page.wait_for_widget_load()
# Check that descriptions are shown
for item in widget_items:
for item in items:
if item.description:
expect(
page.locator(f'text="{item.description}"')
@@ -116,9 +114,9 @@ class TestWidgetEmbedding:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -126,21 +124,20 @@ class TestWidgetEmbedding:
Prices should be formatted with currency and decimal places.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug
widget_page.goto(
live_server_url, organizer.slug, event.slug
)
widget_page.wait_for_widget_load()
# Verify prices are shown (with currency)
# Each item should have its price displayed
for item in widget_items:
for item in items:
# Find the item container first, then check price within it
item_container = page.locator(
f'.pretix-widget-item:has-text("{item.name}")'
)
expect(item_container).to_be_visible()
# Check price is present (formatted as "USD XX.XX")
# Check price is present (formatted as "EUR XX.XX")
price_text = f"{float(item.default_price):.2f}"
price_box = item_container.locator('.pretix-widget-pricebox')
expect(price_box).to_contain_text(price_text)
@@ -154,8 +151,8 @@ class TestWidgetEventInfo:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
organizer,
event,
widget_page
):
"""
@@ -165,10 +162,9 @@ class TestWidgetEventInfo:
date by default. This test verifies the widget loads without
checking for specific date display.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug
widget_page.goto(
live_server_url, organizer.slug, event.slug
)
widget_page.wait_for_widget_load()
# Widget should be present and functional
# (Event date display varies by configuration)
@@ -179,8 +175,8 @@ class TestWidgetEventInfo:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
organizer,
event,
widget_page
):
"""

View File

@@ -18,16 +18,15 @@ class TestErrorDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
organizer,
event,
widget_page
):
"""
Loading a non-existent event should show an error message.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, 'nonexistent-event')
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, 'nonexistent-event')
# Should show error message
expect(page.locator(
@@ -38,17 +37,16 @@ class TestErrorDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
organizer,
event,
widget_page
):
"""
Error state should include a link to open the ticket shop
in a new tab as a fallback.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, 'nonexistent-event')
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, 'nonexistent-event')
# Should show fallback action link
action_link = page.locator('.pretix-widget-error-action a')
@@ -66,19 +64,18 @@ class TestSoldOutState:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_sold_out,
organizer,
event,
item_sold_out,
widget_page
):
"""
Items with zero quota should show as unavailable/sold out.
"""
item = widget_item_sold_out
item = item_sold_out
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{item.name}")')
@@ -93,20 +90,19 @@ class TestSoldOutState:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_sold_out,
organizer,
event,
item_sold_out,
widget_page
):
"""
Sold out items should show a status message like
"Sold out" or "Currently unavailable".
"""
item = widget_item_sold_out
item = item_sold_out
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{item.name}")')

View File

@@ -19,18 +19,17 @@ class TestItemPicture:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_picture,
organizer,
event,
item_with_picture,
widget_page
):
"""Item with picture should display a thumbnail image."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
expect(item_elem).to_be_visible()
# Should have picture element
@@ -41,18 +40,17 @@ class TestItemPicture:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_picture,
organizer,
event,
item_with_picture,
widget_page
):
"""Item picture should have alt text."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
img = item_elem.locator('.pretix-widget-item-picture')
alt = img.get_attribute('alt')
@@ -62,18 +60,17 @@ class TestItemPicture:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_picture,
organizer,
event,
item_with_picture,
widget_page
):
"""Picture should be wrapped in a clickable link."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
link = item_elem.locator('.pretix-widget-item-picture-link')
expect(link).to_be_visible()
@@ -84,18 +81,17 @@ class TestItemPicture:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_picture,
organizer,
event,
item_with_picture,
widget_page
):
"""Item row should have pretix-widget-item-with-picture class."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item-with-picture:has-text("{widget_item_with_picture.name}")')
f'.pretix-widget-item-with-picture:has-text("{item_with_picture.name}")')
expect(item_elem).to_be_visible()
@@ -107,18 +103,17 @@ class TestLightbox:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_picture,
organizer,
event,
item_with_picture,
widget_page
):
"""Clicking item picture should open lightbox overlay."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
# Click the picture link
link = item_elem.locator('.pretix-widget-item-picture-link')
@@ -132,18 +127,17 @@ class TestLightbox:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_picture,
organizer,
event,
item_with_picture,
widget_page
):
"""Lightbox should display fullsize image."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
item_elem.locator('.pretix-widget-item-picture-link').click()
# Wait for lightbox
@@ -157,18 +151,17 @@ class TestLightbox:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_picture,
organizer,
event,
item_with_picture,
widget_page
):
"""Lightbox close button should close the overlay."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
item_elem.locator('.pretix-widget-item-picture-link').click()
# Wait for lightbox to appear
@@ -187,18 +180,17 @@ class TestLightbox:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_picture,
organizer,
event,
item_with_picture,
widget_page
):
"""Lightbox dialog should have role='alertdialog'."""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
item_elem.locator('.pretix-widget-item-picture-link').click()
page.wait_for_timeout(1000)

View File

@@ -19,17 +19,16 @@ class TestLoadingStates:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Loading spinner should be hidden once widget content loads.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Loading spinner should be hidden (display:none)
loading = page.locator('.pretix-widget-loading')
@@ -39,32 +38,32 @@ class TestLoadingStates:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Widget should fully load within 15 seconds.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.goto(
live_server_url, organizer.slug, event.slug, wait=False)
# Widget should appear within 15s
page.wait_for_selector('.pretix-widget', timeout=15000)
# Items should be visible within 15s total
expect(page.locator(
f'.pretix-widget-item:has-text("{widget_items[0].name}")'
f'.pretix-widget-item:has-text("{items[0].name}")'
)).to_be_visible(timeout=15000)
def test_no_javascript_errors_on_load(
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -73,9 +72,8 @@ class TestLoadingStates:
errors = []
page.on('pageerror', lambda err: errors.append(str(err)))
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# No JS errors should have occurred
assert len(errors) == 0, f"JavaScript errors: {errors}"
@@ -89,18 +87,17 @@ class TestWidgetReload:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Widget CSS should load and apply styles.
No SCSS syntax should leak into rendered styles.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Check that widget has actual styled dimensions
# (not zero-height which would indicate CSS failure)

View File

@@ -20,34 +20,33 @@ class TestPriceDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Item prices should display with currency code.
Currency format should match event settings (USD).
Currency format should match event settings (EUR).
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Check that prices are displayed with USD currency code
# General Admission is USD 50.00
# USD is in a separate span, so check for both parts
# Use .first since there are multiple items with USD
expect(page.locator('text=/USD/').first).to_be_visible()
# Check that prices are displayed with EUR currency code
# General Admission is EUR 50.00
# EUR is in a separate span, so check for both parts
# Use .first since there are multiple items with EUR
expect(page.locator('text=/EUR/').first).to_be_visible()
expect(page.locator('text=/50\\.00/').first).to_be_visible()
def test_free_items_display_free_text(
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_free,
organizer,
event,
item_free,
widget_page
):
"""
@@ -55,11 +54,10 @@ class TestPriceDisplay:
Makes free items more obvious to users.
"""
item = widget_item_free
item = item_free
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Should show "FREE" text
item_elem = page.locator(
@@ -73,22 +71,21 @@ class TestPriceDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_decimals,
organizer,
event,
item_with_decimals,
widget_page
):
"""
Prices should display with proper decimal formatting.
USD should show 2 decimal places (e.g., $25.00 not $25).
EUR should show 2 decimal places (e.g., $25.00 not $25).
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Should show price with .50
expect(page.locator('text=/USD.*12\\.50/')).to_be_visible()
expect(page.locator('text=/EUR.*12\\.50/')).to_be_visible()
@pytest.mark.django_db
@@ -99,9 +96,9 @@ class TestTaxDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_tax,
organizer,
event,
item_with_tax,
widget_page
):
"""
@@ -109,11 +106,10 @@ class TestTaxDisplay:
Should display "incl. X% VAT" or similar.
"""
item = widget_item_with_tax
item = item_with_tax
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{item.name}")')
@@ -128,9 +124,9 @@ class TestTaxDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -138,13 +134,12 @@ class TestTaxDisplay:
Tax line should be absent for tax-free items.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# widget_items by default have no tax
# items by default have no tax
# We just verify the items display without errors
for item in widget_items:
for item in items:
item_elem = page.locator(
f'.pretix-widget-item:has-text("{item.name}")')
expect(item_elem).to_be_visible()
@@ -158,9 +153,9 @@ class TestDiscountedPricing:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -168,18 +163,17 @@ class TestDiscountedPricing:
This is a smoke test to ensure price rendering works.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# All items should display their prices
for item in widget_items:
for item in items:
item_elem = page.locator(
f'.pretix-widget-item:has-text("{item.name}")')
expect(item_elem).to_be_visible()
# Should have USD currency and price displayed
expect(item_elem.locator('text=/USD/')).to_be_visible()
# Should have EUR currency and price displayed
expect(item_elem.locator('text=/EUR/')).to_be_visible()
@pytest.mark.django_db
@@ -190,9 +184,9 @@ class TestPriceForVariations:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""
@@ -200,11 +194,10 @@ class TestPriceForVariations:
E.g., "$20.00 - $30.00" for variations from $20 to $30.
"""
item, _ = widget_item_with_variations
item, _ = item_with_variations
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Variations are collapsed by default
item_elem = page.locator(
@@ -212,12 +205,12 @@ class TestPriceForVariations:
expect(item_elem).to_be_visible()
# Should show price range in main row (not in hidden variations)
# Format: USD 20.00 30.00 (en-dash, not hyphen)
# Format: EUR 20.00 30.00 (en-dash, not hyphen)
# Look specifically in the main row's price column
main_row = item_elem.locator('.pretix-widget-main-item-row')
price_col = main_row.locator('.pretix-widget-item-price-col')
expect(
price_col.locator('text=/USD.*20\\.00/')
price_col.locator('text=/EUR.*20\\.00/')
).to_be_visible()
expect(price_col.locator('text=/30\\.00/')).to_be_visible()
@@ -225,9 +218,9 @@ class TestPriceForVariations:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""
@@ -235,16 +228,15 @@ class TestPriceForVariations:
Each size should show its own price.
"""
item, variations = widget_item_with_variations
item, variations = item_with_variations
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Expand variations
widget_page.expand_variations(item.name)
# Check each variation shows a price with USD currency
# Check each variation shows a price with EUR currency
# We don't check exact amounts because formatting may vary
for var in variations:
var_elem = page.locator(
@@ -253,5 +245,5 @@ class TestPriceForVariations:
)
expect(var_elem).to_be_visible()
# Should contain USD currency code
expect(var_elem.locator('text=/USD/')).to_be_visible()
# Should contain EUR currency code
expect(var_elem.locator('text=/EUR/')).to_be_visible()

View File

@@ -19,9 +19,9 @@ class TestQuantityControls:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_single_select,
organizer,
event,
item_single_select,
widget_page
):
"""
@@ -30,10 +30,9 @@ class TestQuantityControls:
For items limited to 1 per order, a checkbox is more intuitive than
a number input.
"""
item = widget_item_single_select
item = item_single_select
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
# Find the item
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
@@ -47,9 +46,9 @@ class TestQuantityControls:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_single_select,
organizer,
event,
item_single_select,
widget_page
):
"""
@@ -60,10 +59,9 @@ class TestQuantityControls:
Note: When there's only one item, it may be auto-selected by the widget.
This test verifies the check/uncheck functionality works regardless.
"""
item = widget_item_single_select
item = item_single_select
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
checkbox = item_elem.locator('input[type="checkbox"]')
@@ -85,9 +83,9 @@ class TestQuantityControls:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -95,11 +93,10 @@ class TestQuantityControls:
Plus/minus buttons provide an easy way to increment/decrement quantity.
"""
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
# Regular items should have number input with +/- buttons
item_elem = page.locator(f'.pretix-widget-item:has-text("{widget_items[0].name}")')
item_elem = page.locator(f'.pretix-widget-item:has-text("{items[0].name}")')
# Should have number input
expect(item_elem.locator('input[type="number"]')).to_be_visible()
@@ -112,9 +109,9 @@ class TestQuantityControls:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -122,10 +119,9 @@ class TestQuantityControls:
Each click increments the value in the number input.
"""
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
item_elem = page.locator(f'.pretix-widget-item:has-text("{widget_items[0].name}")')
item_elem = page.locator(f'.pretix-widget-item:has-text("{items[0].name}")')
number_input = item_elem.locator('input[type="number"]')
plus_button = item_elem.locator('button:has-text("+")').first
@@ -151,9 +147,9 @@ class TestQuantityControls:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -161,10 +157,9 @@ class TestQuantityControls:
Quantity should not go below 0.
"""
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
item_elem = page.locator(f'.pretix-widget-item:has-text("{widget_items[0].name}")')
item_elem = page.locator(f'.pretix-widget-item:has-text("{items[0].name}")')
number_input = item_elem.locator('input[type="number"]')
plus_button = item_elem.locator('button:has-text("+")').first
minus_button = item_elem.locator('button:has-text("-")').first
@@ -193,9 +188,9 @@ class TestQuantityControls:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -203,14 +198,13 @@ class TestQuantityControls:
Number input should accept typed values.
"""
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
# Directly set quantity using helper
widget_page.select_item_quantity(widget_items[0].name, 5)
widget_page.select_item_quantity(items[0].name, 5)
# Verify value
item_elem = page.locator(f'.pretix-widget-item:has-text("{widget_items[0].name}")')
item_elem = page.locator(f'.pretix-widget-item:has-text("{items[0].name}")')
number_input = item_elem.locator('input[type="number"]')
expect(number_input).to_have_value("5")
@@ -223,16 +217,15 @@ class TestOrderLimits:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_single_select,
organizer,
event,
item_single_select,
widget_page
):
"""Item with order_max=1 should show checkbox (implicit max enforcement)."""
item = widget_item_single_select # This has order_max=1
item = item_single_select # This has order_max=1
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
expect(item_elem).to_be_visible()
@@ -246,17 +239,16 @@ class TestOrderLimits:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""Multiple items with different quantities should open iframe checkout."""
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
widget_page.select_item_quantity(widget_items[0].name, 2)
widget_page.select_item_quantity(widget_items[1].name, 1)
widget_page.select_item_quantity(items[0].name, 2)
widget_page.select_item_quantity(items[1].name, 1)
widget_page.click_buy_button()
@@ -276,9 +268,9 @@ class TestFreePrice:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_free_price,
organizer,
event,
item_free_price,
widget_page
):
"""
@@ -286,10 +278,9 @@ class TestFreePrice:
User should be able to enter their own price.
"""
item = widget_item_free_price
item = item_free_price
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
expect(item_elem).to_be_visible()
@@ -302,9 +293,9 @@ class TestFreePrice:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_free_price,
organizer,
event,
item_free_price,
widget_page
):
"""
@@ -312,10 +303,9 @@ class TestFreePrice:
The min attribute should be set to the item's default price.
"""
item = widget_item_free_price
item = item_free_price
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
price_input = item_elem.locator('.pretix-widget-pricebox-price-input, input[name^="price_"]').first
@@ -329,9 +319,9 @@ class TestFreePrice:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_free_price,
organizer,
event,
item_free_price,
widget_page
):
"""
@@ -339,10 +329,9 @@ class TestFreePrice:
Amount above minimum should be accepted.
"""
item = widget_item_free_price
item = item_free_price
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
price_input = item_elem.locator('.pretix-widget-pricebox-price-input, input[name^="price_"]').first

View File

@@ -18,9 +18,9 @@ class TestResponsiveLayout:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -30,9 +30,8 @@ class TestResponsiveLayout:
# Set narrow viewport
page.set_viewport_size({"width": 375, "height": 667})
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Should have mobile class
expect(page.locator('.pretix-widget-mobile')).to_be_visible()
@@ -41,9 +40,9 @@ class TestResponsiveLayout:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -53,9 +52,8 @@ class TestResponsiveLayout:
# Ensure wide viewport (default is 1280)
page.set_viewport_size({"width": 1280, "height": 720})
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Should NOT have mobile class
expect(page.locator('.pretix-widget-mobile')).to_have_count(0)
@@ -67,9 +65,9 @@ class TestResponsiveLayout:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
@@ -79,9 +77,8 @@ class TestResponsiveLayout:
# Start with desktop viewport
page.set_viewport_size({"width": 1280, "height": 720})
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Should not be mobile
expect(page.locator('.pretix-widget-mobile')).to_have_count(0)

View File

@@ -19,9 +19,9 @@ class TestProductVariations:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""
@@ -29,10 +29,9 @@ class TestProductVariations:
Variations should be collapsed by default with a button to expand them.
"""
item, variations = widget_item_with_variations
item, variations = item_with_variations
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
# Should show item name
expect(page.locator(f'text="{item.name}"')).to_be_visible()
@@ -46,9 +45,9 @@ class TestProductVariations:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""
@@ -56,10 +55,9 @@ class TestProductVariations:
After expanding, all variation options should be visible.
"""
item, variations = widget_item_with_variations
item, variations = item_with_variations
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
# Expand variations
widget_page.expand_variations(item.name)
@@ -75,16 +73,15 @@ class TestProductVariations:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""Clicking toggle again should collapse variations."""
item, variations = widget_item_with_variations
item, variations = item_with_variations
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
toggle_btn = item_elem.locator('button:has-text("variants"), button:has-text("Show variants")')
@@ -111,9 +108,9 @@ class TestProductVariations:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""
@@ -121,17 +118,16 @@ class TestProductVariations:
When variations have different prices, should display range like "$20 - $30".
"""
item, variations = widget_item_with_variations
item, variations = item_with_variations
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
# Should show price range
# Variations go from $20 to $30
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
# Look for price range indicators
# Format is "USD 20.00 30.00" in the main item's price box (first one)
# Format is "EUR 20.00 30.00" in the main item's price box (first one)
price_box = item_elem.locator('.pretix-widget-pricebox').first
expect(price_box).to_contain_text('20.00')
expect(price_box).to_contain_text('30.00')
@@ -140,9 +136,9 @@ class TestProductVariations:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""
@@ -150,10 +146,9 @@ class TestProductVariations:
After expanding variations, each should have its own quantity selector.
"""
item, variations = widget_item_with_variations
item, variations = item_with_variations
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
# Expand variations
widget_page.expand_variations(item.name)
@@ -171,9 +166,9 @@ class TestProductVariations:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""
@@ -181,10 +176,9 @@ class TestProductVariations:
When expanded, variations should display their specific prices.
"""
item, variations = widget_item_with_variations
item, variations = item_with_variations
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
# Expand variations
widget_page.expand_variations(item.name)
@@ -216,16 +210,15 @@ class TestVariationSubmission:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_with_variations,
organizer,
event,
item_with_variations,
widget_page
):
"""Submitting with a variation selected should open iframe checkout."""
item, variations = widget_item_with_variations
item, variations = item_with_variations
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(live_server_url, organizer.slug, event.slug)
# Expand and select a variation
widget_page.expand_variations(item.name)

View File

@@ -19,9 +19,9 @@ class TestVoucherDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_voucher,
organizer,
event,
voucher,
widget_page
):
"""
@@ -29,9 +29,8 @@ class TestVoucherDisplay:
The widget checks `vouchers_exist` in the API response.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Voucher section should be visible
voucher_section = page.locator('.pretix-widget-voucher')
@@ -51,17 +50,16 @@ class TestVoucherDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_items,
organizer,
event,
items,
widget_page
):
"""
Voucher input should not appear when no vouchers exist.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Voucher section should NOT be visible
voucher_section = page.locator('.pretix-widget-voucher')
@@ -71,23 +69,22 @@ class TestVoucherDisplay:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_voucher,
organizer,
event,
voucher,
widget_page
):
"""
Voucher explanation text should display when configured.
"""
# Set voucher explanation text on the event
widget_event.settings.set(
event.settings.set(
'voucher_explanation_text',
'Enter your voucher code to get a discount.'
)
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Explanation text should be visible
explanation = page.locator('.pretix-widget-voucher-text')
@@ -103,9 +100,9 @@ class TestVoucherRedemption:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_voucher,
organizer,
event,
voucher,
widget_page
):
"""
@@ -113,9 +110,8 @@ class TestVoucherRedemption:
With skip-ssl-check (added by test harness), this opens in iframe.
"""
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Enter voucher code
voucher_input = page.locator('.pretix-widget-voucher-input')

View File

@@ -17,9 +17,9 @@ class TestWaitingList:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_sold_out_with_waitinglist,
organizer,
event,
item_sold_out_with_waitinglist,
widget_page
):
"""
@@ -31,11 +31,10 @@ class TestWaitingList:
- Item: allow_waitinglist = True
- Item availability < 100 (sold out)
"""
item = widget_item_sold_out_with_waitinglist
item = item_sold_out_with_waitinglist
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
# Find the sold out item
item_elem = page.locator(
@@ -51,19 +50,18 @@ class TestWaitingList:
self,
page: Page,
live_server_url: str,
widget_organizer,
widget_event,
widget_item_sold_out_with_waitinglist,
organizer,
event,
item_sold_out_with_waitinglist,
widget_page
):
"""
Waiting list link URL should include item ID parameter.
"""
item = widget_item_sold_out_with_waitinglist
item = item_sold_out_with_waitinglist
widget_page.goto_widget_test_page(
live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
widget_page.goto(
live_server_url, organizer.slug, event.slug)
item_elem = page.locator(
f'.pretix-widget-item:has-text("{item.name}")')