mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
drop widget_ prefix from e2e test fixtures
This commit is contained in:
@@ -145,11 +145,8 @@ The `widget_page` fixture provides convenient methods:
|
||||
|
||||
```python
|
||||
def test_example(widget_page, live_server_url, widget_event, widget_items):
|
||||
# Navigate to event
|
||||
widget_page.goto_event(live_server_url, 'testorg', 'testevent')
|
||||
|
||||
# Wait for widget to load
|
||||
widget_page.wait_for_widget_load()
|
||||
# Navigate to event and wait for widget to load
|
||||
widget_page.goto(live_server_url, 'testorg', 'testevent')
|
||||
|
||||
# Select item quantity
|
||||
widget_page.select_item_quantity('General Admission', 2)
|
||||
@@ -177,8 +174,7 @@ def test_example(widget_page, live_server_url, widget_event, widget_items):
|
||||
```python
|
||||
@pytest.mark.django_db
|
||||
def test_widget_loads(page, live_server_url, widget_organizer, widget_event, widget_items, widget_page):
|
||||
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
|
||||
# Verify content
|
||||
expect(page.locator(f'text="{widget_event.name}"')).to_be_visible()
|
||||
@@ -191,8 +187,7 @@ def test_widget_loads(page, live_server_url, widget_organizer, widget_event, wid
|
||||
def test_variations(page, live_server_url, widget_organizer, widget_event, widget_item_with_variations, widget_page):
|
||||
item, variations = widget_item_with_variations
|
||||
|
||||
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
|
||||
# Expand variations
|
||||
widget_page.expand_variations(item.name)
|
||||
@@ -207,8 +202,7 @@ def test_variations(page, live_server_url, widget_organizer, widget_event, widge
|
||||
```python
|
||||
@pytest.mark.django_db
|
||||
def test_checkout(page, context, live_server_url, widget_organizer, widget_event, widget_items, widget_page):
|
||||
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
|
||||
# Add items
|
||||
widget_page.select_item_quantity(widget_items[0].name, 2)
|
||||
@@ -447,8 +441,7 @@ class TestFeatureName:
|
||||
Then: expected outcome
|
||||
"""
|
||||
# Arrange
|
||||
widget_page.goto_event(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
|
||||
# Act
|
||||
widget_page.select_item_quantity(widget_items[0].name, 1)
|
||||
|
||||
@@ -84,131 +84,13 @@ def live_server_url(live_server, settings):
|
||||
|
||||
return live_server.url
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def _register_widget_test_view():
|
||||
"""
|
||||
Register a test view that serves an HTML page with widget embedded.
|
||||
|
||||
This allows E2E tests to navigate to a real URL instead of using
|
||||
set_content, which causes CORS issues.
|
||||
"""
|
||||
from django.http import HttpResponse
|
||||
from django.views import View
|
||||
from django.urls import path
|
||||
from pretix.multidomain import maindomain_urlconf as urls
|
||||
|
||||
class WidgetTestView(View):
|
||||
"""Serve HTML page with widget embedded for E2E testing."""
|
||||
|
||||
# Widget attributes that can be passed as query params
|
||||
WIDGET_ATTRS = [
|
||||
'items', 'categories', 'voucher', 'disable-vouchers',
|
||||
'disable-iframe', 'subevent', 'list-type',
|
||||
'display-event-info', 'skip-ssl-check',
|
||||
]
|
||||
|
||||
def get(self, request, organizer, event):
|
||||
# Build URLs exactly as in working index.html
|
||||
base_url = f"{request.scheme}://{request.get_host()}"
|
||||
event_url = f"{base_url}/{organizer}/{event}/"
|
||||
# CSS is scoped to event, JS is at root
|
||||
widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css"
|
||||
widget_js = f"{base_url}/widget/v2.en.js"
|
||||
|
||||
# Build extra attributes from query params
|
||||
extra_attrs = ''
|
||||
for attr in self.WIDGET_ATTRS:
|
||||
val = request.GET.get(attr)
|
||||
if val is not None:
|
||||
if val == '':
|
||||
# Boolean attribute (e.g., disable-vouchers)
|
||||
extra_attrs += f' {attr}'
|
||||
else:
|
||||
extra_attrs += f' {attr}="{val}"'
|
||||
|
||||
# Always add skip-ssl-check so iframe checkout works on HTTP
|
||||
if 'skip-ssl-check' not in extra_attrs:
|
||||
extra_attrs += ' skip-ssl-check'
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Widget Test</title>
|
||||
<link rel="stylesheet" type="text/css" href="{widget_css}" crossorigin>
|
||||
</head>
|
||||
<body>
|
||||
<pretix-widget event="{event_url}"{extra_attrs}></pretix-widget>
|
||||
<script type="text/javascript" src="{widget_js}" async crossorigin></script>
|
||||
</body>
|
||||
</html>"""
|
||||
resp = HttpResponse(html, content_type='text/html')
|
||||
resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'"
|
||||
return resp
|
||||
|
||||
class ButtonTestView(View):
|
||||
"""Serve HTML page with pretix-button element for E2E testing."""
|
||||
|
||||
def get(self, request, organizer, event):
|
||||
base_url = f"{request.scheme}://{request.get_host()}"
|
||||
event_url = f"{base_url}/{organizer}/{event}/"
|
||||
widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css"
|
||||
widget_js = f"{base_url}/widget/v2.en.js"
|
||||
|
||||
# Build extra attributes from query params
|
||||
extra_attrs = ''
|
||||
for attr in ['items', 'voucher', 'subevent', 'disable-iframe']:
|
||||
val = request.GET.get(attr)
|
||||
if val is not None:
|
||||
if val == '':
|
||||
extra_attrs += f' {attr}'
|
||||
else:
|
||||
extra_attrs += f' {attr}="{val}"'
|
||||
|
||||
button_text = request.GET.get('button-text', 'Buy tickets!')
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Button Test</title>
|
||||
<link rel="stylesheet" type="text/css" href="{widget_css}" crossorigin>
|
||||
</head>
|
||||
<body>
|
||||
<pretix-button event="{event_url}"{extra_attrs}>{button_text}</pretix-button>
|
||||
<script type="text/javascript" src="{widget_js}" async crossorigin></script>
|
||||
</body>
|
||||
</html>"""
|
||||
resp = HttpResponse(html, content_type='text/html')
|
||||
resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'"
|
||||
return resp
|
||||
|
||||
# Add URL patterns
|
||||
test_pattern = path(
|
||||
'widget-test/<str:organizer>/<str:event>/',
|
||||
WidgetTestView.as_view()
|
||||
)
|
||||
button_pattern = path(
|
||||
'button-test/<str:organizer>/<str:event>/',
|
||||
ButtonTestView.as_view()
|
||||
)
|
||||
|
||||
# Insert at beginning of URL patterns
|
||||
if hasattr(urls, 'urlpatterns'):
|
||||
urls.urlpatterns.insert(0, test_pattern)
|
||||
urls.urlpatterns.insert(0, button_pattern)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Test Data Fixtures - Organizers and Events
|
||||
# ============================================================================
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_organizer(db):
|
||||
def organizer(db):
|
||||
"""
|
||||
Create an organizer for widget tests.
|
||||
Reuses the same pattern as existing API tests.
|
||||
@@ -222,15 +104,15 @@ def widget_organizer(db):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_event(widget_organizer):
|
||||
def event(organizer):
|
||||
"""Create a basic event for widget tests."""
|
||||
event = Event.objects.create(
|
||||
organizer=widget_organizer,
|
||||
organizer=organizer,
|
||||
name='Test Event',
|
||||
slug='testevent',
|
||||
date_from=datetime(2026, 6, 1, 10, 0, 0, tzinfo=timezone.utc),
|
||||
date_to=datetime(2026, 6, 1, 18, 0, 0, tzinfo=timezone.utc),
|
||||
currency='USD',
|
||||
currency='EUR',
|
||||
live=True,
|
||||
testmode=False,
|
||||
plugins='pretix.plugins.banktransfer',
|
||||
@@ -243,7 +125,7 @@ def widget_event(widget_organizer):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_items(widget_event):
|
||||
def items(event):
|
||||
"""Create basic test items/products."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
@@ -251,14 +133,14 @@ def widget_items(widget_event):
|
||||
|
||||
# Create a proper category
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Tickets',
|
||||
position=0
|
||||
)
|
||||
|
||||
# General Admission ticket
|
||||
item1 = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='General Admission',
|
||||
default_price=Decimal('50.00'),
|
||||
@@ -269,7 +151,7 @@ def widget_items(widget_event):
|
||||
|
||||
# VIP ticket
|
||||
item2 = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='VIP Ticket',
|
||||
default_price=Decimal('150.00'),
|
||||
@@ -281,7 +163,7 @@ def widget_items(widget_event):
|
||||
# Create quotas for each item
|
||||
for item in items:
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name=f'{item.name} Quota',
|
||||
size=100,
|
||||
)
|
||||
@@ -296,19 +178,19 @@ def widget_items(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_with_variations(widget_event):
|
||||
def item_with_variations(event):
|
||||
"""Create an item with size variations (S, M, L, XL)."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
# Create category for the item
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Merchandise',
|
||||
position=1
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Event T-Shirt',
|
||||
default_price=Decimal('25.00'),
|
||||
@@ -334,7 +216,7 @@ def widget_item_with_variations(widget_event):
|
||||
|
||||
# Create quota for all variations
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='T-Shirt Quota',
|
||||
size=50,
|
||||
)
|
||||
@@ -348,18 +230,18 @@ def widget_item_with_variations(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_single_select(widget_event):
|
||||
def item_single_select(event):
|
||||
"""Create an item with max_per_order=1 (should show checkbox)."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='VIP',
|
||||
position=2
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='VIP Pass',
|
||||
default_price=Decimal('500.00'),
|
||||
@@ -369,7 +251,7 @@ def widget_item_single_select(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='VIP Quota',
|
||||
size=10,
|
||||
)
|
||||
@@ -380,18 +262,18 @@ def widget_item_single_select(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_free_price(widget_event):
|
||||
def item_free_price(event):
|
||||
"""Create an item with pay-what-you-want pricing."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Donations',
|
||||
position=3
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Donation',
|
||||
default_price=Decimal('10.00'),
|
||||
@@ -401,7 +283,7 @@ def widget_item_free_price(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Donation Quota',
|
||||
size=999,
|
||||
)
|
||||
@@ -412,18 +294,18 @@ def widget_item_free_price(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_sold_out(widget_event):
|
||||
def item_sold_out(event):
|
||||
"""Create a sold out item."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Early Bird',
|
||||
position=4
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Early Bird Ticket',
|
||||
default_price=Decimal('30.00'),
|
||||
@@ -433,7 +315,7 @@ def widget_item_sold_out(widget_event):
|
||||
|
||||
# Create quota with size=0 (sold out)
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Early Bird Quota',
|
||||
size=0,
|
||||
)
|
||||
@@ -444,18 +326,18 @@ def widget_item_sold_out(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_free(widget_event):
|
||||
def item_free(event):
|
||||
"""Create a free item (price = 0.00)."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Free Stuff',
|
||||
position=10
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Free Gift',
|
||||
default_price=Decimal('0.00'),
|
||||
@@ -463,7 +345,7 @@ def widget_item_free(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Free Gift Quota',
|
||||
size=100,
|
||||
)
|
||||
@@ -474,18 +356,18 @@ def widget_item_free(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_with_decimals(widget_event):
|
||||
def item_with_decimals(event):
|
||||
"""Create an item with non-zero decimal price."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Test Category',
|
||||
position=11
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Half Price Item',
|
||||
default_price=Decimal('12.50'),
|
||||
@@ -493,7 +375,7 @@ def widget_item_with_decimals(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Half Price Quota',
|
||||
size=50,
|
||||
)
|
||||
@@ -504,25 +386,25 @@ def widget_item_with_decimals(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_with_tax(widget_event):
|
||||
def item_with_tax(event):
|
||||
"""Create an item with tax rule."""
|
||||
from pretix.base.models import ItemCategory, TaxRule
|
||||
|
||||
# Create tax rule
|
||||
tax_rule = TaxRule.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='VAT',
|
||||
rate=Decimal('19.00'), # 19% VAT
|
||||
)
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Taxed Items',
|
||||
position=12
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Taxed Product',
|
||||
default_price=Decimal('100.00'),
|
||||
@@ -531,7 +413,7 @@ def widget_item_with_tax(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Taxed Product Quota',
|
||||
size=50,
|
||||
)
|
||||
@@ -546,18 +428,18 @@ def widget_item_with_tax(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_min_order(widget_event):
|
||||
def item_min_order(event):
|
||||
"""Create an item with min_per_order=2."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Group Tickets',
|
||||
position=13
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Group Pass',
|
||||
default_price=Decimal('40.00'),
|
||||
@@ -566,7 +448,7 @@ def widget_item_min_order(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Group Pass Quota',
|
||||
size=50,
|
||||
)
|
||||
@@ -577,18 +459,18 @@ def widget_item_min_order(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_special_chars(widget_event):
|
||||
def item_special_chars(event):
|
||||
"""Create an item with special characters in the name."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Spezial',
|
||||
position=14
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Böhm & Söhne Konzert',
|
||||
default_price=Decimal('55.00'),
|
||||
@@ -596,7 +478,7 @@ def widget_item_special_chars(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Special Quota',
|
||||
size=50,
|
||||
)
|
||||
@@ -611,19 +493,19 @@ def widget_item_special_chars(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_items_with_category_description(widget_event):
|
||||
def items_with_category_description(event):
|
||||
"""Create items with a category that has a description."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Tickets',
|
||||
description='Early bird tickets available',
|
||||
position=0
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Early Bird',
|
||||
default_price=Decimal('35.00'),
|
||||
@@ -631,7 +513,7 @@ def widget_items_with_category_description(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Early Bird Quota',
|
||||
size=100,
|
||||
)
|
||||
@@ -642,24 +524,24 @@ def widget_items_with_category_description(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_items_multiple_categories(widget_event):
|
||||
def items_multiple_categories(event):
|
||||
"""Create items in multiple categories to test grouping and ordering."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
cat_music = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Music',
|
||||
position=0
|
||||
)
|
||||
|
||||
cat_food = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Food & Drink',
|
||||
position=1
|
||||
)
|
||||
|
||||
item1 = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=cat_music,
|
||||
name='Concert Ticket',
|
||||
default_price=Decimal('75.00'),
|
||||
@@ -667,7 +549,7 @@ def widget_items_multiple_categories(widget_event):
|
||||
)
|
||||
|
||||
item2 = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=cat_food,
|
||||
name='Food Pass',
|
||||
default_price=Decimal('25.00'),
|
||||
@@ -676,7 +558,7 @@ def widget_items_multiple_categories(widget_event):
|
||||
|
||||
for item in [item1, item2]:
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name=f'{item.name} Quota',
|
||||
size=100,
|
||||
)
|
||||
@@ -691,33 +573,33 @@ def widget_items_multiple_categories(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_voucher(widget_event, widget_items):
|
||||
def voucher(event, items):
|
||||
"""Create a voucher for the event."""
|
||||
voucher = Voucher.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
code='TESTCODE2024',
|
||||
max_usages=10,
|
||||
price_mode='none',
|
||||
)
|
||||
# Clear the vouchers_exist cache so the widget picks it up
|
||||
widget_event.get_cache().delete('vouchers_exist')
|
||||
event.get_cache().delete('vouchers_exist')
|
||||
return voucher
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_voucher_with_item(widget_event, widget_items):
|
||||
def voucher_with_item(event, items):
|
||||
"""Create a voucher tied to a specific item."""
|
||||
item = widget_items[0]
|
||||
item = items[0]
|
||||
voucher = Voucher.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
code='ITEMVOUCHER',
|
||||
max_usages=5,
|
||||
price_mode='percent',
|
||||
value=Decimal('20.00'), # 20% off
|
||||
item=item,
|
||||
)
|
||||
widget_event.get_cache().delete('vouchers_exist')
|
||||
event.get_cache().delete('vouchers_exist')
|
||||
return voucher
|
||||
|
||||
|
||||
@@ -727,21 +609,21 @@ def widget_voucher_with_item(widget_event, widget_items):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_sold_out_with_waitinglist(widget_event):
|
||||
def item_sold_out_with_waitinglist(event):
|
||||
"""Create a sold out item with waiting list enabled."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
# Enable waiting list on the event
|
||||
widget_event.settings.set('waiting_list_enabled', True)
|
||||
event.settings.set('waiting_list_enabled', True)
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Sold Out',
|
||||
position=20
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Sold Out Concert',
|
||||
default_price=Decimal('80.00'),
|
||||
@@ -751,7 +633,7 @@ def widget_item_sold_out_with_waitinglist(widget_event):
|
||||
|
||||
# Create quota with size=0 (sold out)
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Sold Out Quota',
|
||||
size=0,
|
||||
)
|
||||
@@ -766,17 +648,17 @@ def widget_item_sold_out_with_waitinglist(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_event_series(widget_organizer):
|
||||
def event_series(organizer):
|
||||
"""Create an event series with multiple subevents, items, and quotas."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
event = Event.objects.create(
|
||||
organizer=widget_organizer,
|
||||
organizer=organizer,
|
||||
name='Concert Series',
|
||||
slug='concert-series',
|
||||
date_from=datetime(2026, 6, 1, 19, 0, 0, tzinfo=timezone.utc),
|
||||
has_subevents=True,
|
||||
currency='USD',
|
||||
currency='EUR',
|
||||
live=True,
|
||||
plugins='pretix.plugins.banktransfer',
|
||||
)
|
||||
@@ -839,15 +721,16 @@ def widget_page(page):
|
||||
def __init__(self, page: Page):
|
||||
self.page = page
|
||||
|
||||
def goto_widget_test_page(
|
||||
def goto(
|
||||
self,
|
||||
live_server_url: str,
|
||||
org_slug: str,
|
||||
event_slug: str,
|
||||
wait=True,
|
||||
**widget_attrs
|
||||
):
|
||||
"""
|
||||
Navigate to a test page with widget embedded.
|
||||
Navigate to a test page with widget embedded and wait for it to load.
|
||||
|
||||
Uses a Django view that serves an HTML page with the pretix
|
||||
widget embedded, simulating how it would be used on a customer's
|
||||
@@ -856,6 +739,9 @@ def widget_page(page):
|
||||
Extra keyword arguments are passed as query params to the view,
|
||||
which converts them to widget attributes. For boolean attributes
|
||||
(like disable-vouchers), pass an empty string as value.
|
||||
|
||||
Set wait=False to skip waiting for the widget to load (useful for
|
||||
tests that need to observe loading/error states).
|
||||
"""
|
||||
# Navigate to the test view URL
|
||||
test_url = f"{live_server_url}/widget-test/{org_slug}/{event_slug}/"
|
||||
@@ -863,11 +749,8 @@ def widget_page(page):
|
||||
from urllib.parse import urlencode
|
||||
test_url += '?' + urlencode(widget_attrs)
|
||||
self.page.goto(test_url)
|
||||
return self
|
||||
|
||||
def goto_event(self, live_server_url: str, org_slug: str, event_slug: str):
|
||||
"""Navigate to an event page."""
|
||||
self.page.goto(f"{live_server_url}/{org_slug}/{event_slug}/")
|
||||
if wait:
|
||||
self.wait_for_widget_load()
|
||||
return self
|
||||
|
||||
def wait_for_widget_load(self):
|
||||
@@ -975,18 +858,18 @@ def widget_page(page):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_require_voucher(widget_event):
|
||||
def item_require_voucher(event):
|
||||
"""Create an item that requires a voucher to purchase."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Voucher Only',
|
||||
position=30
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Exclusive Pass',
|
||||
default_price=Decimal('200.00'),
|
||||
@@ -995,7 +878,7 @@ def widget_item_require_voucher(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Exclusive Quota',
|
||||
size=50,
|
||||
)
|
||||
@@ -1006,18 +889,18 @@ def widget_item_require_voucher(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_low_stock(widget_event):
|
||||
def item_low_stock(event):
|
||||
"""Create an item with low stock (quota_left visible)."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Limited',
|
||||
position=31
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Last Chance Ticket',
|
||||
default_price=Decimal('65.00'),
|
||||
@@ -1025,32 +908,32 @@ def widget_item_low_stock(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Limited Quota',
|
||||
size=3,
|
||||
)
|
||||
quota.items.add(item)
|
||||
|
||||
# Enable "show quota left" on the event
|
||||
widget_event.settings.set('show_quota_left', True)
|
||||
event.settings.set('show_quota_left', True)
|
||||
|
||||
return item
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_not_yet_available(widget_event):
|
||||
def item_not_yet_available(event):
|
||||
"""Create an item that is not yet available (future available_from)."""
|
||||
from pretix.base.models import ItemCategory
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Coming Soon',
|
||||
position=32
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Future Ticket',
|
||||
default_price=Decimal('45.00'),
|
||||
@@ -1060,7 +943,7 @@ def widget_item_not_yet_available(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Future Quota',
|
||||
size=100,
|
||||
)
|
||||
@@ -1075,7 +958,7 @@ def widget_item_not_yet_available(widget_event):
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def widget_item_with_picture(widget_event):
|
||||
def item_with_picture(event):
|
||||
"""Create an item with a product picture."""
|
||||
from pretix.base.models import ItemCategory
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
@@ -1083,7 +966,7 @@ def widget_item_with_picture(widget_event):
|
||||
from PIL import Image as PILImage
|
||||
|
||||
category = ItemCategory.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Gallery Items',
|
||||
position=40
|
||||
)
|
||||
@@ -1101,7 +984,7 @@ def widget_item_with_picture(widget_event):
|
||||
)
|
||||
|
||||
item = Item.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
category=category,
|
||||
name='Art Print',
|
||||
default_price=Decimal('35.00'),
|
||||
@@ -1111,7 +994,7 @@ def widget_item_with_picture(widget_event):
|
||||
)
|
||||
|
||||
quota = Quota.objects.create(
|
||||
event=widget_event,
|
||||
event=event,
|
||||
name='Art Print Quota',
|
||||
size=50,
|
||||
)
|
||||
@@ -1144,3 +1027,118 @@ def cross_browser_page(request, playwright):
|
||||
page.close()
|
||||
context.close()
|
||||
browser.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def _register_widget_test_view():
|
||||
"""
|
||||
Register a test view that serves an HTML page with widget embedded.
|
||||
|
||||
This allows E2E tests to navigate to a real URL instead of using
|
||||
set_content, which causes CORS issues.
|
||||
"""
|
||||
from django.http import HttpResponse
|
||||
from django.views import View
|
||||
from django.urls import path
|
||||
from pretix.multidomain import maindomain_urlconf as urls
|
||||
|
||||
class WidgetTestView(View):
|
||||
"""Serve HTML page with widget embedded for E2E testing."""
|
||||
|
||||
# Widget attributes that can be passed as query params
|
||||
WIDGET_ATTRS = [
|
||||
'items', 'categories', 'voucher', 'disable-vouchers',
|
||||
'disable-iframe', 'subevent', 'list-type',
|
||||
'display-event-info', 'skip-ssl-check',
|
||||
]
|
||||
|
||||
def get(self, request, organizer, event):
|
||||
base_url = f"{request.scheme}://{request.get_host()}"
|
||||
event_url = f"{base_url}/{organizer}/{event}/"
|
||||
widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css"
|
||||
widget_js = f"{base_url}/widget/v2.en.js"
|
||||
|
||||
# Build extra attributes from query params
|
||||
extra_attrs = ''
|
||||
for attr in self.WIDGET_ATTRS:
|
||||
val = request.GET.get(attr)
|
||||
if val is not None:
|
||||
if val == '':
|
||||
# Boolean attribute (e.g., disable-vouchers)
|
||||
extra_attrs += f' {attr}'
|
||||
else:
|
||||
extra_attrs += f' {attr}="{val}"'
|
||||
|
||||
# Always add skip-ssl-check so iframe checkout works on HTTP
|
||||
if 'skip-ssl-check' not in extra_attrs:
|
||||
extra_attrs += ' skip-ssl-check'
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Widget Test</title>
|
||||
<link rel="stylesheet" type="text/css" href="{widget_css}" crossorigin>
|
||||
</head>
|
||||
<body>
|
||||
<pretix-widget event="{event_url}"{extra_attrs}></pretix-widget>
|
||||
<script type="text/javascript" src="{widget_js}" async crossorigin></script>
|
||||
</body>
|
||||
</html>"""
|
||||
resp = HttpResponse(html, content_type='text/html')
|
||||
resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'"
|
||||
return resp
|
||||
|
||||
class ButtonTestView(View):
|
||||
"""Serve HTML page with pretix-button element for E2E testing."""
|
||||
|
||||
def get(self, request, organizer, event):
|
||||
base_url = f"{request.scheme}://{request.get_host()}"
|
||||
event_url = f"{base_url}/{organizer}/{event}/"
|
||||
widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css"
|
||||
widget_js = f"{base_url}/widget/v2.en.js"
|
||||
|
||||
# Build extra attributes from query params
|
||||
extra_attrs = ''
|
||||
for attr in ['items', 'voucher', 'subevent', 'disable-iframe']:
|
||||
val = request.GET.get(attr)
|
||||
if val is not None:
|
||||
if val == '':
|
||||
extra_attrs += f' {attr}'
|
||||
else:
|
||||
extra_attrs += f' {attr}="{val}"'
|
||||
|
||||
button_text = request.GET.get('button-text', 'Buy tickets!')
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Button Test</title>
|
||||
<link rel="stylesheet" type="text/css" href="{widget_css}" crossorigin>
|
||||
</head>
|
||||
<body>
|
||||
<pretix-button event="{event_url}"{extra_attrs}>{button_text}</pretix-button>
|
||||
<script type="text/javascript" src="{widget_js}" async crossorigin></script>
|
||||
</body>
|
||||
</html>"""
|
||||
resp = HttpResponse(html, content_type='text/html')
|
||||
resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'"
|
||||
return resp
|
||||
|
||||
# Add URL patterns
|
||||
test_pattern = path(
|
||||
'widget-test/<str:organizer>/<str:event>/',
|
||||
WidgetTestView.as_view()
|
||||
)
|
||||
button_pattern = path(
|
||||
'button-test/<str:organizer>/<str:event>/',
|
||||
ButtonTestView.as_view()
|
||||
)
|
||||
|
||||
# Insert at beginning of URL patterns
|
||||
if hasattr(urls, 'urlpatterns'):
|
||||
urls.urlpatterns.insert(0, test_pattern)
|
||||
urls.urlpatterns.insert(0, button_pattern)
|
||||
|
||||
@@ -9,13 +9,13 @@ from playwright.sync_api import Page
|
||||
def test_css_contains_no_scss_syntax(
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items
|
||||
organizer,
|
||||
event,
|
||||
items
|
||||
):
|
||||
"""Verify CSS is compiled and doesn't contain SCSS syntax."""
|
||||
# Fetch the CSS directly
|
||||
css_url = f"{live_server_url}/{widget_organizer.slug}/{widget_event.slug}/widget/v2.css"
|
||||
css_url = f"{live_server_url}/{organizer.slug}/{event.slug}/widget/v2.css"
|
||||
|
||||
print(f"\nFetching CSS from: {css_url}")
|
||||
response = page.request.get(css_url)
|
||||
|
||||
@@ -23,38 +23,36 @@ class TestWidgetAriaLabels:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Main widget wrapper should have role="article"
|
||||
and aria-label with event name.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
wrapper = page.locator('.pretix-widget-wrapper')
|
||||
expect(wrapper).to_have_attribute('role', 'article')
|
||||
expect(wrapper).to_have_attribute('aria-label', widget_event.name)
|
||||
expect(wrapper).to_have_attribute('aria-label', event.name)
|
||||
|
||||
def test_widget_wrapper_is_focusable(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Widget wrapper should have tabindex="0" for keyboard access.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
wrapper = page.locator('.pretix-widget-wrapper')
|
||||
expect(wrapper).to_have_attribute('tabindex', '0')
|
||||
@@ -63,9 +61,9 @@ class TestWidgetAriaLabels:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -76,13 +74,12 @@ class TestWidgetAriaLabels:
|
||||
is explicitly enabled for single events (auto mode hides it).
|
||||
We use the display-event-info attribute to force it on.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
widget_event.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'display-event-info': 'true'}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
heading = page.locator(
|
||||
'.pretix-widget-event-header strong[role="heading"]')
|
||||
@@ -98,21 +95,20 @@ class TestQuantityControlAccessibility:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Plus/minus buttons should have descriptive aria-labels.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Find increment button for first item
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_items[0].name}")')
|
||||
f'.pretix-widget-item:has-text("{items[0].name}")')
|
||||
inc_btn = item_elem.locator('button[aria-label]').last
|
||||
dec_btn = item_elem.locator('button[aria-label]').first
|
||||
|
||||
@@ -126,20 +122,19 @@ class TestQuantityControlAccessibility:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Quantity input should be connected to a label via aria-labelledby.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_items[0].name}")')
|
||||
f'.pretix-widget-item:has-text("{items[0].name}")')
|
||||
qty_input = item_elem.locator('input[type="number"]')
|
||||
|
||||
labelledby = qty_input.get_attribute('aria-labelledby')
|
||||
@@ -154,17 +149,16 @@ class TestVoucherAccessibility:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_voucher,
|
||||
organizer,
|
||||
event,
|
||||
voucher,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Voucher input should reference the headline via aria-labelledby.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
voucher_input = page.locator('.pretix-widget-voucher-input')
|
||||
headline = page.locator('.pretix-widget-voucher-headline')
|
||||
@@ -186,20 +180,19 @@ class TestVariationAccessibility:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Variations toggle button should have aria-expanded
|
||||
and aria-controls attributes.
|
||||
"""
|
||||
item, _ = widget_item_with_variations
|
||||
item, _ = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")')
|
||||
@@ -226,22 +219,21 @@ class TestCalendarAccessibility:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event_series,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Calendar table should have tabindex="0" and aria-labelledby.
|
||||
"""
|
||||
event, _ = widget_event_series
|
||||
event, _ = event_series
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'calendar'}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
table = page.locator('.pretix-widget-event-calendar-table')
|
||||
expect(table).to_have_attribute('tabindex', '0')
|
||||
@@ -254,23 +246,22 @@ class TestCalendarAccessibility:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event_series,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Calendar day-of-week headers should have full day names
|
||||
as aria-labels (Mo -> Monday, Tu -> Tuesday, etc.).
|
||||
"""
|
||||
event, _ = widget_event_series
|
||||
event, _ = event_series
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'calendar'}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Check first day header has aria-label
|
||||
first_header = page.locator(
|
||||
@@ -289,18 +280,17 @@ class TestKeyboardNavigation:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Pressing Tab should cycle through interactive elements
|
||||
(inputs, buttons) within the widget.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Tab through several elements
|
||||
focused_tags = set()
|
||||
|
||||
@@ -20,18 +20,17 @@ class TestSoldOutState:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_sold_out,
|
||||
organizer,
|
||||
event,
|
||||
item_sold_out,
|
||||
widget_page
|
||||
):
|
||||
"""Sold out item should show 'Sold out' text."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_sold_out.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_sold_out.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should show "Sold out" in the availability area
|
||||
@@ -42,18 +41,17 @@ class TestSoldOutState:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_sold_out,
|
||||
organizer,
|
||||
event,
|
||||
item_sold_out,
|
||||
widget_page
|
||||
):
|
||||
"""Sold out item should not show any input controls."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_sold_out.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_sold_out.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should not have any input controls
|
||||
@@ -68,21 +66,20 @@ class TestQuotaLeftDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_low_stock,
|
||||
organizer,
|
||||
event,
|
||||
item_low_stock,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Item with low stock and show_quota_left should display
|
||||
the number of remaining tickets.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_low_stock.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_low_stock.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should show quota left text containing "3"
|
||||
@@ -93,18 +90,17 @@ class TestQuotaLeftDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_low_stock,
|
||||
organizer,
|
||||
event,
|
||||
item_low_stock,
|
||||
widget_page
|
||||
):
|
||||
"""Low stock items should still be purchasable."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_low_stock.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_low_stock.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should have quantity input
|
||||
@@ -119,18 +115,17 @@ class TestRequireVoucherState:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_require_voucher,
|
||||
organizer,
|
||||
event,
|
||||
item_require_voucher,
|
||||
widget_page
|
||||
):
|
||||
"""Item requiring voucher should show 'Only available with a voucher'."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_require_voucher.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_require_voucher.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should show unavailability message with voucher link
|
||||
@@ -142,19 +137,18 @@ class TestRequireVoucherState:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_require_voucher,
|
||||
widget_voucher,
|
||||
organizer,
|
||||
event,
|
||||
item_require_voucher,
|
||||
voucher,
|
||||
widget_page
|
||||
):
|
||||
"""Voucher-required message should link to the voucher input field."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_require_voucher.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_require_voucher.name}")')
|
||||
unavail = item_elem.locator('.pretix-widget-availability-unavailable')
|
||||
|
||||
# Should have a link (to jump to voucher input)
|
||||
@@ -170,18 +164,17 @@ class TestNotYetAvailable:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_not_yet_available,
|
||||
organizer,
|
||||
event,
|
||||
item_not_yet_available,
|
||||
widget_page
|
||||
):
|
||||
"""Item with future available_from should show 'Not yet available'."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_not_yet_available.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_not_yet_available.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should show unavailability message
|
||||
@@ -192,18 +185,17 @@ class TestNotYetAvailable:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_not_yet_available,
|
||||
organizer,
|
||||
event,
|
||||
item_not_yet_available,
|
||||
widget_page
|
||||
):
|
||||
"""Not-yet-available items should not have quantity controls."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_not_yet_available.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_not_yet_available.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should not have any input controls
|
||||
|
||||
@@ -21,17 +21,16 @@ class TestCartBasics:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""Selecting items and clicking Buy should open iframe checkout."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
widget_page.select_item_quantity(widget_items[0].name, 2)
|
||||
widget_page.select_item_quantity(items[0].name, 2)
|
||||
widget_page.click_buy_button()
|
||||
|
||||
# Iframe checkout should open
|
||||
@@ -48,15 +47,14 @@ class TestCartBasics:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""Clicking Buy without selecting items should not open checkout."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Don't select any items, just click buy
|
||||
widget_page.click_buy_button()
|
||||
@@ -70,7 +68,7 @@ class TestCartBasics:
|
||||
|
||||
# Items should still be there
|
||||
expect(page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_items[0].name}")'
|
||||
f'.pretix-widget-item:has-text("{items[0].name}")'
|
||||
)).to_be_visible()
|
||||
|
||||
|
||||
@@ -83,17 +81,16 @@ class TestCartPersistence:
|
||||
page: Page,
|
||||
context: BrowserContext,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""Adding items to cart should create a pretix_widget cookie."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
widget_page.select_item_quantity(widget_items[0].name, 1)
|
||||
widget_page.select_item_quantity(items[0].name, 1)
|
||||
widget_page.click_buy_button()
|
||||
|
||||
# Wait for iframe checkout to open (cookie is set during cart creation)
|
||||
@@ -111,18 +108,17 @@ class TestCartPersistence:
|
||||
page: Page,
|
||||
context: BrowserContext,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""After creating a cart and reloading, widget should show resume option."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Create a real cart by adding items and opening checkout
|
||||
widget_page.select_item_quantity(widget_items[0].name, 1)
|
||||
widget_page.select_item_quantity(items[0].name, 1)
|
||||
widget_page.click_buy_button()
|
||||
widget_page.wait_for_iframe_checkout()
|
||||
|
||||
@@ -150,17 +146,16 @@ class TestIframeCheckout:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""With skip-ssl-check, checkout should open in iframe on HTTP."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
widget_page.select_item_quantity(widget_items[0].name, 1)
|
||||
widget_page.select_item_quantity(items[0].name, 1)
|
||||
widget_page.click_buy_button()
|
||||
|
||||
widget_page.wait_for_iframe_checkout()
|
||||
@@ -168,23 +163,22 @@ class TestIframeCheckout:
|
||||
iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
|
||||
src = iframe_elem.get_attribute('src')
|
||||
assert 'iframe=1' in src
|
||||
assert widget_organizer.slug in src
|
||||
assert organizer.slug in src
|
||||
|
||||
def test_close_iframe_button(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""User should be able to close checkout iframe."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
widget_page.select_item_quantity(widget_items[0].name, 1)
|
||||
widget_page.select_item_quantity(items[0].name, 1)
|
||||
widget_page.click_buy_button()
|
||||
|
||||
widget_page.wait_for_iframe_checkout()
|
||||
@@ -198,17 +192,16 @@ class TestIframeCheckout:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""Iframe checkout URL should include take_cart_id parameter."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
widget_page.select_item_quantity(widget_items[0].name, 2)
|
||||
widget_page.select_item_quantity(items[0].name, 2)
|
||||
widget_page.click_buy_button()
|
||||
|
||||
widget_page.wait_for_iframe_checkout()
|
||||
@@ -221,17 +214,16 @@ class TestIframeCheckout:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""Iframe checkout overlay container should be visible during checkout."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
widget_page.select_item_quantity(widget_items[0].name, 1)
|
||||
widget_page.select_item_quantity(items[0].name, 1)
|
||||
widget_page.click_buy_button()
|
||||
|
||||
widget_page.wait_for_iframe_checkout()
|
||||
@@ -248,19 +240,18 @@ class TestMultipleItemSelection:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""Selecting multiple items should open checkout with all of them."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Select multiple items
|
||||
widget_page.select_item_quantity(widget_items[0].name, 2)
|
||||
widget_page.select_item_quantity(widget_items[1].name, 1)
|
||||
widget_page.select_item_quantity(items[0].name, 2)
|
||||
widget_page.select_item_quantity(items[1].name, 1)
|
||||
|
||||
widget_page.click_buy_button()
|
||||
|
||||
@@ -275,22 +266,21 @@ class TestMultipleItemSelection:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
widget_item_single_select,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
item_single_select,
|
||||
widget_page
|
||||
):
|
||||
"""Selecting both checkbox and quantity items should open checkout."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Select quantity item
|
||||
widget_page.select_item_quantity(widget_items[0].name, 3)
|
||||
widget_page.select_item_quantity(items[0].name, 3)
|
||||
|
||||
# Select checkbox item
|
||||
widget_page.select_item_quantity(widget_item_single_select.name, 1)
|
||||
widget_page.select_item_quantity(item_single_select.name, 1)
|
||||
|
||||
widget_page.click_buy_button()
|
||||
|
||||
|
||||
@@ -19,19 +19,18 @@ class TestCategoryDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Category names should be shown as h3 headers.
|
||||
|
||||
The widget_items fixture creates a 'Tickets' category.
|
||||
The items fixture creates a 'Tickets' category.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Category header should be visible
|
||||
category_header = page.locator(
|
||||
@@ -42,17 +41,16 @@ class TestCategoryDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items_with_category_description,
|
||||
organizer,
|
||||
event,
|
||||
items_with_category_description,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Category descriptions should be displayed below category name.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Category description should be visible
|
||||
desc = page.locator('.pretix-widget-category-description')
|
||||
@@ -63,18 +61,17 @@ class TestCategoryDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items_multiple_categories,
|
||||
organizer,
|
||||
event,
|
||||
items_multiple_categories,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Items should be grouped under respective categories
|
||||
and maintain category sort order.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Both categories should be visible
|
||||
expect(page.locator(
|
||||
|
||||
@@ -19,25 +19,24 @@ class TestItemsFilter:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
When items="<id>" is set, only that item should be shown.
|
||||
"""
|
||||
# Get the first item's ID
|
||||
target_item = widget_items[0]
|
||||
other_item = widget_items[1]
|
||||
target_item = items[0]
|
||||
other_item = items[1]
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
widget_event.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
items=str(target_item.pk)
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Target item should be visible
|
||||
expect(page.locator(
|
||||
@@ -53,26 +52,25 @@ class TestItemsFilter:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
When items="<id1>,<id2>", both items should be shown.
|
||||
"""
|
||||
ids = ','.join(str(item.pk) for item in widget_items)
|
||||
ids = ','.join(str(item.pk) for item in items)
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
widget_event.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
items=ids
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Both items should be visible
|
||||
for item in widget_items:
|
||||
for item in items:
|
||||
expect(page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")'
|
||||
)).to_be_visible()
|
||||
@@ -86,26 +84,25 @@ class TestCategoriesFilter:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items_multiple_categories,
|
||||
organizer,
|
||||
event,
|
||||
items_multiple_categories,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
When categories="<id>" is set, only items from that category
|
||||
should be shown.
|
||||
"""
|
||||
items = widget_items_multiple_categories
|
||||
items = items_multiple_categories
|
||||
# Get the category of the first item (Music)
|
||||
target_category = items[0].category
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
widget_event.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
categories=str(target_category.pk)
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Music item should be visible
|
||||
expect(page.locator(
|
||||
@@ -126,9 +123,9 @@ class TestDisableVouchers:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_voucher,
|
||||
organizer,
|
||||
event,
|
||||
voucher,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -136,13 +133,12 @@ class TestDisableVouchers:
|
||||
even when vouchers exist.
|
||||
"""
|
||||
# Navigate with disable-vouchers attribute
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
widget_event.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'disable-vouchers': ''}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Voucher section should NOT be visible
|
||||
voucher_section = page.locator('.pretix-widget-voucher')
|
||||
@@ -157,22 +153,21 @@ class TestDisableIframe:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
When disable-iframe is set, checkout should open in a new tab
|
||||
instead of an iframe overlay.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
widget_event.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'disable-iframe': ''}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Select quantity for an item
|
||||
widget_page.select_item_quantity('General Admission', 1)
|
||||
@@ -183,5 +178,5 @@ class TestDisableIframe:
|
||||
|
||||
popup = popup_info.value
|
||||
# New tab should navigate to checkout URL
|
||||
assert widget_organizer.slug in popup.url
|
||||
assert organizer.slug in popup.url
|
||||
popup.close()
|
||||
|
||||
@@ -21,21 +21,20 @@ class TestWidgetMode:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Default widget mode should show full product listing
|
||||
with categories, items, and a buy button.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Should show items
|
||||
for item in widget_items:
|
||||
for item in items:
|
||||
expect(page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")'
|
||||
)).to_be_visible()
|
||||
@@ -54,22 +53,21 @@ class TestCalendarView:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event_series,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Calendar view should show a monthly grid with event dates.
|
||||
"""
|
||||
event, subevents = widget_event_series
|
||||
event, subevents = event_series
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'calendar'}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Should show calendar table
|
||||
expect(page.locator(
|
||||
@@ -84,22 +82,21 @@ class TestCalendarView:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event_series,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Clicking next month button should navigate to the next month.
|
||||
"""
|
||||
event, subevents = widget_event_series
|
||||
event, subevents = event_series
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'calendar'}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Get current month heading text
|
||||
header = page.locator('.pretix-widget-event-calendar-head')
|
||||
@@ -121,8 +118,8 @@ class TestCalendarView:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event_series,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -133,15 +130,14 @@ class TestCalendarView:
|
||||
(target_url resolves to configured domain, not live_server).
|
||||
We verify the links exist and have correct structure.
|
||||
"""
|
||||
event, _ = widget_event_series
|
||||
event, _ = event_series
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'calendar'}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Event links should exist and show event info
|
||||
event_link = page.locator(
|
||||
@@ -172,22 +168,21 @@ class TestListView:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event_series,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
List view should display events as a linear list.
|
||||
"""
|
||||
event, subevents = widget_event_series
|
||||
event, subevents = event_series
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'list'}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Should show event list entries
|
||||
entries = page.locator('.pretix-widget-event-list-entry')
|
||||
@@ -200,22 +195,21 @@ class TestListView:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event_series,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
List view should show subevent names.
|
||||
"""
|
||||
event, subevents = widget_event_series
|
||||
event, subevents = event_series
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'list'}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# At least the first subevent name should appear
|
||||
expect(page.locator(
|
||||
@@ -226,8 +220,8 @@ class TestListView:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event_series,
|
||||
organizer,
|
||||
event_series,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -237,15 +231,14 @@ class TestListView:
|
||||
(target_url resolves to configured domain, not live_server).
|
||||
We verify the entries have correct structure.
|
||||
"""
|
||||
event, _ = widget_event_series
|
||||
event, _ = event_series
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
widget_page.goto(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
organizer.slug,
|
||||
event.slug,
|
||||
**{'list-type': 'list'}
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Each entry should show availability info
|
||||
first_entry = page.locator(
|
||||
@@ -266,9 +259,9 @@ class TestButtonMode:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -276,8 +269,8 @@ class TestButtonMode:
|
||||
"""
|
||||
widget_page.goto_button_test_page(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
widget_event.slug
|
||||
organizer.slug,
|
||||
event.slug
|
||||
)
|
||||
|
||||
# Wait for script to load and initialize
|
||||
@@ -292,9 +285,9 @@ class TestButtonMode:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -302,8 +295,8 @@ class TestButtonMode:
|
||||
"""
|
||||
widget_page.goto_button_test_page(
|
||||
live_server_url,
|
||||
widget_organizer.slug,
|
||||
widget_event.slug
|
||||
organizer.slug,
|
||||
event.slug
|
||||
)
|
||||
|
||||
page.wait_for_timeout(2000)
|
||||
@@ -313,5 +306,5 @@ class TestButtonMode:
|
||||
page.locator('.pretix-button').click()
|
||||
|
||||
popup = popup_info.value
|
||||
assert widget_organizer.slug in popup.url
|
||||
assert organizer.slug in popup.url
|
||||
popup.close()
|
||||
|
||||
@@ -19,8 +19,8 @@ class TestEmptyStates:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
organizer,
|
||||
event,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -28,9 +28,8 @@ class TestEmptyStates:
|
||||
Should show the widget container but no item rows.
|
||||
"""
|
||||
# Navigate without creating any items
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Widget should still be present
|
||||
expect(page.locator('.pretix-widget')).to_be_visible()
|
||||
@@ -43,18 +42,17 @@ class TestEmptyStates:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Submitting with zero quantity should not navigate away.
|
||||
The widget should remain visible without opening checkout.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Don't select any items, just click buy
|
||||
widget_page.click_buy_button()
|
||||
@@ -65,7 +63,7 @@ class TestEmptyStates:
|
||||
|
||||
# Items should still be visible
|
||||
expect(page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_items[0].name}")'
|
||||
f'.pretix-widget-item:has-text("{items[0].name}")'
|
||||
)).to_be_visible()
|
||||
|
||||
|
||||
@@ -77,20 +75,19 @@ class TestMinPerOrder:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_min_order,
|
||||
organizer,
|
||||
event,
|
||||
item_min_order,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Items with min_per_order should display a text message
|
||||
indicating the minimum quantity (e.g. "minimum amount to order: 2").
|
||||
"""
|
||||
item = widget_item_min_order
|
||||
item = item_min_order
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")')
|
||||
@@ -110,20 +107,19 @@ class TestSpecialCharacters:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_special_chars,
|
||||
organizer,
|
||||
event,
|
||||
item_special_chars,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Items with special characters (umlauts, ampersands, etc.)
|
||||
should display correctly.
|
||||
"""
|
||||
item = widget_item_special_chars
|
||||
item = item_special_chars
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Item with special characters should be visible
|
||||
item_elem = page.locator(
|
||||
|
||||
@@ -16,9 +16,9 @@ class TestWidgetEmbedding:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -29,25 +29,24 @@ class TestWidgetEmbedding:
|
||||
- All configured items
|
||||
"""
|
||||
# Navigate to test page with widget embedded
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Verify widget container exists with event aria-label
|
||||
widget = page.locator('.pretix-widget-wrapper')
|
||||
expect(widget).to_have_attribute('aria-label', widget_event.name)
|
||||
expect(widget).to_have_attribute('aria-label', event.name)
|
||||
|
||||
# Verify items are listed
|
||||
for item in widget_items:
|
||||
for item in items:
|
||||
expect(page.locator(f'text="{item.name}"')).to_be_visible()
|
||||
|
||||
def test_widget_displays_loading_state(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
organizer,
|
||||
event,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -56,8 +55,8 @@ class TestWidgetEmbedding:
|
||||
The loading spinner should eventually disappear when data is loaded.
|
||||
"""
|
||||
# Navigate to widget test page
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug, wait=False
|
||||
)
|
||||
|
||||
# Wait for widget element
|
||||
@@ -74,8 +73,8 @@ class TestWidgetEmbedding:
|
||||
widget_page
|
||||
):
|
||||
"""Widget should display error message for invalid event."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, 'invalid-org', 'invalid-event'
|
||||
widget_page.goto(
|
||||
live_server_url, 'invalid-org', 'invalid-event', wait=False
|
||||
)
|
||||
|
||||
# Should show widget container
|
||||
@@ -90,9 +89,9 @@ class TestWidgetEmbedding:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -100,13 +99,12 @@ class TestWidgetEmbedding:
|
||||
|
||||
Item descriptions should be visible for items that have them.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Check that descriptions are shown
|
||||
for item in widget_items:
|
||||
for item in items:
|
||||
if item.description:
|
||||
expect(
|
||||
page.locator(f'text="{item.description}"')
|
||||
@@ -116,9 +114,9 @@ class TestWidgetEmbedding:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -126,21 +124,20 @@ class TestWidgetEmbedding:
|
||||
|
||||
Prices should be formatted with currency and decimal places.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Verify prices are shown (with currency)
|
||||
# Each item should have its price displayed
|
||||
for item in widget_items:
|
||||
for item in items:
|
||||
# Find the item container first, then check price within it
|
||||
item_container = page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")'
|
||||
)
|
||||
expect(item_container).to_be_visible()
|
||||
|
||||
# Check price is present (formatted as "USD XX.XX")
|
||||
# Check price is present (formatted as "EUR XX.XX")
|
||||
price_text = f"{float(item.default_price):.2f}"
|
||||
price_box = item_container.locator('.pretix-widget-pricebox')
|
||||
expect(price_box).to_contain_text(price_text)
|
||||
@@ -154,8 +151,8 @@ class TestWidgetEventInfo:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
organizer,
|
||||
event,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -165,10 +162,9 @@ class TestWidgetEventInfo:
|
||||
date by default. This test verifies the widget loads without
|
||||
checking for specific date display.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug
|
||||
)
|
||||
widget_page.wait_for_widget_load()
|
||||
|
||||
# Widget should be present and functional
|
||||
# (Event date display varies by configuration)
|
||||
@@ -179,8 +175,8 @@ class TestWidgetEventInfo:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
organizer,
|
||||
event,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
|
||||
@@ -18,16 +18,15 @@ class TestErrorDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
organizer,
|
||||
event,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Loading a non-existent event should show an error message.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, 'nonexistent-event')
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, 'nonexistent-event')
|
||||
|
||||
# Should show error message
|
||||
expect(page.locator(
|
||||
@@ -38,17 +37,16 @@ class TestErrorDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
organizer,
|
||||
event,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Error state should include a link to open the ticket shop
|
||||
in a new tab as a fallback.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, 'nonexistent-event')
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, 'nonexistent-event')
|
||||
|
||||
# Should show fallback action link
|
||||
action_link = page.locator('.pretix-widget-error-action a')
|
||||
@@ -66,19 +64,18 @@ class TestSoldOutState:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_sold_out,
|
||||
organizer,
|
||||
event,
|
||||
item_sold_out,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Items with zero quota should show as unavailable/sold out.
|
||||
"""
|
||||
item = widget_item_sold_out
|
||||
item = item_sold_out
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")')
|
||||
@@ -93,20 +90,19 @@ class TestSoldOutState:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_sold_out,
|
||||
organizer,
|
||||
event,
|
||||
item_sold_out,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Sold out items should show a status message like
|
||||
"Sold out" or "Currently unavailable".
|
||||
"""
|
||||
item = widget_item_sold_out
|
||||
item = item_sold_out
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")')
|
||||
|
||||
@@ -19,18 +19,17 @@ class TestItemPicture:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_picture,
|
||||
organizer,
|
||||
event,
|
||||
item_with_picture,
|
||||
widget_page
|
||||
):
|
||||
"""Item with picture should display a thumbnail image."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should have picture element
|
||||
@@ -41,18 +40,17 @@ class TestItemPicture:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_picture,
|
||||
organizer,
|
||||
event,
|
||||
item_with_picture,
|
||||
widget_page
|
||||
):
|
||||
"""Item picture should have alt text."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
|
||||
|
||||
img = item_elem.locator('.pretix-widget-item-picture')
|
||||
alt = img.get_attribute('alt')
|
||||
@@ -62,18 +60,17 @@ class TestItemPicture:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_picture,
|
||||
organizer,
|
||||
event,
|
||||
item_with_picture,
|
||||
widget_page
|
||||
):
|
||||
"""Picture should be wrapped in a clickable link."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
|
||||
|
||||
link = item_elem.locator('.pretix-widget-item-picture-link')
|
||||
expect(link).to_be_visible()
|
||||
@@ -84,18 +81,17 @@ class TestItemPicture:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_picture,
|
||||
organizer,
|
||||
event,
|
||||
item_with_picture,
|
||||
widget_page
|
||||
):
|
||||
"""Item row should have pretix-widget-item-with-picture class."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item-with-picture:has-text("{widget_item_with_picture.name}")')
|
||||
f'.pretix-widget-item-with-picture:has-text("{item_with_picture.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
|
||||
@@ -107,18 +103,17 @@ class TestLightbox:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_picture,
|
||||
organizer,
|
||||
event,
|
||||
item_with_picture,
|
||||
widget_page
|
||||
):
|
||||
"""Clicking item picture should open lightbox overlay."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
|
||||
|
||||
# Click the picture link
|
||||
link = item_elem.locator('.pretix-widget-item-picture-link')
|
||||
@@ -132,18 +127,17 @@ class TestLightbox:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_picture,
|
||||
organizer,
|
||||
event,
|
||||
item_with_picture,
|
||||
widget_page
|
||||
):
|
||||
"""Lightbox should display fullsize image."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
|
||||
item_elem.locator('.pretix-widget-item-picture-link').click()
|
||||
|
||||
# Wait for lightbox
|
||||
@@ -157,18 +151,17 @@ class TestLightbox:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_picture,
|
||||
organizer,
|
||||
event,
|
||||
item_with_picture,
|
||||
widget_page
|
||||
):
|
||||
"""Lightbox close button should close the overlay."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
|
||||
item_elem.locator('.pretix-widget-item-picture-link').click()
|
||||
|
||||
# Wait for lightbox to appear
|
||||
@@ -187,18 +180,17 @@ class TestLightbox:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_picture,
|
||||
organizer,
|
||||
event,
|
||||
item_with_picture,
|
||||
widget_page
|
||||
):
|
||||
"""Lightbox dialog should have role='alertdialog'."""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_item_with_picture.name}")')
|
||||
f'.pretix-widget-item:has-text("{item_with_picture.name}")')
|
||||
item_elem.locator('.pretix-widget-item-picture-link').click()
|
||||
|
||||
page.wait_for_timeout(1000)
|
||||
|
||||
@@ -19,17 +19,16 @@ class TestLoadingStates:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Loading spinner should be hidden once widget content loads.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Loading spinner should be hidden (display:none)
|
||||
loading = page.locator('.pretix-widget-loading')
|
||||
@@ -39,32 +38,32 @@ class TestLoadingStates:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Widget should fully load within 15 seconds.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug, wait=False)
|
||||
|
||||
# Widget should appear within 15s
|
||||
page.wait_for_selector('.pretix-widget', timeout=15000)
|
||||
|
||||
# Items should be visible within 15s total
|
||||
expect(page.locator(
|
||||
f'.pretix-widget-item:has-text("{widget_items[0].name}")'
|
||||
f'.pretix-widget-item:has-text("{items[0].name}")'
|
||||
)).to_be_visible(timeout=15000)
|
||||
|
||||
def test_no_javascript_errors_on_load(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -73,9 +72,8 @@ class TestLoadingStates:
|
||||
errors = []
|
||||
page.on('pageerror', lambda err: errors.append(str(err)))
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# No JS errors should have occurred
|
||||
assert len(errors) == 0, f"JavaScript errors: {errors}"
|
||||
@@ -89,18 +87,17 @@ class TestWidgetReload:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Widget CSS should load and apply styles.
|
||||
No SCSS syntax should leak into rendered styles.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Check that widget has actual styled dimensions
|
||||
# (not zero-height which would indicate CSS failure)
|
||||
|
||||
@@ -20,34 +20,33 @@ class TestPriceDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Item prices should display with currency code.
|
||||
|
||||
Currency format should match event settings (USD).
|
||||
Currency format should match event settings (EUR).
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Check that prices are displayed with USD currency code
|
||||
# General Admission is USD 50.00
|
||||
# USD is in a separate span, so check for both parts
|
||||
# Use .first since there are multiple items with USD
|
||||
expect(page.locator('text=/USD/').first).to_be_visible()
|
||||
# Check that prices are displayed with EUR currency code
|
||||
# General Admission is EUR 50.00
|
||||
# EUR is in a separate span, so check for both parts
|
||||
# Use .first since there are multiple items with EUR
|
||||
expect(page.locator('text=/EUR/').first).to_be_visible()
|
||||
expect(page.locator('text=/50\\.00/').first).to_be_visible()
|
||||
|
||||
def test_free_items_display_free_text(
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_free,
|
||||
organizer,
|
||||
event,
|
||||
item_free,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -55,11 +54,10 @@ class TestPriceDisplay:
|
||||
|
||||
Makes free items more obvious to users.
|
||||
"""
|
||||
item = widget_item_free
|
||||
item = item_free
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Should show "FREE" text
|
||||
item_elem = page.locator(
|
||||
@@ -73,22 +71,21 @@ class TestPriceDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_decimals,
|
||||
organizer,
|
||||
event,
|
||||
item_with_decimals,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Prices should display with proper decimal formatting.
|
||||
|
||||
USD should show 2 decimal places (e.g., $25.00 not $25).
|
||||
EUR should show 2 decimal places (e.g., $25.00 not $25).
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Should show price with .50
|
||||
expect(page.locator('text=/USD.*12\\.50/')).to_be_visible()
|
||||
expect(page.locator('text=/EUR.*12\\.50/')).to_be_visible()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -99,9 +96,9 @@ class TestTaxDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_tax,
|
||||
organizer,
|
||||
event,
|
||||
item_with_tax,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -109,11 +106,10 @@ class TestTaxDisplay:
|
||||
|
||||
Should display "incl. X% VAT" or similar.
|
||||
"""
|
||||
item = widget_item_with_tax
|
||||
item = item_with_tax
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")')
|
||||
@@ -128,9 +124,9 @@ class TestTaxDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -138,13 +134,12 @@ class TestTaxDisplay:
|
||||
|
||||
Tax line should be absent for tax-free items.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# widget_items by default have no tax
|
||||
# items by default have no tax
|
||||
# We just verify the items display without errors
|
||||
for item in widget_items:
|
||||
for item in items:
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
@@ -158,9 +153,9 @@ class TestDiscountedPricing:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -168,18 +163,17 @@ class TestDiscountedPricing:
|
||||
|
||||
This is a smoke test to ensure price rendering works.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# All items should display their prices
|
||||
for item in widget_items:
|
||||
for item in items:
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should have USD currency and price displayed
|
||||
expect(item_elem.locator('text=/USD/')).to_be_visible()
|
||||
# Should have EUR currency and price displayed
|
||||
expect(item_elem.locator('text=/EUR/')).to_be_visible()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -190,9 +184,9 @@ class TestPriceForVariations:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -200,11 +194,10 @@ class TestPriceForVariations:
|
||||
|
||||
E.g., "$20.00 - $30.00" for variations from $20 to $30.
|
||||
"""
|
||||
item, _ = widget_item_with_variations
|
||||
item, _ = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Variations are collapsed by default
|
||||
item_elem = page.locator(
|
||||
@@ -212,12 +205,12 @@ class TestPriceForVariations:
|
||||
expect(item_elem).to_be_visible()
|
||||
|
||||
# Should show price range in main row (not in hidden variations)
|
||||
# Format: USD 20.00 – 30.00 (en-dash, not hyphen)
|
||||
# Format: EUR 20.00 – 30.00 (en-dash, not hyphen)
|
||||
# Look specifically in the main row's price column
|
||||
main_row = item_elem.locator('.pretix-widget-main-item-row')
|
||||
price_col = main_row.locator('.pretix-widget-item-price-col')
|
||||
expect(
|
||||
price_col.locator('text=/USD.*20\\.00/')
|
||||
price_col.locator('text=/EUR.*20\\.00/')
|
||||
).to_be_visible()
|
||||
expect(price_col.locator('text=/30\\.00/')).to_be_visible()
|
||||
|
||||
@@ -225,9 +218,9 @@ class TestPriceForVariations:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -235,16 +228,15 @@ class TestPriceForVariations:
|
||||
|
||||
Each size should show its own price.
|
||||
"""
|
||||
item, variations = widget_item_with_variations
|
||||
item, variations = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Expand variations
|
||||
widget_page.expand_variations(item.name)
|
||||
|
||||
# Check each variation shows a price with USD currency
|
||||
# Check each variation shows a price with EUR currency
|
||||
# We don't check exact amounts because formatting may vary
|
||||
for var in variations:
|
||||
var_elem = page.locator(
|
||||
@@ -253,5 +245,5 @@ class TestPriceForVariations:
|
||||
)
|
||||
expect(var_elem).to_be_visible()
|
||||
|
||||
# Should contain USD currency code
|
||||
expect(var_elem.locator('text=/USD/')).to_be_visible()
|
||||
# Should contain EUR currency code
|
||||
expect(var_elem.locator('text=/EUR/')).to_be_visible()
|
||||
|
||||
@@ -19,9 +19,9 @@ class TestQuantityControls:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_single_select,
|
||||
organizer,
|
||||
event,
|
||||
item_single_select,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -30,10 +30,9 @@ class TestQuantityControls:
|
||||
For items limited to 1 per order, a checkbox is more intuitive than
|
||||
a number input.
|
||||
"""
|
||||
item = widget_item_single_select
|
||||
item = item_single_select
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Find the item
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
|
||||
@@ -47,9 +46,9 @@ class TestQuantityControls:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_single_select,
|
||||
organizer,
|
||||
event,
|
||||
item_single_select,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -60,10 +59,9 @@ class TestQuantityControls:
|
||||
Note: When there's only one item, it may be auto-selected by the widget.
|
||||
This test verifies the check/uncheck functionality works regardless.
|
||||
"""
|
||||
item = widget_item_single_select
|
||||
item = item_single_select
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
|
||||
checkbox = item_elem.locator('input[type="checkbox"]')
|
||||
@@ -85,9 +83,9 @@ class TestQuantityControls:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -95,11 +93,10 @@ class TestQuantityControls:
|
||||
|
||||
Plus/minus buttons provide an easy way to increment/decrement quantity.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Regular items should have number input with +/- buttons
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{widget_items[0].name}")')
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{items[0].name}")')
|
||||
|
||||
# Should have number input
|
||||
expect(item_elem.locator('input[type="number"]')).to_be_visible()
|
||||
@@ -112,9 +109,9 @@ class TestQuantityControls:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -122,10 +119,9 @@ class TestQuantityControls:
|
||||
|
||||
Each click increments the value in the number input.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{widget_items[0].name}")')
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{items[0].name}")')
|
||||
number_input = item_elem.locator('input[type="number"]')
|
||||
plus_button = item_elem.locator('button:has-text("+")').first
|
||||
|
||||
@@ -151,9 +147,9 @@ class TestQuantityControls:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -161,10 +157,9 @@ class TestQuantityControls:
|
||||
|
||||
Quantity should not go below 0.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{widget_items[0].name}")')
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{items[0].name}")')
|
||||
number_input = item_elem.locator('input[type="number"]')
|
||||
plus_button = item_elem.locator('button:has-text("+")').first
|
||||
minus_button = item_elem.locator('button:has-text("-")').first
|
||||
@@ -193,9 +188,9 @@ class TestQuantityControls:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -203,14 +198,13 @@ class TestQuantityControls:
|
||||
|
||||
Number input should accept typed values.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Directly set quantity using helper
|
||||
widget_page.select_item_quantity(widget_items[0].name, 5)
|
||||
widget_page.select_item_quantity(items[0].name, 5)
|
||||
|
||||
# Verify value
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{widget_items[0].name}")')
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{items[0].name}")')
|
||||
number_input = item_elem.locator('input[type="number"]')
|
||||
expect(number_input).to_have_value("5")
|
||||
|
||||
@@ -223,16 +217,15 @@ class TestOrderLimits:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_single_select,
|
||||
organizer,
|
||||
event,
|
||||
item_single_select,
|
||||
widget_page
|
||||
):
|
||||
"""Item with order_max=1 should show checkbox (implicit max enforcement)."""
|
||||
item = widget_item_single_select # This has order_max=1
|
||||
item = item_single_select # This has order_max=1
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
@@ -246,17 +239,16 @@ class TestOrderLimits:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""Multiple items with different quantities should open iframe checkout."""
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
widget_page.select_item_quantity(widget_items[0].name, 2)
|
||||
widget_page.select_item_quantity(widget_items[1].name, 1)
|
||||
widget_page.select_item_quantity(items[0].name, 2)
|
||||
widget_page.select_item_quantity(items[1].name, 1)
|
||||
|
||||
widget_page.click_buy_button()
|
||||
|
||||
@@ -276,9 +268,9 @@ class TestFreePrice:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_free_price,
|
||||
organizer,
|
||||
event,
|
||||
item_free_price,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -286,10 +278,9 @@ class TestFreePrice:
|
||||
|
||||
User should be able to enter their own price.
|
||||
"""
|
||||
item = widget_item_free_price
|
||||
item = item_free_price
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
|
||||
expect(item_elem).to_be_visible()
|
||||
@@ -302,9 +293,9 @@ class TestFreePrice:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_free_price,
|
||||
organizer,
|
||||
event,
|
||||
item_free_price,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -312,10 +303,9 @@ class TestFreePrice:
|
||||
|
||||
The min attribute should be set to the item's default price.
|
||||
"""
|
||||
item = widget_item_free_price
|
||||
item = item_free_price
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
|
||||
price_input = item_elem.locator('.pretix-widget-pricebox-price-input, input[name^="price_"]').first
|
||||
@@ -329,9 +319,9 @@ class TestFreePrice:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_free_price,
|
||||
organizer,
|
||||
event,
|
||||
item_free_price,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -339,10 +329,9 @@ class TestFreePrice:
|
||||
|
||||
Amount above minimum should be accepted.
|
||||
"""
|
||||
item = widget_item_free_price
|
||||
item = item_free_price
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
|
||||
price_input = item_elem.locator('.pretix-widget-pricebox-price-input, input[name^="price_"]').first
|
||||
|
||||
@@ -18,9 +18,9 @@ class TestResponsiveLayout:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -30,9 +30,8 @@ class TestResponsiveLayout:
|
||||
# Set narrow viewport
|
||||
page.set_viewport_size({"width": 375, "height": 667})
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Should have mobile class
|
||||
expect(page.locator('.pretix-widget-mobile')).to_be_visible()
|
||||
@@ -41,9 +40,9 @@ class TestResponsiveLayout:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -53,9 +52,8 @@ class TestResponsiveLayout:
|
||||
# Ensure wide viewport (default is 1280)
|
||||
page.set_viewport_size({"width": 1280, "height": 720})
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Should NOT have mobile class
|
||||
expect(page.locator('.pretix-widget-mobile')).to_have_count(0)
|
||||
@@ -67,9 +65,9 @@ class TestResponsiveLayout:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -79,9 +77,8 @@ class TestResponsiveLayout:
|
||||
# Start with desktop viewport
|
||||
page.set_viewport_size({"width": 1280, "height": 720})
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Should not be mobile
|
||||
expect(page.locator('.pretix-widget-mobile')).to_have_count(0)
|
||||
|
||||
@@ -19,9 +19,9 @@ class TestProductVariations:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -29,10 +29,9 @@ class TestProductVariations:
|
||||
|
||||
Variations should be collapsed by default with a button to expand them.
|
||||
"""
|
||||
item, variations = widget_item_with_variations
|
||||
item, variations = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Should show item name
|
||||
expect(page.locator(f'text="{item.name}"')).to_be_visible()
|
||||
@@ -46,9 +45,9 @@ class TestProductVariations:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -56,10 +55,9 @@ class TestProductVariations:
|
||||
|
||||
After expanding, all variation options should be visible.
|
||||
"""
|
||||
item, variations = widget_item_with_variations
|
||||
item, variations = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Expand variations
|
||||
widget_page.expand_variations(item.name)
|
||||
@@ -75,16 +73,15 @@ class TestProductVariations:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""Clicking toggle again should collapse variations."""
|
||||
item, variations = widget_item_with_variations
|
||||
item, variations = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
|
||||
toggle_btn = item_elem.locator('button:has-text("variants"), button:has-text("Show variants")')
|
||||
@@ -111,9 +108,9 @@ class TestProductVariations:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -121,17 +118,16 @@ class TestProductVariations:
|
||||
|
||||
When variations have different prices, should display range like "$20 - $30".
|
||||
"""
|
||||
item, variations = widget_item_with_variations
|
||||
item, variations = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Should show price range
|
||||
# Variations go from $20 to $30
|
||||
item_elem = page.locator(f'.pretix-widget-item:has-text("{item.name}")')
|
||||
|
||||
# Look for price range indicators
|
||||
# Format is "USD 20.00 – 30.00" in the main item's price box (first one)
|
||||
# Format is "EUR 20.00 – 30.00" in the main item's price box (first one)
|
||||
price_box = item_elem.locator('.pretix-widget-pricebox').first
|
||||
expect(price_box).to_contain_text('20.00')
|
||||
expect(price_box).to_contain_text('30.00')
|
||||
@@ -140,9 +136,9 @@ class TestProductVariations:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -150,10 +146,9 @@ class TestProductVariations:
|
||||
|
||||
After expanding variations, each should have its own quantity selector.
|
||||
"""
|
||||
item, variations = widget_item_with_variations
|
||||
item, variations = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Expand variations
|
||||
widget_page.expand_variations(item.name)
|
||||
@@ -171,9 +166,9 @@ class TestProductVariations:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -181,10 +176,9 @@ class TestProductVariations:
|
||||
|
||||
When expanded, variations should display their specific prices.
|
||||
"""
|
||||
item, variations = widget_item_with_variations
|
||||
item, variations = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Expand variations
|
||||
widget_page.expand_variations(item.name)
|
||||
@@ -216,16 +210,15 @@ class TestVariationSubmission:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_with_variations,
|
||||
organizer,
|
||||
event,
|
||||
item_with_variations,
|
||||
widget_page
|
||||
):
|
||||
"""Submitting with a variation selected should open iframe checkout."""
|
||||
item, variations = widget_item_with_variations
|
||||
item, variations = item_with_variations
|
||||
|
||||
widget_page.goto_widget_test_page(live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Expand and select a variation
|
||||
widget_page.expand_variations(item.name)
|
||||
|
||||
@@ -19,9 +19,9 @@ class TestVoucherDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_voucher,
|
||||
organizer,
|
||||
event,
|
||||
voucher,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -29,9 +29,8 @@ class TestVoucherDisplay:
|
||||
|
||||
The widget checks `vouchers_exist` in the API response.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Voucher section should be visible
|
||||
voucher_section = page.locator('.pretix-widget-voucher')
|
||||
@@ -51,17 +50,16 @@ class TestVoucherDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_items,
|
||||
organizer,
|
||||
event,
|
||||
items,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Voucher input should not appear when no vouchers exist.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Voucher section should NOT be visible
|
||||
voucher_section = page.locator('.pretix-widget-voucher')
|
||||
@@ -71,23 +69,22 @@ class TestVoucherDisplay:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_voucher,
|
||||
organizer,
|
||||
event,
|
||||
voucher,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Voucher explanation text should display when configured.
|
||||
"""
|
||||
# Set voucher explanation text on the event
|
||||
widget_event.settings.set(
|
||||
event.settings.set(
|
||||
'voucher_explanation_text',
|
||||
'Enter your voucher code to get a discount.'
|
||||
)
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Explanation text should be visible
|
||||
explanation = page.locator('.pretix-widget-voucher-text')
|
||||
@@ -103,9 +100,9 @@ class TestVoucherRedemption:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_voucher,
|
||||
organizer,
|
||||
event,
|
||||
voucher,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -113,9 +110,8 @@ class TestVoucherRedemption:
|
||||
|
||||
With skip-ssl-check (added by test harness), this opens in iframe.
|
||||
"""
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Enter voucher code
|
||||
voucher_input = page.locator('.pretix-widget-voucher-input')
|
||||
|
||||
@@ -17,9 +17,9 @@ class TestWaitingList:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_sold_out_with_waitinglist,
|
||||
organizer,
|
||||
event,
|
||||
item_sold_out_with_waitinglist,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
@@ -31,11 +31,10 @@ class TestWaitingList:
|
||||
- Item: allow_waitinglist = True
|
||||
- Item availability < 100 (sold out)
|
||||
"""
|
||||
item = widget_item_sold_out_with_waitinglist
|
||||
item = item_sold_out_with_waitinglist
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
# Find the sold out item
|
||||
item_elem = page.locator(
|
||||
@@ -51,19 +50,18 @@ class TestWaitingList:
|
||||
self,
|
||||
page: Page,
|
||||
live_server_url: str,
|
||||
widget_organizer,
|
||||
widget_event,
|
||||
widget_item_sold_out_with_waitinglist,
|
||||
organizer,
|
||||
event,
|
||||
item_sold_out_with_waitinglist,
|
||||
widget_page
|
||||
):
|
||||
"""
|
||||
Waiting list link URL should include item ID parameter.
|
||||
"""
|
||||
item = widget_item_sold_out_with_waitinglist
|
||||
item = item_sold_out_with_waitinglist
|
||||
|
||||
widget_page.goto_widget_test_page(
|
||||
live_server_url, widget_organizer.slug, widget_event.slug)
|
||||
widget_page.wait_for_widget_load()
|
||||
widget_page.goto(
|
||||
live_server_url, organizer.slug, event.slug)
|
||||
|
||||
item_elem = page.locator(
|
||||
f'.pretix-widget-item:has-text("{item.name}")')
|
||||
|
||||
Reference in New Issue
Block a user