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 ```python
def test_example(widget_page, live_server_url, widget_event, widget_items): def test_example(widget_page, live_server_url, widget_event, widget_items):
# Navigate to event # Navigate to event and wait for widget to load
widget_page.goto_event(live_server_url, 'testorg', 'testevent') widget_page.goto(live_server_url, 'testorg', 'testevent')
# Wait for widget to load
widget_page.wait_for_widget_load()
# Select item quantity # Select item quantity
widget_page.select_item_quantity('General Admission', 2) 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 ```python
@pytest.mark.django_db @pytest.mark.django_db
def test_widget_loads(page, live_server_url, widget_organizer, widget_event, widget_items, widget_page): 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.goto(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
# Verify content # Verify content
expect(page.locator(f'text="{widget_event.name}"')).to_be_visible() 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): def test_variations(page, live_server_url, widget_organizer, widget_event, widget_item_with_variations, widget_page):
item, variations = widget_item_with_variations item, variations = widget_item_with_variations
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug) widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
# Expand variations # Expand variations
widget_page.expand_variations(item.name) widget_page.expand_variations(item.name)
@@ -207,8 +202,7 @@ def test_variations(page, live_server_url, widget_organizer, widget_event, widge
```python ```python
@pytest.mark.django_db @pytest.mark.django_db
def test_checkout(page, context, live_server_url, widget_organizer, widget_event, widget_items, widget_page): 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.goto(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
# Add items # Add items
widget_page.select_item_quantity(widget_items[0].name, 2) widget_page.select_item_quantity(widget_items[0].name, 2)
@@ -447,8 +441,7 @@ class TestFeatureName:
Then: expected outcome Then: expected outcome
""" """
# Arrange # Arrange
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug) widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
widget_page.wait_for_widget_load()
# Act # Act
widget_page.select_item_quantity(widget_items[0].name, 1) 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 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 # Test Data Fixtures - Organizers and Events
# ============================================================================ # ============================================================================
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_organizer(db): def organizer(db):
""" """
Create an organizer for widget tests. Create an organizer for widget tests.
Reuses the same pattern as existing API tests. Reuses the same pattern as existing API tests.
@@ -222,15 +104,15 @@ def widget_organizer(db):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_event(widget_organizer): def event(organizer):
"""Create a basic event for widget tests.""" """Create a basic event for widget tests."""
event = Event.objects.create( event = Event.objects.create(
organizer=widget_organizer, organizer=organizer,
name='Test Event', name='Test Event',
slug='testevent', slug='testevent',
date_from=datetime(2026, 6, 1, 10, 0, 0, tzinfo=timezone.utc), date_from=datetime(2026, 6, 1, 10, 0, 0, tzinfo=timezone.utc),
date_to=datetime(2026, 6, 1, 18, 0, 0, tzinfo=timezone.utc), date_to=datetime(2026, 6, 1, 18, 0, 0, tzinfo=timezone.utc),
currency='USD', currency='EUR',
live=True, live=True,
testmode=False, testmode=False,
plugins='pretix.plugins.banktransfer', plugins='pretix.plugins.banktransfer',
@@ -243,7 +125,7 @@ def widget_event(widget_organizer):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_items(widget_event): def items(event):
"""Create basic test items/products.""" """Create basic test items/products."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
@@ -251,14 +133,14 @@ def widget_items(widget_event):
# Create a proper category # Create a proper category
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Tickets', name='Tickets',
position=0 position=0
) )
# General Admission ticket # General Admission ticket
item1 = Item.objects.create( item1 = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='General Admission', name='General Admission',
default_price=Decimal('50.00'), default_price=Decimal('50.00'),
@@ -269,7 +151,7 @@ def widget_items(widget_event):
# VIP ticket # VIP ticket
item2 = Item.objects.create( item2 = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='VIP Ticket', name='VIP Ticket',
default_price=Decimal('150.00'), default_price=Decimal('150.00'),
@@ -281,7 +163,7 @@ def widget_items(widget_event):
# Create quotas for each item # Create quotas for each item
for item in items: for item in items:
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name=f'{item.name} Quota', name=f'{item.name} Quota',
size=100, size=100,
) )
@@ -296,19 +178,19 @@ def widget_items(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_with_variations(widget_event): def item_with_variations(event):
"""Create an item with size variations (S, M, L, XL).""" """Create an item with size variations (S, M, L, XL)."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
# Create category for the item # Create category for the item
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Merchandise', name='Merchandise',
position=1 position=1
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Event T-Shirt', name='Event T-Shirt',
default_price=Decimal('25.00'), default_price=Decimal('25.00'),
@@ -334,7 +216,7 @@ def widget_item_with_variations(widget_event):
# Create quota for all variations # Create quota for all variations
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='T-Shirt Quota', name='T-Shirt Quota',
size=50, size=50,
) )
@@ -348,18 +230,18 @@ def widget_item_with_variations(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @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).""" """Create an item with max_per_order=1 (should show checkbox)."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='VIP', name='VIP',
position=2 position=2
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='VIP Pass', name='VIP Pass',
default_price=Decimal('500.00'), default_price=Decimal('500.00'),
@@ -369,7 +251,7 @@ def widget_item_single_select(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='VIP Quota', name='VIP Quota',
size=10, size=10,
) )
@@ -380,18 +262,18 @@ def widget_item_single_select(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_free_price(widget_event): def item_free_price(event):
"""Create an item with pay-what-you-want pricing.""" """Create an item with pay-what-you-want pricing."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Donations', name='Donations',
position=3 position=3
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Donation', name='Donation',
default_price=Decimal('10.00'), default_price=Decimal('10.00'),
@@ -401,7 +283,7 @@ def widget_item_free_price(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Donation Quota', name='Donation Quota',
size=999, size=999,
) )
@@ -412,18 +294,18 @@ def widget_item_free_price(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_sold_out(widget_event): def item_sold_out(event):
"""Create a sold out item.""" """Create a sold out item."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Early Bird', name='Early Bird',
position=4 position=4
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Early Bird Ticket', name='Early Bird Ticket',
default_price=Decimal('30.00'), default_price=Decimal('30.00'),
@@ -433,7 +315,7 @@ def widget_item_sold_out(widget_event):
# Create quota with size=0 (sold out) # Create quota with size=0 (sold out)
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Early Bird Quota', name='Early Bird Quota',
size=0, size=0,
) )
@@ -444,18 +326,18 @@ def widget_item_sold_out(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_free(widget_event): def item_free(event):
"""Create a free item (price = 0.00).""" """Create a free item (price = 0.00)."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Free Stuff', name='Free Stuff',
position=10 position=10
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Free Gift', name='Free Gift',
default_price=Decimal('0.00'), default_price=Decimal('0.00'),
@@ -463,7 +345,7 @@ def widget_item_free(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Free Gift Quota', name='Free Gift Quota',
size=100, size=100,
) )
@@ -474,18 +356,18 @@ def widget_item_free(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_with_decimals(widget_event): def item_with_decimals(event):
"""Create an item with non-zero decimal price.""" """Create an item with non-zero decimal price."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Test Category', name='Test Category',
position=11 position=11
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Half Price Item', name='Half Price Item',
default_price=Decimal('12.50'), default_price=Decimal('12.50'),
@@ -493,7 +375,7 @@ def widget_item_with_decimals(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Half Price Quota', name='Half Price Quota',
size=50, size=50,
) )
@@ -504,25 +386,25 @@ def widget_item_with_decimals(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_with_tax(widget_event): def item_with_tax(event):
"""Create an item with tax rule.""" """Create an item with tax rule."""
from pretix.base.models import ItemCategory, TaxRule from pretix.base.models import ItemCategory, TaxRule
# Create tax rule # Create tax rule
tax_rule = TaxRule.objects.create( tax_rule = TaxRule.objects.create(
event=widget_event, event=event,
name='VAT', name='VAT',
rate=Decimal('19.00'), # 19% VAT rate=Decimal('19.00'), # 19% VAT
) )
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Taxed Items', name='Taxed Items',
position=12 position=12
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Taxed Product', name='Taxed Product',
default_price=Decimal('100.00'), default_price=Decimal('100.00'),
@@ -531,7 +413,7 @@ def widget_item_with_tax(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Taxed Product Quota', name='Taxed Product Quota',
size=50, size=50,
) )
@@ -546,18 +428,18 @@ def widget_item_with_tax(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_min_order(widget_event): def item_min_order(event):
"""Create an item with min_per_order=2.""" """Create an item with min_per_order=2."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Group Tickets', name='Group Tickets',
position=13 position=13
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Group Pass', name='Group Pass',
default_price=Decimal('40.00'), default_price=Decimal('40.00'),
@@ -566,7 +448,7 @@ def widget_item_min_order(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Group Pass Quota', name='Group Pass Quota',
size=50, size=50,
) )
@@ -577,18 +459,18 @@ def widget_item_min_order(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_special_chars(widget_event): def item_special_chars(event):
"""Create an item with special characters in the name.""" """Create an item with special characters in the name."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Spezial', name='Spezial',
position=14 position=14
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Böhm & Söhne Konzert', name='Böhm & Söhne Konzert',
default_price=Decimal('55.00'), default_price=Decimal('55.00'),
@@ -596,7 +478,7 @@ def widget_item_special_chars(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Special Quota', name='Special Quota',
size=50, size=50,
) )
@@ -611,19 +493,19 @@ def widget_item_special_chars(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @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.""" """Create items with a category that has a description."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Tickets', name='Tickets',
description='Early bird tickets available', description='Early bird tickets available',
position=0 position=0
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Early Bird', name='Early Bird',
default_price=Decimal('35.00'), default_price=Decimal('35.00'),
@@ -631,7 +513,7 @@ def widget_items_with_category_description(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Early Bird Quota', name='Early Bird Quota',
size=100, size=100,
) )
@@ -642,24 +524,24 @@ def widget_items_with_category_description(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_items_multiple_categories(widget_event): def items_multiple_categories(event):
"""Create items in multiple categories to test grouping and ordering.""" """Create items in multiple categories to test grouping and ordering."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
cat_music = ItemCategory.objects.create( cat_music = ItemCategory.objects.create(
event=widget_event, event=event,
name='Music', name='Music',
position=0 position=0
) )
cat_food = ItemCategory.objects.create( cat_food = ItemCategory.objects.create(
event=widget_event, event=event,
name='Food & Drink', name='Food & Drink',
position=1 position=1
) )
item1 = Item.objects.create( item1 = Item.objects.create(
event=widget_event, event=event,
category=cat_music, category=cat_music,
name='Concert Ticket', name='Concert Ticket',
default_price=Decimal('75.00'), default_price=Decimal('75.00'),
@@ -667,7 +549,7 @@ def widget_items_multiple_categories(widget_event):
) )
item2 = Item.objects.create( item2 = Item.objects.create(
event=widget_event, event=event,
category=cat_food, category=cat_food,
name='Food Pass', name='Food Pass',
default_price=Decimal('25.00'), default_price=Decimal('25.00'),
@@ -676,7 +558,7 @@ def widget_items_multiple_categories(widget_event):
for item in [item1, item2]: for item in [item1, item2]:
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name=f'{item.name} Quota', name=f'{item.name} Quota',
size=100, size=100,
) )
@@ -691,33 +573,33 @@ def widget_items_multiple_categories(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_voucher(widget_event, widget_items): def voucher(event, items):
"""Create a voucher for the event.""" """Create a voucher for the event."""
voucher = Voucher.objects.create( voucher = Voucher.objects.create(
event=widget_event, event=event,
code='TESTCODE2024', code='TESTCODE2024',
max_usages=10, max_usages=10,
price_mode='none', price_mode='none',
) )
# Clear the vouchers_exist cache so the widget picks it up # 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 return voucher
@pytest.fixture @pytest.fixture
@scopes_disabled() @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.""" """Create a voucher tied to a specific item."""
item = widget_items[0] item = items[0]
voucher = Voucher.objects.create( voucher = Voucher.objects.create(
event=widget_event, event=event,
code='ITEMVOUCHER', code='ITEMVOUCHER',
max_usages=5, max_usages=5,
price_mode='percent', price_mode='percent',
value=Decimal('20.00'), # 20% off value=Decimal('20.00'), # 20% off
item=item, item=item,
) )
widget_event.get_cache().delete('vouchers_exist') event.get_cache().delete('vouchers_exist')
return voucher return voucher
@@ -727,21 +609,21 @@ def widget_voucher_with_item(widget_event, widget_items):
@pytest.fixture @pytest.fixture
@scopes_disabled() @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.""" """Create a sold out item with waiting list enabled."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
# Enable waiting list on the event # 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( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Sold Out', name='Sold Out',
position=20 position=20
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Sold Out Concert', name='Sold Out Concert',
default_price=Decimal('80.00'), 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) # Create quota with size=0 (sold out)
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Sold Out Quota', name='Sold Out Quota',
size=0, size=0,
) )
@@ -766,17 +648,17 @@ def widget_item_sold_out_with_waitinglist(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_event_series(widget_organizer): def event_series(organizer):
"""Create an event series with multiple subevents, items, and quotas.""" """Create an event series with multiple subevents, items, and quotas."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
event = Event.objects.create( event = Event.objects.create(
organizer=widget_organizer, organizer=organizer,
name='Concert Series', name='Concert Series',
slug='concert-series', slug='concert-series',
date_from=datetime(2026, 6, 1, 19, 0, 0, tzinfo=timezone.utc), date_from=datetime(2026, 6, 1, 19, 0, 0, tzinfo=timezone.utc),
has_subevents=True, has_subevents=True,
currency='USD', currency='EUR',
live=True, live=True,
plugins='pretix.plugins.banktransfer', plugins='pretix.plugins.banktransfer',
) )
@@ -839,15 +721,16 @@ def widget_page(page):
def __init__(self, page: Page): def __init__(self, page: Page):
self.page = page self.page = page
def goto_widget_test_page( def goto(
self, self,
live_server_url: str, live_server_url: str,
org_slug: str, org_slug: str,
event_slug: str, event_slug: str,
wait=True,
**widget_attrs **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 Uses a Django view that serves an HTML page with the pretix
widget embedded, simulating how it would be used on a customer's 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, Extra keyword arguments are passed as query params to the view,
which converts them to widget attributes. For boolean attributes which converts them to widget attributes. For boolean attributes
(like disable-vouchers), pass an empty string as value. (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 # Navigate to the test view URL
test_url = f"{live_server_url}/widget-test/{org_slug}/{event_slug}/" 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 from urllib.parse import urlencode
test_url += '?' + urlencode(widget_attrs) test_url += '?' + urlencode(widget_attrs)
self.page.goto(test_url) self.page.goto(test_url)
return self if wait:
self.wait_for_widget_load()
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}/")
return self return self
def wait_for_widget_load(self): def wait_for_widget_load(self):
@@ -975,18 +858,18 @@ def widget_page(page):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_require_voucher(widget_event): def item_require_voucher(event):
"""Create an item that requires a voucher to purchase.""" """Create an item that requires a voucher to purchase."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Voucher Only', name='Voucher Only',
position=30 position=30
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Exclusive Pass', name='Exclusive Pass',
default_price=Decimal('200.00'), default_price=Decimal('200.00'),
@@ -995,7 +878,7 @@ def widget_item_require_voucher(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Exclusive Quota', name='Exclusive Quota',
size=50, size=50,
) )
@@ -1006,18 +889,18 @@ def widget_item_require_voucher(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_low_stock(widget_event): def item_low_stock(event):
"""Create an item with low stock (quota_left visible).""" """Create an item with low stock (quota_left visible)."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Limited', name='Limited',
position=31 position=31
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Last Chance Ticket', name='Last Chance Ticket',
default_price=Decimal('65.00'), default_price=Decimal('65.00'),
@@ -1025,32 +908,32 @@ def widget_item_low_stock(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Limited Quota', name='Limited Quota',
size=3, size=3,
) )
quota.items.add(item) quota.items.add(item)
# Enable "show quota left" on the event # Enable "show quota left" on the event
widget_event.settings.set('show_quota_left', True) event.settings.set('show_quota_left', True)
return item return item
@pytest.fixture @pytest.fixture
@scopes_disabled() @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).""" """Create an item that is not yet available (future available_from)."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Coming Soon', name='Coming Soon',
position=32 position=32
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Future Ticket', name='Future Ticket',
default_price=Decimal('45.00'), default_price=Decimal('45.00'),
@@ -1060,7 +943,7 @@ def widget_item_not_yet_available(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Future Quota', name='Future Quota',
size=100, size=100,
) )
@@ -1075,7 +958,7 @@ def widget_item_not_yet_available(widget_event):
@pytest.fixture @pytest.fixture
@scopes_disabled() @scopes_disabled()
def widget_item_with_picture(widget_event): def item_with_picture(event):
"""Create an item with a product picture.""" """Create an item with a product picture."""
from pretix.base.models import ItemCategory from pretix.base.models import ItemCategory
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
@@ -1083,7 +966,7 @@ def widget_item_with_picture(widget_event):
from PIL import Image as PILImage from PIL import Image as PILImage
category = ItemCategory.objects.create( category = ItemCategory.objects.create(
event=widget_event, event=event,
name='Gallery Items', name='Gallery Items',
position=40 position=40
) )
@@ -1101,7 +984,7 @@ def widget_item_with_picture(widget_event):
) )
item = Item.objects.create( item = Item.objects.create(
event=widget_event, event=event,
category=category, category=category,
name='Art Print', name='Art Print',
default_price=Decimal('35.00'), default_price=Decimal('35.00'),
@@ -1111,7 +994,7 @@ def widget_item_with_picture(widget_event):
) )
quota = Quota.objects.create( quota = Quota.objects.create(
event=widget_event, event=event,
name='Art Print Quota', name='Art Print Quota',
size=50, size=50,
) )
@@ -1144,3 +1027,118 @@ def cross_browser_page(request, playwright):
page.close() page.close()
context.close() context.close()
browser.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( def test_css_contains_no_scss_syntax(
page: Page, page: Page,
live_server_url: str, live_server_url: str,
widget_organizer, organizer,
widget_event, event,
widget_items items
): ):
"""Verify CSS is compiled and doesn't contain SCSS syntax.""" """Verify CSS is compiled and doesn't contain SCSS syntax."""
# Fetch the CSS directly # 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}") print(f"\nFetching CSS from: {css_url}")
response = page.request.get(css_url) response = page.request.get(css_url)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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