diff --git a/src/tests/e2e/README.md b/src/tests/e2e/README.md
deleted file mode 100644
index a53c6b527c..0000000000
--- a/src/tests/e2e/README.md
+++ /dev/null
@@ -1,480 +0,0 @@
-# Pretix Widget E2E Tests
-
-End-to-end tests for the pretix widget using Python Playwright and pytest.
-
-## Overview
-
-These tests verify the complete widget experience from a user's perspective, including:
-- Widget embedding and initialization
-- Product browsing and variations
-- Quantity controls and free price inputs
-- Cart management and checkout flows
-- Iframe/new tab integration
-- Responsive behavior and accessibility
-
-## Installation
-
-### Prerequisites
-
-- Python 3.11+
-- Virtual environment activated
-- Django test database configured
-
-### Setup
-
-1. **Activate the virtual environment:**
- ```bash
- source env/bin/activate
- ```
-
-2. **Install pytest-playwright:**
- ```bash
- pip install pytest-playwright
- ```
-
-3. **Install browser binaries:**
- ```bash
- # Install all browsers (Chromium, Firefox, WebKit)
- python -m playwright install
-
- # Or install specific browsers only
- python -m playwright install chromium
- python -m playwright install firefox
- ```
-
-4. **Verify installation:**
- ```bash
- pytest src/tests/e2e/ --collect-only
- ```
-
-## Running Tests
-
-### Basic Usage
-
-```bash
-# Run all E2E tests
-pytest src/tests/e2e/
-
-# Run specific test file
-pytest src/tests/e2e/test_widget_embedding.py
-
-# Run specific test
-pytest src/tests/e2e/test_widget_cart.py::TestCartBasics::test_add_to_cart_and_open_checkout
-```
-
-### Browser Selection
-
-```bash
-# Run with specific browser (default: chromium)
-pytest src/tests/e2e/ --browser firefox
-pytest src/tests/e2e/ --browser webkit
-
-# Run with multiple browsers
-pytest src/tests/e2e/ --browser chromium --browser firefox
-```
-
-### Debugging Options
-
-```bash
-# Show browser UI (headed mode)
-pytest src/tests/e2e/ --headed
-
-# Slow down operations for observation (milliseconds)
-pytest src/tests/e2e/ --headed --slowmo 1000
-
-# Generate video recordings
-pytest src/tests/e2e/ --video on
-
-# Keep browser open on failure
-pytest src/tests/e2e/ --headed --pdb
-
-# Generate screenshots on failure
-pytest src/tests/e2e/ --screenshot on
-```
-
-### Parallel Execution
-
-```bash
-# Run tests in parallel (requires pytest-xdist)
-pip install pytest-xdist
-pytest src/tests/e2e/ -n 4
-```
-
-### Verbose Output
-
-```bash
-# Show detailed output
-pytest src/tests/e2e/ -v
-
-# Show print statements
-pytest src/tests/e2e/ -s
-
-# Show all captured output even for passing tests
-pytest src/tests/e2e/ -v -s --capture=no
-```
-
-## Test Organization
-
-```
-src/tests/e2e/
-├── conftest.py # Shared fixtures and configuration
-├── test_widget_embedding.py # Widget loading and initialization
-├── test_widget_variations.py # Product variations and expansion
-├── test_widget_quantity_controls.py # Quantity inputs, checkboxes, free price
-└── test_widget_cart.py # Cart management and checkout flow
-```
-
-### Key Fixtures
-
-Defined in `conftest.py`:
-
-- **`widget_organizer`** - Creates test organizer (testorg)
-- **`widget_event`** - Creates test event (testevent)
-- **`widget_items`** - Creates General Admission ($50) and VIP Ticket ($150)
-- **`widget_item_with_variations`** - Creates t-shirt with S/M/L/XL sizes
-- **`widget_item_single_select`** - Creates item with order_max=1 (checkbox)
-- **`widget_item_free_price`** - Creates pay-what-you-want donation item
-- **`widget_item_sold_out`** - Creates sold out item
-- **`widget_event_series`** - Creates event series with 15 subevents
-- **`widget_page`** - Enhanced page object with helper methods
-- **`live_server_url`** - Django live server URL
-
-### WidgetPage Helper Methods
-
-The `widget_page` fixture provides convenient methods:
-
-```python
-def test_example(widget_page, live_server_url, widget_event, widget_items):
- # 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)
-
- # Expand variations
- widget_page.expand_variations('Event T-Shirt')
-
- # Select variation quantity
- widget_page.select_variation_quantity('Event T-Shirt', 'Medium', 1)
-
- # Click buy button
- widget_page.click_buy_button()
-
- # Wait for iframe checkout
- iframe = widget_page.wait_for_iframe_checkout()
-
- # Close iframe
- widget_page.close_iframe()
-```
-
-## Common Test Patterns
-
-### Basic Widget Load Test
-
-```python
-@pytest.mark.django_db
-def test_widget_loads(page, live_server_url, widget_organizer, widget_event, widget_items, widget_page):
- 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()
-```
-
-### Variation Interaction Test
-
-```python
-@pytest.mark.django_db
-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(live_server_url, widget_organizer.slug, widget_event.slug)
-
- # Expand variations
- widget_page.expand_variations(item.name)
-
- # Verify all variations visible
- for variation in variations:
- expect(page.locator(f'text="{variation.value}"')).to_be_visible()
-```
-
-### Cart Flow Test
-
-```python
-@pytest.mark.django_db
-def test_checkout(page, context, live_server_url, widget_organizer, widget_event, widget_items, widget_page):
- widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
-
- # Add items
- widget_page.select_item_quantity(widget_items[0].name, 2)
- widget_page.click_buy_button()
-
- # Wait for checkout to open
- page.wait_for_timeout(2000)
-
- # Verify cookies set
- cookies = context.cookies()
- assert len(cookies) > 0
-```
-
-## Debugging
-
-### View Browser in Action
-
-Run tests with `--headed` flag to see the browser:
-
-```bash
-pytest src/tests/e2e/test_widget_embedding.py::TestWidgetEmbedding::test_widget_loads_successfully --headed
-```
-
-### Slow Down for Observation
-
-Use `--slowmo` to slow operations (milliseconds):
-
-```bash
-pytest src/tests/e2e/ --headed --slowmo 1000
-```
-
-### Interactive Debugging
-
-Use `--pdb` to drop into debugger on failure:
-
-```bash
-pytest src/tests/e2e/test_widget_cart.py --headed --pdb
-```
-
-### Record Videos
-
-Generate video recordings of test runs:
-
-```bash
-pytest src/tests/e2e/ --video on
-
-# Videos saved to: test-results/
-```
-
-### Generate Trace Files
-
-For detailed debugging with Playwright Inspector:
-
-```bash
-pytest src/tests/e2e/ --tracing on
-
-# View traces with:
-playwright show-trace test-results/.../trace.zip
-```
-
-## Troubleshooting
-
-### Browser binaries not found
-
-**Error:** `playwright._impl._errors.Error: Executable doesn't exist`
-
-**Solution:**
-```bash
-python -m playwright install chromium
-```
-
-### Django database errors
-
-**Error:** `django.db.utils.OperationalError: no such table`
-
-**Solution:**
-```bash
-# Run migrations in test settings
-DJANGO_SETTINGS_MODULE=tests.settings python manage.py migrate
-```
-
-### Live server not starting
-
-**Error:** `OSError: [Errno 98] Address already in use`
-
-**Solution:** Kill processes using the port or let pytest assign random ports automatically (default behavior).
-
-### Widget not loading
-
-**Issue:** Widget displays loading spinner indefinitely
-
-**Debug steps:**
-1. Check browser console for JavaScript errors:
- ```python
- page.on("console", lambda msg: print(f"Console: {msg.text}"))
- ```
-
-2. Verify widget JavaScript is built:
- ```bash
- ls -l src/pretix/static/pretixpresale/widget/dist/
- ```
-
-3. Check Django static files are served:
- ```bash
- DJANGO_SETTINGS_MODULE=tests.settings python manage.py collectstatic --noinput
- ```
-
-### Timeout errors
-
-**Error:** `playwright._impl._errors.TimeoutError: Timeout 10000ms exceeded`
-
-**Solution:** Increase timeout for specific operations:
-```python
-page.wait_for_selector('.pretix-widget', timeout=30000)
-```
-
-Or globally in conftest.py:
-```python
-@pytest.fixture
-def page(context):
- page = context.new_page()
- page.set_default_timeout(30000) # 30 seconds
- yield page
- page.close()
-```
-
-### Cookie issues
-
-**Issue:** Cart cookies not being set
-
-**Debug:**
-```python
-# Print all cookies
-cookies = context.cookies()
-for cookie in cookies:
- print(f"{cookie['name']}: {cookie['value']}")
-```
-
-Check domain/path settings match your test server.
-
-## CI/CD Integration
-
-### GitHub Actions Example
-
-```yaml
-name: E2E Tests
-
-on: [push, pull_request]
-
-jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
-
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: '3.11'
-
- - name: Install dependencies
- run: |
- pip install -r requirements.txt
- pip install pytest-playwright
- python -m playwright install --with-deps chromium
-
- - name: Run E2E tests
- run: |
- pytest src/tests/e2e/ --browser chromium --video on
-
- - name: Upload test artifacts
- if: always()
- uses: actions/upload-artifact@v3
- with:
- name: test-results
- path: test-results/
-```
-
-## Configuration
-
-### setup.cfg
-
-Playwright configuration is in `/home/rash/Projects/pretix/src/setup.cfg`:
-
-```ini
-[tool:pytest]
-DJANGO_SETTINGS_MODULE = tests.settings
-addopts = -rw
-# Uncomment for debugging:
-# addopts = -rw --headed --slowmo 500
-# --browser chromium
-```
-
-### Browser Context Args
-
-Customize in `conftest.py`:
-
-```python
-@pytest.fixture(scope="session")
-def browser_context_args(browser_context_args):
- return {
- **browser_context_args,
- "viewport": {"width": 1280, "height": 720},
- "locale": "en-US",
- "timezone_id": "America/New_York",
- }
-```
-
-## Writing New Tests
-
-### Test Structure
-
-```python
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestFeatureName:
- """Test description."""
-
- def test_specific_behavior(
- self,
- page: Page,
- live_server_url: str,
- widget_organizer,
- widget_event,
- widget_items,
- widget_page
- ):
- """
- Test should description.
-
- Given: initial state
- When: action occurs
- Then: expected outcome
- """
- # Arrange
- widget_page.goto(live_server_url, widget_organizer.slug, widget_event.slug)
-
- # Act
- widget_page.select_item_quantity(widget_items[0].name, 1)
-
- # Assert
- expect(page.locator('.some-element')).to_be_visible()
-```
-
-### Best Practices
-
-1. **Use descriptive test names** - `test_variation_quantity_updates_on_input` vs `test_var`
-2. **Use WidgetPage helpers** - Encapsulate common interactions
-3. **Wait for elements** - Use `expect().to_be_visible()` instead of `wait_for_timeout()`
-4. **Mark Django tests** - Always use `@pytest.mark.django_db` when accessing database
-5. **Clean test data** - Use fixtures, let pytest handle cleanup
-6. **Verify user-visible behavior** - Test what users see, not implementation details
-
-## Related Documentation
-
-- [Playwright Python Docs](https://playwright.dev/python/)
-- [pytest-playwright Plugin](https://github.com/microsoft/playwright-pytest)
-- [Pretix Widget Documentation](https://docs.pretix.eu/guides/widget/)
-- [Plan Document](/home/rash/.claude/plans/snazzy-wishing-horizon.md) - Complete test specification
-
-## Test Coverage
-
-Current test files cover Phase 1 (Critical Path):
-
-- ✅ Widget embedding & initialization (6 tests)
-- ✅ Product variations (7 tests)
-- ✅ Quantity controls & free price (11 tests)
-- ✅ Cart management & checkout (7 tests)
-
-**Total: 31 tests implemented**
-
-See plan document for complete 130-test specification covering all widget features.
diff --git a/src/tests/e2e/test_css_check.py b/src/tests/e2e/test_css_check.py
deleted file mode 100644
index 82c3a51dbb..0000000000
--- a/src/tests/e2e/test_css_check.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-Test to verify CSS is properly compiled without SCSS syntax.
-"""
-import pytest
-from playwright.sync_api import Page
-
-
-@pytest.mark.django_db
-def test_css_contains_no_scss_syntax(
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items
-):
- """Verify CSS is compiled and doesn't contain SCSS syntax."""
- # Fetch the CSS directly
- 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)
-
- print(f"Status: {response.status}")
- assert response.status == 200, f"CSS returned {response.status}"
-
- css_content = response.text()
-
- # Check that CSS doesn't contain SCSS syntax
- scss_indicators = [
- '@include', # SCSS mixins
- '@extend', # SCSS extends
- '$', # SCSS variables (though $ can appear in selectors, check more carefully)
- ]
-
- print(f"\nCSS length: {len(css_content)} characters")
- print(f"\nFirst 500 chars of CSS:\n{css_content[:500]}")
-
- has_scss = False
- for indicator in scss_indicators:
- if indicator in css_content:
- # For $, be more specific - check if it's actually a variable
- if indicator == '$':
- # Look for variable patterns like $variable-name or $variable_name
- import re
- if re.search(r'\$[a-zA-Z_]', css_content):
- has_scss = True
- print(f"\n⚠️ Found SCSS syntax: {indicator}")
- # Find and print examples
- matches = re.findall(r'\$[a-zA-Z_][a-zA-Z0-9_-]*', css_content)
- print(f"Examples: {matches[:5]}")
- else:
- has_scss = True
- print(f"\n⚠️ Found SCSS syntax: {indicator}")
- # Find line with the indicator
- for i, line in enumerate(css_content.split('\n')[:100]):
- if indicator in line:
- print(f"Line {i+1}: {line}")
- break
-
- if not has_scss:
- print("\n✅ CSS is properly compiled - no SCSS syntax found!")
-
- assert not has_scss, "CSS contains SCSS syntax - not properly compiled!"
diff --git a/src/tests/e2e/test_views.py b/src/tests/e2e/test_views.py
deleted file mode 100644
index b128af7c30..0000000000
--- a/src/tests/e2e/test_views.py
+++ /dev/null
@@ -1,50 +0,0 @@
-"""
-Test views for E2E widget testing.
-
-These views serve HTML pages with embedded widgets for testing purposes.
-"""
-from django.http import HttpResponse
-from django.template import Template, Context
-from django.views import View
-
-
-class WidgetTestPageView(View):
- """
- Serves a simple HTML page with the pretix widget embedded.
-
- Used for E2E testing to simulate how the widget would be embedded
- in a customer's website.
- """
-
- def get(self, request, organizer, event):
- """Render test page with widget embedded."""
- # Build event URL
- event_url = request.build_absolute_uri(
- f"/{organizer}/{event}/"
- )
-
- # Build widget script URL (old version)
- widget_script_url = request.build_absolute_uri(
- f"/widget/v2.en.js"
- )
-
- html_content = f"""
-
-
-
-
- Widget Test Page
-
-
- Pretix Widget E2E Test Page
-
-
-
-
-
-
-
-
-"""
-
- return HttpResponse(html_content, content_type="text/html")
diff --git a/src/tests/e2e/test_widget_accessibility.py b/src/tests/e2e/test_widget_accessibility.py
deleted file mode 100644
index e44adae165..0000000000
--- a/src/tests/e2e/test_widget_accessibility.py
+++ /dev/null
@@ -1,303 +0,0 @@
-"""
-E2E Tests for Accessibility
-
-Tests that verify:
-- ARIA labels on main widget wrapper
-- Heading roles and levels
-- Voucher input labeling
-- Buy button aria-describedby
-- Keyboard navigation
-- Quantity control labels
-- Calendar table accessibility
-- Variations toggle aria-expanded/aria-controls
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestWidgetAriaLabels:
- """Test ARIA attributes on the widget structure."""
-
- def test_widget_wrapper_has_role_article(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Main widget wrapper should have role="article"
- and aria-label with event name.
- """
- 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', event.name)
-
- def test_widget_wrapper_is_focusable(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget wrapper should have tabindex="0" for keyboard access.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- wrapper = page.locator('.pretix-widget-wrapper')
- expect(wrapper).to_have_attribute('tabindex', '0')
-
- def test_event_name_has_heading_role(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Event name heading in event form should have role="heading"
- with aria-level="2".
-
- Note: Event header is only shown when display_event_info
- is explicitly enabled for single events (auto mode hides it).
- We use the display-event-info attribute to force it on.
- """
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'display-event-info': 'true'}
- )
-
- heading = page.locator(
- '.pretix-widget-event-header strong[role="heading"]')
- expect(heading).to_be_visible()
- expect(heading).to_have_attribute('aria-level', '2')
-
-
-@pytest.mark.django_db
-class TestQuantityControlAccessibility:
- """Test accessibility of quantity controls."""
-
- def test_increment_button_has_aria_label(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Plus/minus buttons should have descriptive aria-labels.
- """
- 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("{items[0].name}")')
- inc_btn = item_elem.locator('button[aria-label]').last
- dec_btn = item_elem.locator('button[aria-label]').first
-
- # Should have aria-labels
- inc_label = inc_btn.get_attribute('aria-label')
- dec_label = dec_btn.get_attribute('aria-label')
- assert inc_label is not None and len(inc_label) > 0
- assert dec_label is not None and len(dec_label) > 0
-
- def test_quantity_input_has_aria_labelledby(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Quantity input should be connected to a label via aria-labelledby.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{items[0].name}")')
- qty_input = item_elem.locator('input[type="number"]')
-
- labelledby = qty_input.get_attribute('aria-labelledby')
- assert labelledby is not None and len(labelledby) > 0
-
-
-@pytest.mark.django_db
-class TestVoucherAccessibility:
- """Test accessibility of voucher input."""
-
- def test_voucher_input_has_aria_labelledby(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- voucher,
- widget_page
- ):
- """
- Voucher input should reference the headline via aria-labelledby.
- """
- 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')
-
- # Headline should have an ID
- headline_id = headline.get_attribute('id')
- assert headline_id is not None
-
- # Input should reference it
- labelledby = voucher_input.get_attribute('aria-labelledby')
- assert labelledby == headline_id
-
-
-@pytest.mark.django_db
-class TestVariationAccessibility:
- """Test accessibility of variation toggles."""
-
- def test_variations_toggle_has_aria_expanded(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """
- Variations toggle button should have aria-expanded
- and aria-controls attributes.
- """
- item, _ = item_with_variations
-
- 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[aria-expanded]')
-
- # Should start collapsed
- expect(toggle_btn).to_have_attribute('aria-expanded', 'false')
-
- # Should reference the variations container
- controls = toggle_btn.get_attribute('aria-controls')
- assert controls is not None
-
- # Click to expand
- toggle_btn.click()
- expect(toggle_btn).to_have_attribute('aria-expanded', 'true')
-
-
-@pytest.mark.django_db
-class TestCalendarAccessibility:
- """Test accessibility of calendar view."""
-
- def test_calendar_table_is_focusable(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event_series,
- widget_page
- ):
- """
- Calendar table should have tabindex="0" and aria-labelledby.
- """
- event, _ = event_series
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'list-type': 'calendar'}
- )
-
- table = page.locator('.pretix-widget-event-calendar-table')
- expect(table).to_have_attribute('tabindex', '0')
-
- # Should be labeled by the month heading
- labelledby = table.get_attribute('aria-labelledby')
- assert labelledby is not None
-
- def test_calendar_day_headers_have_aria_labels(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event_series,
- widget_page
- ):
- """
- Calendar day-of-week headers should have full day names
- as aria-labels (Mo -> Monday, Tu -> Tuesday, etc.).
- """
- event, _ = event_series
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'list-type': 'calendar'}
- )
-
- # Check first day header has aria-label
- first_header = page.locator(
- '.pretix-widget-event-calendar-table thead th').first
- label = first_header.get_attribute('aria-label')
- assert label is not None
- # Should be a full day name like "Monday"
- assert len(label) > 2
-
-
-@pytest.mark.django_db
-class TestKeyboardNavigation:
- """Test keyboard navigation through the widget."""
-
- def test_tab_reaches_interactive_elements(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Pressing Tab should cycle through interactive elements
- (inputs, buttons) within the widget.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Tab through several elements
- focused_tags = set()
- for _ in range(10):
- page.keyboard.press('Tab')
- tag = page.evaluate('() => document.activeElement.tagName')
- focused_tags.add(tag)
-
- # Should have reached at least inputs and buttons
- assert 'INPUT' in focused_tags or 'BUTTON' in focused_tags
diff --git a/src/tests/e2e/test_widget_availability.py b/src/tests/e2e/test_widget_availability.py
deleted file mode 100644
index 72eaab0c5c..0000000000
--- a/src/tests/e2e/test_widget_availability.py
+++ /dev/null
@@ -1,202 +0,0 @@
-"""
-E2E Tests for Availability States
-
-Tests that verify:
-- Sold out items show "Sold out" message
-- Low stock items show "currently available: N"
-- Require-voucher items show voucher message
-- Not-yet-available items show "Not yet available"
-- Available items show quantity selector
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestSoldOutState:
- """Test sold out availability display."""
-
- def test_sold_out_shows_message(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_sold_out,
- widget_page
- ):
- """Sold out item should show 'Sold out' text."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_sold_out.name}")')
- expect(item_elem).to_be_visible()
-
- # Should show "Sold out" in the availability area
- avail = item_elem.locator('.pretix-widget-availability-gone')
- expect(avail).to_be_visible()
-
- def test_sold_out_has_no_quantity_selector(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_sold_out,
- widget_page
- ):
- """Sold out item should not show any input controls."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_sold_out.name}")')
- expect(item_elem).to_be_visible()
-
- # Should not have any input controls
- assert item_elem.locator('input').count() == 0
-
-
-@pytest.mark.django_db
-class TestQuotaLeftDisplay:
- """Test quota-left indicator for low stock items."""
-
- def test_low_stock_shows_currently_available(
- self,
- page: Page,
- live_server_url: str,
- 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(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_low_stock.name}")')
- expect(item_elem).to_be_visible()
-
- # Should show quota left text containing "3"
- # The widget uses "currently available: 3" format
- expect(item_elem).to_contain_text('3')
-
- def test_low_stock_still_has_quantity_selector(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_low_stock,
- widget_page
- ):
- """Low stock items should still be purchasable."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_low_stock.name}")')
- expect(item_elem).to_be_visible()
-
- # Should have quantity input
- expect(item_elem.locator('input')).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestRequireVoucherState:
- """Test require-voucher unavailability message."""
-
- def test_require_voucher_shows_message(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_require_voucher,
- widget_page
- ):
- """Item requiring voucher should show 'Only available with a voucher'."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_require_voucher.name}")')
- expect(item_elem).to_be_visible()
-
- # Should show unavailability message with voucher link
- unavail = item_elem.locator('.pretix-widget-availability-unavailable')
- expect(unavail).to_be_visible()
- expect(unavail).to_contain_text('voucher')
-
- def test_require_voucher_has_link_to_voucher_input(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_require_voucher,
- voucher,
- widget_page
- ):
- """Voucher-required message should link to the voucher input field."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- 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)
- link = unavail.locator('a')
- expect(link).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestNotYetAvailable:
- """Test not-yet-available state."""
-
- def test_future_item_shows_not_yet_available(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_not_yet_available,
- widget_page
- ):
- """Item with future available_from should show 'Not yet available'."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_not_yet_available.name}")')
- expect(item_elem).to_be_visible()
-
- # Should show unavailability message
- unavail = item_elem.locator('.pretix-widget-availability-unavailable')
- expect(unavail).to_be_visible()
-
- def test_future_item_has_no_quantity_selector(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_not_yet_available,
- widget_page
- ):
- """Not-yet-available items should not have quantity controls."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_not_yet_available.name}")')
- expect(item_elem).to_be_visible()
-
- # Should not have any input controls
- assert item_elem.locator('input').count() == 0
diff --git a/src/tests/e2e/test_widget_cart.py b/src/tests/e2e/test_widget_cart.py
deleted file mode 100644
index 26d91fa780..0000000000
--- a/src/tests/e2e/test_widget_cart.py
+++ /dev/null
@@ -1,292 +0,0 @@
-"""
-E2E Tests for Cart Management & Checkout Flow
-
-Tests that verify:
-- Adding items to cart opens iframe checkout
-- Empty cart does not open checkout
-- Cart persistence via cookies
-- Resume checkout after page reload
-- Multiple item selection
-- Mixed input types (checkbox + quantity)
-"""
-import pytest
-from playwright.sync_api import Page, expect, BrowserContext
-
-
-@pytest.mark.django_db
-class TestCartBasics:
- """Test basic cart functionality."""
-
- def test_add_to_cart_opens_iframe_checkout(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """Selecting items and clicking Buy should open iframe checkout."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- widget_page.select_item_quantity(items[0].name, 2)
- widget_page.click_buy_button()
-
- # Iframe checkout should open
- widget_page.wait_for_iframe_checkout()
-
- iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
- src = iframe_elem.get_attribute('src')
- assert 'iframe=1' in src
- assert 'take_cart_id' in src
-
- # page.pause()
-
- def test_empty_cart_does_not_open_checkout(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """Clicking Buy without selecting items should not open checkout."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Don't select any items, just click buy
- widget_page.click_buy_button()
- page.wait_for_timeout(1000)
-
- # No iframe should have opened
- expect(page.locator('.pretix-widget-frame-shown')).not_to_be_visible()
-
- # Widget should still be visible (didn't navigate away)
- expect(page.locator('.pretix-widget')).to_be_visible()
-
- # Items should still be there
- expect(page.locator(
- f'.pretix-widget-item:has-text("{items[0].name}")'
- )).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestCartPersistence:
- """Test cart persistence across page reloads."""
-
- def test_cart_cookie_set_after_checkout(
- self,
- page: Page,
- context: BrowserContext,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """Adding items to cart should create a pretix_widget cookie."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- 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)
- widget_page.wait_for_iframe_checkout()
- page.wait_for_timeout(2000)
-
- cookies = context.cookies()
- widget_cookies = [c for c in cookies if c['name'].startswith('pretix_widget_')]
- assert len(widget_cookies) > 0, (
- f"Expected pretix_widget cookie after checkout, got: {[c['name'] for c in cookies]}"
- )
-
- def test_resume_checkout_after_reload(
- self,
- page: Page,
- context: BrowserContext,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """After creating a cart and reloading, widget should show resume option."""
- 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(items[0].name, 1)
- widget_page.click_buy_button()
- widget_page.wait_for_iframe_checkout()
-
- # Wait for cart cookie to be set (set in buy_callback when cart is created)
- page.wait_for_function(
- "() => document.cookie.includes('pretix_widget_')",
- timeout=10000
- )
-
- # Close iframe and reload
- widget_page.close_iframe()
- page.reload()
- widget_page.wait_for_widget_load()
-
- # Should show "Resume checkout" button (class: pretix-widget-resume-button)
- resume_btn = page.locator('.pretix-widget-resume-button')
- expect(resume_btn).to_be_visible(timeout=5000)
-
-
-@pytest.mark.django_db
-class TestIframeCheckout:
- """Test iframe checkout flow (enabled via skip-ssl-check + SITE_URL fix)."""
-
- def test_iframe_checkout_opens(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """With skip-ssl-check, checkout should open in iframe on HTTP."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- widget_page.select_item_quantity(items[0].name, 1)
- widget_page.click_buy_button()
-
- widget_page.wait_for_iframe_checkout()
-
- iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
- src = iframe_elem.get_attribute('src')
- assert 'iframe=1' in src
- assert organizer.slug in src
-
- def test_close_iframe_button(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """User should be able to close checkout iframe."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- widget_page.select_item_quantity(items[0].name, 1)
- widget_page.click_buy_button()
-
- widget_page.wait_for_iframe_checkout()
- page.wait_for_timeout(3000)
-
- widget_page.close_iframe()
-
- expect(page.locator('.pretix-widget-frame-shown')).not_to_be_visible()
-
- def test_iframe_checkout_has_take_cart_id(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """Iframe checkout URL should include take_cart_id parameter."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- widget_page.select_item_quantity(items[0].name, 2)
- widget_page.click_buy_button()
-
- widget_page.wait_for_iframe_checkout()
-
- iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
- src = iframe_elem.get_attribute('src')
- assert 'take_cart_id' in src
-
- def test_overlay_visible_during_checkout(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """Iframe checkout overlay container should be visible during checkout."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- widget_page.select_item_quantity(items[0].name, 1)
- widget_page.click_buy_button()
-
- widget_page.wait_for_iframe_checkout()
-
- overlay = page.locator('.pretix-widget-frame-holder')
- expect(overlay).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestMultipleItemSelection:
- """Test selecting and submitting multiple items."""
-
- def test_select_multiple_different_items(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """Selecting multiple items should open checkout with all of them."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Select multiple items
- widget_page.select_item_quantity(items[0].name, 2)
- widget_page.select_item_quantity(items[1].name, 1)
-
- widget_page.click_buy_button()
-
- # Iframe should open with both items in the cart
- widget_page.wait_for_iframe_checkout()
-
- iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
- src = iframe_elem.get_attribute('src')
- assert 'take_cart_id' in src
-
- def test_mixed_checkbox_and_quantity_items(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- item_single_select,
- widget_page
- ):
- """Selecting both checkbox and quantity items should open checkout."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Select quantity item
- widget_page.select_item_quantity(items[0].name, 3)
-
- # Select checkbox item
- widget_page.select_item_quantity(item_single_select.name, 1)
-
- widget_page.click_buy_button()
-
- # Iframe should open
- widget_page.wait_for_iframe_checkout()
-
- iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
- src = iframe_elem.get_attribute('src')
- assert 'take_cart_id' in src
diff --git a/src/tests/e2e/test_widget_categories.py b/src/tests/e2e/test_widget_categories.py
deleted file mode 100644
index bd5fa47879..0000000000
--- a/src/tests/e2e/test_widget_categories.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""
-E2E Tests for Categories & Organization
-
-Tests that verify:
-- Category headers display correctly
-- Category descriptions render
-- Items are grouped under their respective categories
-- Category sort order is maintained
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestCategoryDisplay:
- """Test category header and description rendering."""
-
- def test_category_headers_display(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Category names should be shown as h3 headers.
-
- The items fixture creates a 'Tickets' category.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Category header should be visible
- category_header = page.locator(
- '.pretix-widget-category-name:text-is("Tickets")')
- expect(category_header).to_be_visible()
-
- def test_category_description_renders(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items_with_category_description,
- widget_page
- ):
- """
- Category descriptions should be displayed below category name.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Category description should be visible
- desc = page.locator('.pretix-widget-category-description')
- expect(desc.first).to_be_visible()
- expect(desc.first).to_contain_text('Early bird tickets available')
-
- def test_items_grouped_by_category(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items_multiple_categories,
- widget_page
- ):
- """
- Items should be grouped under respective categories
- and maintain category sort order.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Both categories should be visible
- expect(page.locator(
- '.pretix-widget-category-name:text-is("Music")'
- )).to_be_visible()
- expect(page.locator(
- '.pretix-widget-category-name:text-is("Food & Drink")'
- )).to_be_visible()
-
- # Concert Ticket should be under "Music" category
- music_cat = page.locator(
- '.pretix-widget-category:has(.pretix-widget-category-name'
- ':text-is("Music"))')
- expect(music_cat.locator(
- '.pretix-widget-item:has-text("Concert Ticket")'
- )).to_be_visible()
-
- # Food Pass should be under "Food & Drink" category
- food_cat = page.locator(
- '.pretix-widget-category:has(.pretix-widget-category-name'
- ':text-is("Food & Drink"))')
- expect(food_cat.locator(
- '.pretix-widget-item:has-text("Food Pass")'
- )).to_be_visible()
-
- # Verify ordering: Music (position=0) should come before
- # Food & Drink (position=1)
- categories = page.locator('.pretix-widget-category-name')
- first_cat = categories.nth(0)
- expect(first_cat).to_have_text('Music')
diff --git a/src/tests/e2e/test_widget_config.py b/src/tests/e2e/test_widget_config.py
deleted file mode 100644
index 5b1732d26c..0000000000
--- a/src/tests/e2e/test_widget_config.py
+++ /dev/null
@@ -1,182 +0,0 @@
-"""
-E2E Tests for Widget Configuration Attributes
-
-Tests that verify:
-- items attribute filters to specific products
-- categories attribute filters by category
-- disable-vouchers hides voucher input
-- disable-iframe forces new tab checkout
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestItemsFilter:
- """Test items attribute for filtering products."""
-
- def test_items_attribute_shows_only_specified_items(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- When items="" is set, only that item should be shown.
- """
- # Get the first item's ID
- target_item = items[0]
- other_item = items[1]
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- items=str(target_item.pk)
- )
-
- # Target item should be visible
- expect(page.locator(
- f'.pretix-widget-item:has-text("{target_item.name}")'
- )).to_be_visible()
-
- # Other item should NOT be visible
- expect(page.locator(
- f'.pretix-widget-item:has-text("{other_item.name}")'
- )).to_have_count(0)
-
- def test_items_attribute_with_multiple_ids(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- When items=",", both items should be shown.
- """
- ids = ','.join(str(item.pk) for item in items)
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- items=ids
- )
-
- # Both items should be visible
- for item in items:
- expect(page.locator(
- f'.pretix-widget-item:has-text("{item.name}")'
- )).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestCategoriesFilter:
- """Test categories attribute for filtering by category."""
-
- def test_categories_attribute_shows_only_specified_category(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items_multiple_categories,
- widget_page
- ):
- """
- When categories="" is set, only items from that category
- should be shown.
- """
- items = items_multiple_categories
- # Get the category of the first item (Music)
- target_category = items[0].category
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- categories=str(target_category.pk)
- )
-
- # Music item should be visible
- expect(page.locator(
- f'.pretix-widget-item:has-text("{items[0].name}")'
- )).to_be_visible()
-
- # Food item should NOT be visible
- expect(page.locator(
- f'.pretix-widget-item:has-text("{items[1].name}")'
- )).to_have_count(0)
-
-
-@pytest.mark.django_db
-class TestDisableVouchers:
- """Test disable-vouchers attribute."""
-
- def test_disable_vouchers_hides_voucher_input(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- voucher,
- widget_page
- ):
- """
- When disable-vouchers is set, voucher input should be hidden
- even when vouchers exist.
- """
- # Navigate with disable-vouchers attribute
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'disable-vouchers': ''}
- )
-
- # Voucher section should NOT be visible
- voucher_section = page.locator('.pretix-widget-voucher')
- expect(voucher_section).to_have_count(0)
-
-
-@pytest.mark.django_db
-class TestDisableIframe:
- """Test disable-iframe attribute."""
-
- def test_disable_iframe_opens_new_tab(
- self,
- page: Page,
- live_server_url: str,
- 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(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'disable-iframe': ''}
- )
-
- # Select quantity for an item
- widget_page.select_item_quantity('General Admission', 1)
-
- # Clicking buy should open a new tab (popup), not an iframe
- with page.expect_popup() as popup_info:
- widget_page.click_buy_button()
-
- popup = popup_info.value
- # New tab should navigate to checkout URL
- assert organizer.slug in popup.url
- popup.close()
diff --git a/src/tests/e2e/test_widget_display_modes.py b/src/tests/e2e/test_widget_display_modes.py
deleted file mode 100644
index 09e4f1f095..0000000000
--- a/src/tests/e2e/test_widget_display_modes.py
+++ /dev/null
@@ -1,310 +0,0 @@
-"""
-E2E Tests for Widget Display Modes
-
-Tests that verify:
-- Default widget mode shows full ticket shop
-- Calendar view for event series
-- List view for event series
-- Button mode opens checkout directly
-- Calendar navigation (next/prev month)
-- Clicking a date in calendar navigates to event
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestWidgetMode:
- """Test default widget mode (full ticket shop)."""
-
- def test_widget_mode_shows_items_and_buy_button(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Default widget mode should show full product listing
- with categories, items, and a buy button.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Should show items
- for item in items:
- expect(page.locator(
- f'.pretix-widget-item:has-text("{item.name}")'
- )).to_be_visible()
-
- # Should show buy/add-to-cart button
- expect(page.locator(
- 'button:has-text("Add to cart"), button:has-text("Buy")'
- ).first).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestCalendarView:
- """Test calendar display mode for event series."""
-
- def test_calendar_view_displays_month_grid(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event_series,
- widget_page
- ):
- """
- Calendar view should show a monthly grid with event dates.
- """
- event, subevents = event_series
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'list-type': 'calendar'}
- )
-
- # Should show calendar table
- expect(page.locator(
- '.pretix-widget-event-calendar-table'
- )).to_be_visible()
-
- # Should have day cells with events
- event_cells = page.locator('.pretix-widget-has-events')
- expect(event_cells.first).to_be_visible()
-
- def test_calendar_view_navigation_next_month(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event_series,
- widget_page
- ):
- """
- Clicking next month button should navigate to the next month.
- """
- event, subevents = event_series
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'list-type': 'calendar'}
- )
-
- # Get current month heading text
- header = page.locator('.pretix-widget-event-calendar-head')
- initial_text = header.inner_text()
-
- # Click next month button
- next_btn = page.locator(
- '.pretix-widget-event-calendar-head a').last
- next_btn.click()
-
- # Wait for calendar to update
- page.wait_for_timeout(1000)
-
- # Month heading should change
- updated_text = header.inner_text()
- assert updated_text != initial_text
-
- def test_calendar_event_links_are_clickable(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event_series,
- widget_page
- ):
- """
- Calendar event entries should be clickable links that show
- event name, time, and availability status.
-
- Note: Full subevent navigation requires domain configuration
- (target_url resolves to configured domain, not live_server).
- We verify the links exist and have correct structure.
- """
- event, _ = event_series
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'list-type': 'calendar'}
- )
-
- # Event links should exist and show event info
- event_link = page.locator(
- '.pretix-widget-event-calendar-event').first
- expect(event_link).to_be_visible()
-
- # Should show event name
- expect(event_link.locator(
- '.pretix-widget-event-calendar-event-name'
- )).to_be_visible()
-
- # Should show time range
- expect(event_link.locator(
- '.pretix-widget-event-calendar-event-date'
- )).to_be_visible()
-
- # Should show availability ("Buy now" for available events)
- expect(event_link.locator(
- '.pretix-widget-event-calendar-event-availability'
- )).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestListView:
- """Test list display mode for event series."""
-
- def test_list_view_displays_events(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event_series,
- widget_page
- ):
- """
- List view should display events as a linear list.
- """
- event, subevents = event_series
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'list-type': 'list'}
- )
-
- # Should show event list entries
- entries = page.locator('.pretix-widget-event-list-entry')
- expect(entries.first).to_be_visible()
-
- # Should have multiple entries
- assert entries.count() > 0
-
- def test_list_view_shows_event_names(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event_series,
- widget_page
- ):
- """
- List view should show subevent names.
- """
- event, subevents = event_series
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'list-type': 'list'}
- )
-
- # At least the first subevent name should appear
- expect(page.locator(
- f':text-is("{subevents[0].name}")'
- )).to_be_visible()
-
- def test_list_view_entries_show_availability(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event_series,
- widget_page
- ):
- """
- List view entries should show availability status.
-
- Note: Full subevent navigation requires domain configuration
- (target_url resolves to configured domain, not live_server).
- We verify the entries have correct structure.
- """
- event, _ = event_series
-
- widget_page.goto(
- live_server_url,
- organizer.slug,
- event.slug,
- **{'list-type': 'list'}
- )
-
- # Each entry should show availability info
- first_entry = page.locator(
- '.pretix-widget-event-list-entry').first
- expect(first_entry).to_be_visible()
-
- # Should show availability indicator (green = available)
- availability = first_entry.locator(
- '.pretix-widget-event-list-entry-availability')
- expect(availability).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestButtonMode:
- """Test button display mode."""
-
- def test_button_mode_shows_button(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Button mode should show a simple button.
- """
- widget_page.goto_button_test_page(
- live_server_url,
- organizer.slug,
- event.slug
- )
-
- # Wait for script to load and initialize
- page.wait_for_timeout(2000)
-
- # Should show the button
- button = page.locator('.pretix-button')
- expect(button).to_be_visible()
- expect(button).to_have_text('Buy tickets!')
-
- def test_button_click_opens_checkout(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Clicking the button should open checkout (new tab on HTTP).
- """
- widget_page.goto_button_test_page(
- live_server_url,
- organizer.slug,
- event.slug
- )
-
- page.wait_for_timeout(2000)
-
- # Click button - on HTTP it opens in new tab
- with page.expect_popup() as popup_info:
- page.locator('.pretix-button').click()
-
- popup = popup_info.value
- assert organizer.slug in popup.url
- popup.close()
diff --git a/src/tests/e2e/test_widget_edge_cases.py b/src/tests/e2e/test_widget_edge_cases.py
deleted file mode 100644
index b99dc9bbf9..0000000000
--- a/src/tests/e2e/test_widget_edge_cases.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""
-E2E Tests for Edge Cases
-
-Tests that verify:
-- Empty event (no items) displays gracefully
-- Item with min_per_order enforces minimum
-- Zero quantity submission shows warning
-- Widget handles special characters in names
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestEmptyStates:
- """Test widget behavior with empty or minimal data."""
-
- def test_event_with_no_items_shows_empty(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- widget_page
- ):
- """
- Event with no items should still load without errors.
- Should show the widget container but no item rows.
- """
- # Navigate without creating any items
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Widget should still be present
- expect(page.locator('.pretix-widget')).to_be_visible()
-
- # No items should be shown
- items = page.locator('.pretix-widget-item')
- assert items.count() == 0
-
- def test_zero_quantity_stays_on_widget(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Submitting with zero quantity should not navigate away.
- The widget should remain visible without opening checkout.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Don't select any items, just click buy
- widget_page.click_buy_button()
- page.wait_for_timeout(1000)
-
- # Widget should still be on the same page (no checkout opened)
- expect(page.locator('.pretix-widget')).to_be_visible()
-
- # Items should still be visible
- expect(page.locator(
- f'.pretix-widget-item:has-text("{items[0].name}")'
- )).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestMinPerOrder:
- """Test minimum order quantity enforcement."""
-
- def test_item_with_min_per_order_shows_message(
- self,
- page: Page,
- live_server_url: str,
- 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 = item_min_order
-
- 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()
-
- # Should show minimum order message containing "2"
- meta = item_elem.locator('.pretix-widget-item-meta')
- expect(meta).to_be_visible()
- expect(meta).to_contain_text('2')
-
-
-@pytest.mark.django_db
-class TestSpecialCharacters:
- """Test widget handles special characters correctly."""
-
- def test_item_name_with_special_characters(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_special_chars,
- widget_page
- ):
- """
- Items with special characters (umlauts, ampersands, etc.)
- should display correctly.
- """
- item = item_special_chars
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Item with special characters should be visible
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item.name}")')
- expect(item_elem).to_be_visible()
diff --git a/src/tests/e2e/test_widget_embedding.py b/src/tests/e2e/test_widget_embedding.py
deleted file mode 100644
index 5e61d9317f..0000000000
--- a/src/tests/e2e/test_widget_embedding.py
+++ /dev/null
@@ -1,190 +0,0 @@
-"""
-E2E Tests for Widget Embedding & Initialization
-
-Tests that verify the pretix widget loads correctly, initializes properly,
-and displays basic event information.
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestWidgetEmbedding:
- """Test basic widget embedding and initialization."""
-
- def test_widget_loads_successfully(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget should load and display event information.
-
- Verifies that the widget loads on the page and shows:
- - Event name
- - All configured items
- """
- # Navigate to test page with widget embedded
- widget_page.goto(
- live_server_url, organizer.slug, event.slug
- )
-
- # Verify widget container exists with event aria-label
- widget = page.locator('.pretix-widget-wrapper')
- expect(widget).to_have_attribute('aria-label', event.name)
-
- # Verify items are listed
- 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,
- organizer,
- event,
- widget_page
- ):
- """
- Widget should show loading spinner during initial load.
-
- The loading spinner should eventually disappear when data is loaded.
- """
- # Navigate to widget test page
- widget_page.goto(
- live_server_url, organizer.slug, event.slug, wait=False
- )
-
- # Wait for widget element
- page.wait_for_selector('.pretix-widget', timeout=10000)
-
- # Loading spinner should eventually be hidden
- loading = page.locator('.pretix-widget-loading')
- expect(loading).to_be_hidden(timeout=10000)
-
- def test_widget_handles_invalid_event(
- self,
- page: Page,
- live_server_url: str,
- widget_page
- ):
- """Widget should display error message for invalid event."""
- widget_page.goto(
- live_server_url, 'invalid-org', 'invalid-event', wait=False
- )
-
- # Should show widget container
- page.wait_for_selector('.pretix-widget', timeout=10000)
-
- # Should show error message
- error_msg = page.locator('.pretix-widget-error-message')
- expect(error_msg).to_be_visible(timeout=10000)
- expect(error_msg).to_contain_text('could not be loaded')
-
- def test_widget_shows_item_descriptions(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget should display item descriptions.
-
- Item descriptions should be visible for items that have them.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug
- )
-
- # Check that descriptions are shown
- for item in items:
- if item.description:
- expect(
- page.locator(f'text="{item.description}"')
- ).to_be_visible()
-
- def test_widget_shows_item_prices(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget should display item prices correctly.
-
- Prices should be formatted with currency and decimal places.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug
- )
-
- # Verify prices are shown (with currency)
- # Each item should have its price displayed
- 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 "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)
-
-
-@pytest.mark.django_db
-class TestWidgetEventInfo:
- """Test event information display in widget."""
-
- def test_widget_displays_event_date(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- widget_page
- ):
- """
- Widget should show event date and time.
-
- Note: The old widget.js implementation may not display event
- date by default. This test verifies the widget loads without
- checking for specific date display.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug
- )
-
- # Widget should be present and functional
- # (Event date display varies by configuration)
- widget = page.locator('.pretix-widget')
- expect(widget).to_be_visible()
-
- def test_widget_hides_event_info_when_configured(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- widget_page
- ):
- """
- Widget should hide event info when display-event-info="false".
-
- This test would require creating a widget embed page with the attribute.
- Currently just a placeholder for future implementation.
- """
- # TODO: This requires creating a custom HTML page with widget embed
- # For now, skip this test
- pytest.skip("Requires custom widget embed page with attributes")
diff --git a/src/tests/e2e/test_widget_errors.py b/src/tests/e2e/test_widget_errors.py
deleted file mode 100644
index 7227c57824..0000000000
--- a/src/tests/e2e/test_widget_errors.py
+++ /dev/null
@@ -1,116 +0,0 @@
-"""
-E2E Tests for Error Handling
-
-Tests that verify:
-- Error message on invalid event
-- Error message shows "Open ticket shop" link
-- Sold out items show unavailable state
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestErrorDisplay:
- """Test error messages and states."""
-
- def test_invalid_event_shows_error_message(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- widget_page
- ):
- """
- Loading a non-existent event should show an error message.
- """
- widget_page.goto(
- live_server_url, organizer.slug, 'nonexistent-event')
-
- # Should show error message
- expect(page.locator(
- '.pretix-widget-error-message'
- )).to_be_visible()
-
- def test_error_shows_open_in_new_tab_link(
- self,
- page: Page,
- live_server_url: str,
- 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(
- live_server_url, organizer.slug, 'nonexistent-event')
-
- # Should show fallback action link
- action_link = page.locator('.pretix-widget-error-action a')
- expect(action_link).to_be_visible()
-
- # Link should open in new tab
- expect(action_link).to_have_attribute('target', '_blank')
-
-
-@pytest.mark.django_db
-class TestSoldOutState:
- """Test sold out item display."""
-
- def test_sold_out_item_shows_unavailable(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_sold_out,
- widget_page
- ):
- """
- Items with zero quota should show as unavailable/sold out.
- """
- item = item_sold_out
-
- 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()
-
- # Should not have a quantity input or checkbox (sold out)
- input_count = item_elem.locator(
- 'input[type="number"], input[type="checkbox"]').count()
- assert input_count == 0
-
- def test_sold_out_item_shows_status_text(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_sold_out,
- widget_page
- ):
- """
- Sold out items should show a status message like
- "Sold out" or "Currently unavailable".
- """
- item = item_sold_out
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item.name}")')
-
- # Should show some unavailability text
- avail_col = item_elem.locator(
- '.pretix-widget-item-availability-col')
- expect(avail_col).to_be_visible()
- # The text could be "Sold out", "Currently unavailable", etc.
- avail_text = avail_col.inner_text()
- assert len(avail_text.strip()) > 0
diff --git a/src/tests/e2e/test_widget_lightbox.py b/src/tests/e2e/test_widget_lightbox.py
deleted file mode 100644
index cc3424b773..0000000000
--- a/src/tests/e2e/test_widget_lightbox.py
+++ /dev/null
@@ -1,200 +0,0 @@
-"""
-E2E Tests for Item Images & Lightbox
-
-Tests that verify:
-- Item with picture shows thumbnail
-- Clicking thumbnail opens lightbox overlay
-- Lightbox close button works
-- Lightbox has correct ARIA structure
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestItemPicture:
- """Test item picture thumbnail display."""
-
- def test_item_with_picture_shows_thumbnail(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_picture,
- widget_page
- ):
- """Item with picture should display a thumbnail image."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_with_picture.name}")')
- expect(item_elem).to_be_visible()
-
- # Should have picture element
- picture = item_elem.locator('.pretix-widget-item-picture')
- expect(picture).to_be_visible()
-
- def test_item_with_picture_has_alt_text(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_picture,
- widget_page
- ):
- """Item picture should have alt text."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_with_picture.name}")')
-
- img = item_elem.locator('.pretix-widget-item-picture')
- alt = img.get_attribute('alt')
- assert alt is not None and len(alt) > 0
-
- def test_item_with_picture_has_link(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_picture,
- widget_page
- ):
- """Picture should be wrapped in a clickable link."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_with_picture.name}")')
-
- link = item_elem.locator('.pretix-widget-item-picture-link')
- expect(link).to_be_visible()
- href = link.get_attribute('href')
- assert href is not None and len(href) > 0
-
- def test_item_has_picture_class(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_picture,
- widget_page
- ):
- """Item row should have pretix-widget-item-with-picture class."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item-with-picture:has-text("{item_with_picture.name}")')
- expect(item_elem).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestLightbox:
- """Test lightbox overlay for item pictures."""
-
- def test_click_picture_opens_lightbox(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_picture,
- widget_page
- ):
- """Clicking item picture should open lightbox overlay."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_with_picture.name}")')
-
- # Click the picture link
- link = item_elem.locator('.pretix-widget-item-picture-link')
- link.click()
-
- # Lightbox should appear
- lightbox = page.locator('.pretix-widget-lightbox-shown')
- expect(lightbox).to_be_visible()
-
- def test_lightbox_shows_fullsize_image(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_picture,
- widget_page
- ):
- """Lightbox should display fullsize image."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_with_picture.name}")')
- item_elem.locator('.pretix-widget-item-picture-link').click()
-
- # Wait for lightbox
- page.wait_for_timeout(1000)
-
- # Should have an image inside the lightbox
- lightbox_img = page.locator('.pretix-widget-lightbox-image img')
- expect(lightbox_img).to_be_visible()
-
- def test_lightbox_close_button(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_picture,
- widget_page
- ):
- """Lightbox close button should close the overlay."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_with_picture.name}")')
- item_elem.locator('.pretix-widget-item-picture-link').click()
-
- # Wait for lightbox to appear
- lightbox = page.locator('.pretix-widget-lightbox-shown')
- expect(lightbox).to_be_visible()
-
- # Click close button
- close_btn = page.locator('.pretix-widget-lightbox-close button')
- close_btn.click()
-
- # Lightbox should close
- page.wait_for_timeout(500)
- expect(page.locator('.pretix-widget-lightbox-shown')).not_to_be_visible()
-
- def test_lightbox_has_alertdialog_role(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_picture,
- widget_page
- ):
- """Lightbox dialog should have role='alertdialog'."""
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item_with_picture.name}")')
- item_elem.locator('.pretix-widget-item-picture-link').click()
-
- page.wait_for_timeout(1000)
-
- dialog = page.locator('.pretix-widget-lightbox-holder')
- role = dialog.get_attribute('role')
- assert role == 'alertdialog'
diff --git a/src/tests/e2e/test_widget_loading.py b/src/tests/e2e/test_widget_loading.py
deleted file mode 100644
index 2772544eb9..0000000000
--- a/src/tests/e2e/test_widget_loading.py
+++ /dev/null
@@ -1,108 +0,0 @@
-"""
-E2E Tests for Loading States & Performance
-
-Tests that verify:
-- Loading spinner appears during widget initialization
-- Loading spinner disappears after content loads
-- Widget loads within acceptable time
-- No JavaScript errors during widget initialization
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestLoadingStates:
- """Test loading states and transitions."""
-
- def test_loading_spinner_disappears_after_load(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Loading spinner should be hidden once widget content loads.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Loading spinner should be hidden (display:none)
- loading = page.locator('.pretix-widget-loading')
- expect(loading).to_be_hidden()
-
- def test_widget_loads_within_timeout(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget should fully load within 15 seconds.
- """
- 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("{items[0].name}")'
- )).to_be_visible(timeout=15000)
-
- def test_no_javascript_errors_on_load(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget should load without any JavaScript console errors.
- """
- errors = []
- page.on('pageerror', lambda err: errors.append(str(err)))
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # No JS errors should have occurred
- assert len(errors) == 0, f"JavaScript errors: {errors}"
-
-
-@pytest.mark.django_db
-class TestWidgetReload:
- """Test widget behavior on page interactions."""
-
- def test_widget_css_loads_correctly(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget CSS should load and apply styles.
- No SCSS syntax should leak into rendered styles.
- """
- 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)
- widget = page.locator('.pretix-widget')
- box = widget.bounding_box()
- assert box is not None
- assert box['height'] > 50 # Widget should have meaningful height
- assert box['width'] > 100 # Widget should have meaningful width
diff --git a/src/tests/e2e/test_widget_pricing.py b/src/tests/e2e/test_widget_pricing.py
deleted file mode 100644
index 5bfeae0893..0000000000
--- a/src/tests/e2e/test_widget_pricing.py
+++ /dev/null
@@ -1,249 +0,0 @@
-"""
-E2E Tests for Pricing & Tax Display
-
-Tests that verify:
-- Net vs gross price display
-- Tax information lines
-- Mixed tax rates
-- Original price strikethrough for discounts
-- Free items display
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestPriceDisplay:
- """Test price formatting and display."""
-
- def test_price_displays_with_currency(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Item prices should display with currency code.
-
- Currency format should match event settings (EUR).
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # 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,
- organizer,
- event,
- item_free,
- widget_page
- ):
- """
- Items with price 0.00 should display "FREE" instead of $0.00.
-
- Makes free items more obvious to users.
- """
- item = item_free
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Should show "FREE" text
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item.name}")')
- expect(item_elem).to_be_visible()
-
- # Should contain "FREE" or "free" text (case insensitive)
- expect(item_elem.locator('text=/free/i')).to_be_visible()
-
- def test_price_includes_decimals(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_decimals,
- widget_page
- ):
- """
- Prices should display with proper decimal formatting.
-
- EUR should show 2 decimal places (e.g., $25.00 not $25).
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Should show price with .50
- expect(page.locator('text=/EUR.*12\\.50/')).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestTaxDisplay:
- """Test tax information display."""
-
- def test_tax_rate_displayed_for_taxed_items(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_tax,
- widget_page
- ):
- """
- Items with tax should show tax rate information.
-
- Should display "incl. X% VAT" or similar.
- """
- item = item_with_tax
-
- 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()
-
- # Should show tax information
- # (could be "incl." or "plus" depending on settings)
- # Looking for "19" and "%" near each other
- expect(item_elem.locator('text=/19.*%|%.*19/')).to_be_visible()
-
- def test_items_without_tax_no_tax_line(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Items without tax rules should not show tax information.
-
- Tax line should be absent for tax-free items.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # items by default have no tax
- # We just verify the items display without errors
- for item in items:
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item.name}")')
- expect(item_elem).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestDiscountedPricing:
- """Test display of discounted prices."""
-
- def test_widget_displays_prices_without_errors(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget should display all prices without errors.
-
- This is a smoke test to ensure price rendering works.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # All items should display their prices
- for item in items:
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item.name}")')
- expect(item_elem).to_be_visible()
-
- # Should have EUR currency and price displayed
- expect(item_elem.locator('text=/EUR/')).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestPriceForVariations:
- """Test price display for items with variations."""
-
- def test_variation_price_range_when_collapsed(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """
- Collapsed variations should show price range (min - max).
-
- E.g., "$20.00 - $30.00" for variations from $20 to $30.
- """
- item, _ = item_with_variations
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Variations are collapsed by default
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item.name}")')
- expect(item_elem).to_be_visible()
-
- # Should show price range in main row (not in hidden variations)
- # 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=/EUR.*20\\.00/')
- ).to_be_visible()
- expect(price_col.locator('text=/30\\.00/')).to_be_visible()
-
- def test_expanded_variations_show_individual_prices(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """
- Expanded variations show individual prices for each variation.
-
- Each size should show its own price.
- """
- item, variations = item_with_variations
-
- 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 EUR currency
- # We don't check exact amounts because formatting may vary
- for var in variations:
- var_elem = page.locator(
- f'.pretix-widget-variation:has('
- f'strong:text-is("{var.value}"))'
- )
- expect(var_elem).to_be_visible()
-
- # Should contain EUR currency code
- expect(var_elem.locator('text=/EUR/')).to_be_visible()
diff --git a/src/tests/e2e/test_widget_quantity_controls.py b/src/tests/e2e/test_widget_quantity_controls.py
deleted file mode 100644
index d04aa00c15..0000000000
--- a/src/tests/e2e/test_widget_quantity_controls.py
+++ /dev/null
@@ -1,343 +0,0 @@
-"""
-E2E Tests for Quantity Controls & Order Limits
-
-Tests that verify:
-- Checkbox display for order_max=1 items
-- +/- buttons for multi-quantity items
-- Order minimum/maximum enforcement
-- Auto-selection of single items
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestQuantityControls:
- """Test quantity control UI elements."""
-
- def test_checkbox_for_single_select_item(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_single_select,
- widget_page
- ):
- """
- Item with order_max=1 should show checkbox instead of quantity input.
-
- For items limited to 1 per order, a checkbox is more intuitive than
- a number input.
- """
- item = item_single_select
-
- 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}")')
- expect(item_elem).to_be_visible()
-
- # Should have checkbox, NOT number input
- expect(item_elem.locator('input[type="checkbox"]')).to_be_visible()
- expect(item_elem.locator('input[type="number"]')).not_to_be_visible()
-
- def test_checkbox_selection(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_single_select,
- widget_page
- ):
- """
- Checking the checkbox should select the item.
-
- User can check/uncheck to add/remove the item.
-
- 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 = item_single_select
-
- 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"]')
-
- # Ensure checkbox is unchecked to start test
- if checkbox.is_checked():
- checkbox.uncheck()
- expect(checkbox).not_to_be_checked()
-
- # Check it
- checkbox.check()
- expect(checkbox).to_be_checked()
-
- # Uncheck it
- checkbox.uncheck()
- expect(checkbox).not_to_be_checked()
-
- def test_plus_minus_buttons_for_multi_quantity(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Items with order_max > 1 should have +/- buttons.
-
- Plus/minus buttons provide an easy way to increment/decrement quantity.
- """
- 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("{items[0].name}")')
-
- # Should have number input
- expect(item_elem.locator('input[type="number"]')).to_be_visible()
-
- # Should have + and - buttons
- expect(item_elem.locator('button:has-text("+")')).to_be_visible()
- expect(item_elem.locator('button:has-text("-")')).to_be_visible()
-
- def test_increment_button_functionality(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Clicking + button should increase quantity by 1.
-
- Each click increments the value in the number input.
- """
- widget_page.goto(live_server_url, organizer.slug, event.slug)
-
- 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
-
- # Initial value should be 0 or empty
- initial_value = number_input.input_value()
- if not initial_value:
- initial_value = "0"
-
- # Click +
- plus_button.click()
- page.wait_for_timeout(100)
-
- # Should be incremented
- expect(number_input).to_have_value("1")
-
- # Click + again
- plus_button.click()
- page.wait_for_timeout(100)
-
- expect(number_input).to_have_value("2")
-
- def test_decrement_button_functionality(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Clicking - button should decrease quantity by 1.
-
- Quantity should not go below 0.
- """
- widget_page.goto(live_server_url, organizer.slug, event.slug)
-
- 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
-
- # Set to 2
- plus_button.click()
- page.wait_for_timeout(100)
- plus_button.click()
- page.wait_for_timeout(100)
-
- expect(number_input).to_have_value("2")
-
- # Click -
- minus_button.click()
- page.wait_for_timeout(100)
-
- expect(number_input).to_have_value("1")
-
- # Click - again
- minus_button.click()
- page.wait_for_timeout(100)
-
- expect(number_input).to_have_value("0")
-
- def test_manual_quantity_input(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- User should be able to type quantity directly.
-
- Number input should accept typed values.
- """
- widget_page.goto(live_server_url, organizer.slug, event.slug)
-
- # Directly set quantity using helper
- widget_page.select_item_quantity(items[0].name, 5)
-
- # Verify value
- 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")
-
-
-@pytest.mark.django_db
-class TestOrderLimits:
- """Test order minimum and maximum enforcement."""
-
- def test_order_max_enforces_checkbox_for_single(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_single_select,
- widget_page
- ):
- """Item with order_max=1 should show checkbox (implicit max enforcement)."""
- item = item_single_select # This has order_max=1
-
- 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()
-
- # order_max=1 items get a checkbox (max is enforced by being binary)
- expect(item_elem.locator('input[type="checkbox"]')).to_be_visible()
- # No number input should exist
- expect(item_elem.locator('input[type="number"]')).not_to_be_visible()
-
- def test_submit_with_multiple_items_opens_checkout(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """Multiple items with different quantities should open iframe checkout."""
- widget_page.goto(live_server_url, organizer.slug, event.slug)
-
- widget_page.select_item_quantity(items[0].name, 2)
- widget_page.select_item_quantity(items[1].name, 1)
-
- widget_page.click_buy_button()
-
- # Iframe checkout should open
- widget_page.wait_for_iframe_checkout()
-
- iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
- src = iframe_elem.get_attribute('src')
- assert 'take_cart_id' in src
-
-
-@pytest.mark.django_db
-class TestFreePrice:
- """Test pay-what-you-want (free price) items."""
-
- def test_free_price_input_appears(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_free_price,
- widget_page
- ):
- """
- Free price item should show price input field.
-
- User should be able to enter their own price.
- """
- item = item_free_price
-
- 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()
-
- # Should show a price input (type=number for price)
- price_inputs = item_elem.locator('.pretix-widget-pricebox-price-input, input[name^="price_"]')
- expect(price_inputs.first).to_be_visible()
-
- def test_free_price_minimum_enforcement(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_free_price,
- widget_page
- ):
- """
- Free price input should have minimum value set.
-
- The min attribute should be set to the item's default price.
- """
- item = item_free_price
-
- 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
-
- # Should have min attribute
- min_value = price_input.get_attribute('min')
- assert min_value is not None
- assert float(min_value) == float(item.default_price)
-
- def test_free_price_custom_amount(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_free_price,
- widget_page
- ):
- """
- User should be able to enter custom price amount.
-
- Amount above minimum should be accepted.
- """
- item = item_free_price
-
- 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
-
- # Enter custom amount
- price_input.fill("25.00")
-
- # Verify value
- expect(price_input).to_have_value("25.00")
diff --git a/src/tests/e2e/test_widget_responsive.py b/src/tests/e2e/test_widget_responsive.py
deleted file mode 100644
index 3b905dcd9e..0000000000
--- a/src/tests/e2e/test_widget_responsive.py
+++ /dev/null
@@ -1,93 +0,0 @@
-"""
-E2E Tests for Responsive Behavior
-
-Tests that verify:
-- Mobile layout at narrow widths (pretix-widget-mobile class)
-- Layout updates on resize
-- Desktop layout at wide widths
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestResponsiveLayout:
- """Test responsive layout behavior."""
-
- def test_mobile_class_at_narrow_width(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget should add pretix-widget-mobile class when
- container width <= 800px.
- """
- # Set narrow viewport
- page.set_viewport_size({"width": 375, "height": 667})
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Should have mobile class
- expect(page.locator('.pretix-widget-mobile')).to_be_visible()
-
- def test_no_mobile_class_at_wide_width(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget should NOT have pretix-widget-mobile class when
- container width > 800px.
- """
- # Ensure wide viewport (default is 1280)
- page.set_viewport_size({"width": 1280, "height": 720})
-
- 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)
-
- # But widget itself should be present
- expect(page.locator('.pretix-widget')).to_be_visible()
-
- def test_responsive_layout_updates_on_resize(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Widget should switch to mobile layout when browser is resized
- from desktop to mobile width.
- """
- # Start with desktop viewport
- page.set_viewport_size({"width": 1280, "height": 720})
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Should not be mobile
- expect(page.locator('.pretix-widget-mobile')).to_have_count(0)
-
- # Resize to mobile
- page.set_viewport_size({"width": 375, "height": 667})
-
- # Wait for ResizeObserver to fire
- page.wait_for_timeout(500)
-
- # Should now have mobile class
- expect(page.locator('.pretix-widget-mobile')).to_be_visible()
diff --git a/src/tests/e2e/test_widget_variations.py b/src/tests/e2e/test_widget_variations.py
deleted file mode 100644
index 029f17c830..0000000000
--- a/src/tests/e2e/test_widget_variations.py
+++ /dev/null
@@ -1,235 +0,0 @@
-"""
-E2E Tests for Product Variations
-
-Tests that verify items with variations (sizes, colors, etc.) work correctly:
-- Expand/collapse behavior
-- Price ranges
-- Individual variation selection
-- Auto-expand when filtered
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestProductVariations:
- """Test product variation functionality."""
-
- def test_item_with_variations_shows_toggle_button(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """
- Item with variations should show expand/collapse button.
-
- Variations should be collapsed by default with a button to expand them.
- """
- item, variations = item_with_variations
-
- widget_page.goto(live_server_url, organizer.slug, event.slug)
-
- # Should show item name
- expect(page.locator(f'text="{item.name}"')).to_be_visible()
-
- # Should show variations toggle button
- 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")')
- expect(toggle_btn.first).to_be_visible()
-
- def test_expand_variations_on_click(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """
- Clicking toggle button should expand variations.
-
- After expanding, all variation options should be visible.
- """
- item, variations = item_with_variations
-
- widget_page.goto(live_server_url, organizer.slug, event.slug)
-
- # Expand variations
- widget_page.expand_variations(item.name)
-
- # Wait for variations to be visible
- page.wait_for_timeout(500) # Wait for animation
-
- # All variations should now be visible
- for variation in variations:
- expect(page.locator(f'text="{variation.value}"')).to_be_visible()
-
- def test_collapse_variations_on_second_click(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """Clicking toggle again should collapse variations."""
- item, variations = item_with_variations
-
- 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")')
-
- # First click - expand
- toggle_btn.first.click()
- page.wait_for_timeout(500)
-
- # Variations should be visible
- for variation in variations:
- expect(page.locator(f'text="{variation.value}"')).to_be_visible()
-
- # Second click - collapse
- toggle_btn.first.click()
- page.wait_for_timeout(500)
-
- # Variation inputs should no longer be visible
- for variation in variations:
- var_row = item_elem.locator(
- f'.pretix-widget-variation:has(strong:text-is("{variation.value}"))')
- expect(var_row.locator('input')).not_to_be_visible()
-
- def test_price_range_for_collapsed_variations(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """
- Collapsed variations should show price range.
-
- When variations have different prices, should display range like "$20 - $30".
- """
- item, variations = item_with_variations
-
- 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 "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')
-
- def test_select_variation_quantity(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """
- User should be able to select quantity for specific variation.
-
- After expanding variations, each should have its own quantity selector.
- """
- item, variations = item_with_variations
-
- widget_page.goto(live_server_url, organizer.slug, event.slug)
-
- # Expand variations
- widget_page.expand_variations(item.name)
- page.wait_for_timeout(500)
-
- # Select quantity for "Medium" variation
- widget_page.select_variation_quantity(item.name, "Medium", 2)
-
- # Verify input has the value
- medium_var = page.locator('.pretix-widget-variation:has-text("Medium")')
- input_field = medium_var.locator('input[type="number"]')
- expect(input_field).to_have_value("2")
-
- def test_variation_individual_prices(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """
- Each variation should show its individual price.
-
- When expanded, variations should display their specific prices.
- """
- item, variations = item_with_variations
-
- widget_page.goto(live_server_url, organizer.slug, event.slug)
-
- # Expand variations
- widget_page.expand_variations(item.name)
- page.wait_for_timeout(500)
-
- # Check each variation shows its price
- variation_prices = {
- 'Small': '20.00',
- 'Medium': '25.00',
- 'Large': '25.00',
- 'X-Large': '30.00',
- }
-
- for var_name, price in variation_prices.items():
- # Use exact heading match to avoid "Large" matching "X-Large"
- var_elem = page.locator(
- f'.pretix-widget-variation:has(strong:text-is("{var_name}"))'
- )
- expect(var_elem).to_be_visible()
- # Price should be visible within the variation element
- expect(var_elem.locator(f'text=/{price}/')).to_be_visible()
-
-
-@pytest.mark.django_db
-class TestVariationSubmission:
- """Test that variation selections submit correctly."""
-
- def test_submit_with_variation_selection(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_with_variations,
- widget_page
- ):
- """Submitting with a variation selected should open iframe checkout."""
- item, variations = item_with_variations
-
- widget_page.goto(live_server_url, organizer.slug, event.slug)
-
- # Expand and select a variation
- widget_page.expand_variations(item.name)
- page.wait_for_timeout(500)
- widget_page.select_variation_quantity(item.name, "Large", 1)
-
- widget_page.click_buy_button()
-
- # Iframe checkout should open with the variation in the cart
- widget_page.wait_for_iframe_checkout()
-
- iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
- src = iframe_elem.get_attribute('src')
- assert 'take_cart_id' in src
diff --git a/src/tests/e2e/test_widget_vouchers.py b/src/tests/e2e/test_widget_vouchers.py
deleted file mode 100644
index 7b568dd32a..0000000000
--- a/src/tests/e2e/test_widget_vouchers.py
+++ /dev/null
@@ -1,131 +0,0 @@
-"""
-E2E Tests for Voucher Redemption
-
-Tests that verify:
-- Voucher input field appears when vouchers exist
-- Voucher redemption flow works
-- Voucher input hidden when disable-vouchers is set
-- Voucher explanation text displays
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestVoucherDisplay:
- """Test voucher input rendering."""
-
- def test_voucher_input_appears_when_vouchers_exist(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- voucher,
- widget_page
- ):
- """
- Voucher input field should be visible when event has vouchers.
-
- The widget checks `vouchers_exist` in the API response.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Voucher section should be visible
- voucher_section = page.locator('.pretix-widget-voucher')
- expect(voucher_section).to_be_visible()
-
- # Should have the "Redeem a voucher" heading
- expect(page.locator(
- '.pretix-widget-voucher-headline'
- )).to_be_visible()
-
- # Should have the voucher input
- expect(page.locator(
- '.pretix-widget-voucher-input'
- )).to_be_visible()
-
- def test_voucher_input_hidden_when_no_vouchers(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- items,
- widget_page
- ):
- """
- Voucher input should not appear when no vouchers exist.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Voucher section should NOT be visible
- voucher_section = page.locator('.pretix-widget-voucher')
- expect(voucher_section).to_have_count(0)
-
- def test_voucher_explanation_text_displays(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- voucher,
- widget_page
- ):
- """
- Voucher explanation text should display when configured.
- """
- # Set voucher explanation text on the event
- event.settings.set(
- 'voucher_explanation_text',
- 'Enter your voucher code to get a discount.'
- )
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Explanation text should be visible
- explanation = page.locator('.pretix-widget-voucher-text')
- expect(explanation).to_be_visible()
- expect(explanation).to_contain_text('Enter your voucher code')
-
-
-@pytest.mark.django_db
-class TestVoucherRedemption:
- """Test voucher redemption flow."""
-
- def test_redeem_voucher_opens_checkout(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- voucher,
- widget_page
- ):
- """
- Entering a voucher code and clicking Redeem should open checkout.
-
- With skip-ssl-check (added by test harness), this opens in iframe.
- """
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Enter voucher code
- voucher_input = page.locator('.pretix-widget-voucher-input')
- voucher_input.fill('TESTCODE2024')
-
- # Click the Redeem button
- page.locator(
- '.pretix-widget-voucher-button-wrap button'
- ).click()
-
- # With skip-ssl-check, voucher redemption opens in iframe
- iframe = widget_page.wait_for_iframe_checkout()
-
- # The iframe src should contain the voucher code
- iframe_elem = page.locator('iframe[name^="pretix-widget-"]')
- src = iframe_elem.get_attribute('src')
- assert 'TESTCODE2024' in src or 'voucher' in src
diff --git a/src/tests/e2e/test_widget_waitinglist.py b/src/tests/e2e/test_widget_waitinglist.py
deleted file mode 100644
index d03cacdee0..0000000000
--- a/src/tests/e2e/test_widget_waitinglist.py
+++ /dev/null
@@ -1,74 +0,0 @@
-"""
-E2E Tests for Waiting List Integration
-
-Tests that verify:
-- Waiting list link appears for sold out items when enabled
-- Waiting list link URL includes correct parameters
-"""
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.mark.django_db
-class TestWaitingList:
- """Test waiting list display for sold out items."""
-
- def test_waiting_list_link_appears_when_sold_out(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_sold_out_with_waitinglist,
- widget_page
- ):
- """
- Sold out items with waiting list enabled should show
- a "Waiting list" link.
-
- Requires:
- - Event setting: waiting_list_enabled = True
- - Item: allow_waitinglist = True
- - Item availability < 100 (sold out)
- """
- item = item_sold_out_with_waitinglist
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- # Find the sold out item
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item.name}")')
- expect(item_elem).to_be_visible()
-
- # Should show waiting list link
- waiting_list = item_elem.locator(
- '.pretix-widget-waiting-list-link a')
- expect(waiting_list).to_be_visible()
-
- def test_waiting_list_link_url_contains_item_id(
- self,
- page: Page,
- live_server_url: str,
- organizer,
- event,
- item_sold_out_with_waitinglist,
- widget_page
- ):
- """
- Waiting list link URL should include item ID parameter.
- """
- item = item_sold_out_with_waitinglist
-
- widget_page.goto(
- live_server_url, organizer.slug, event.slug)
-
- item_elem = page.locator(
- f'.pretix-widget-item:has-text("{item.name}")')
- waiting_list_link = item_elem.locator(
- '.pretix-widget-waiting-list-link a')
-
- href = waiting_list_link.get_attribute('href')
- assert href is not None
- assert f'item={item.pk}' in href
- assert 'waitinglist' in href