# # This file is part of pretix (Community Edition). # # Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2020-today pretix GmbH and contributors # # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # Public License as published by the Free Software Foundation in version 3 of the License. # # ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are # applicable granting you additional permissions and placing additional restrictions on your usage of this software. # Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive # this file, see . # # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # """ E2E Test Configuration for Pretix Widget with Playwright This module provides pytest fixtures for end-to-end testing of the pretix widget using Playwright. It integrates Playwright with Django's test infrastructure. """ # TODO dev server websocket does not work, but is this relevant? import os import subprocess from datetime import date, datetime, timedelta, timezone from decimal import Decimal from urllib.error import URLError from urllib.request import urlopen import pytest from django_scopes import scopes_disabled from playwright.sync_api import ( # noqa: F401 Browser, BrowserContext, Page, expect, ) from pretix.base.models import ( Event, Item, ItemVariation, Organizer, Quota, SubEvent, Voucher, ) # Allow Django ORM operations in async context (required for Playwright integration) os.environ.setdefault("DJANGO_ALLOW_ASYNC_UNSAFE", "true") def _future_dt(days=30, hour=10, minute=0): """Build a future UTC datetime with a fixed time-of-day. Uses a relative date so tests don't expire, but pins the time component so results are deterministic regardless of when tests run. """ d = date.today() + timedelta(days=days) return datetime(d.year, d.month, d.day, hour, minute, tzinfo=timezone.utc) # ============================================================================ # Widget Asset Configuration (old Vue2 / new Vite / Vite dev server) # ============================================================================ PROJECT_ROOT = os.path.join( os.path.dirname(__file__), '../../..' ) VITE_DEV_PORT = 5180 @pytest.fixture(scope="session", autouse=True) def _widget_assets(): """ Build or check the widget JS depending on env vars. - Default: old Vue2 widget (no build needed) - PRETIX_WIDGET_VITE=1: run vite build, Django serves the output - PRETIX_WIDGET_VITE_DEV=1: uses your already-running vite dev server """ if os.environ.get("PRETIX_WIDGET_VITE_DEV"): try: urlopen(f'http://localhost:{VITE_DEV_PORT}/', timeout=2) except (URLError, OSError): raise RuntimeError( f'PRETIX_WIDGET_VITE_DEV is set but no Vite dev server found on port {VITE_DEV_PORT}. ' f'Start it with: npm run dev:widget' ) yield elif os.environ.get("PRETIX_WIDGET_VITE"): subprocess.check_call(['npm', 'run', 'build:widget'], cwd=PROJECT_ROOT) yield else: yield # Old widget, no build needed # ============================================================================ # Playwright Configuration # ============================================================================ @pytest.fixture(scope="session") def browser_context_args(browser_context_args): """Configure browser context for all tests.""" return { **browser_context_args, "viewport": {"width": 1280, "height": 720}, "locale": "en-US", "timezone_id": "Europe/Berlin", # Enable video recording for debugging (optional) # "record_video_dir": "test-results/videos/", } @pytest.fixture(scope="session") def browser_type_launch_args(browser_type_launch_args): """Configure browser launch arguments.""" return { **browser_type_launch_args, # Uncomment for debugging # "headless": False, # "slow_mo": 500, # Slow down operations by 500ms } # ============================================================================ # Django Live Server Fixtures # ============================================================================ @pytest.fixture def live_server_url(live_server, settings): """ Get the live server URL. Uses pytest-django's built-in live_server fixture which starts a Django development server for E2E tests. """ # Enable django-compressor for on-the-fly SCSS compilation settings.COMPRESS_ENABLED = True settings.COMPRESS_OFFLINE = False # Compile on-the-fly, not from cache # Re-enable SCSS precompilers (disabled in test settings) from pretix.testutils.settings import COMPRESS_PRECOMPILERS_ORIGINAL settings.COMPRESS_PRECOMPILERS = COMPRESS_PRECOMPILERS_ORIGINAL # Fix cache backend for compression settings.COMPRESS_CACHE_BACKEND = 'default' # Add testcache to CACHES if needed (for compatibility) if 'testcache' not in settings.CACHES: settings.CACHES['testcache'] = { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', } settings.SITE_URL = live_server.url # Enable Vite widget if requested via env var if os.environ.get("PRETIX_WIDGET_VITE") or os.environ.get("PRETIX_WIDGET_VITE_DEV"): settings.PRETIX_WIDGET_VITE = True return live_server.url # ============================================================================ # Test Data Fixtures - Organizers and Events # ============================================================================ @pytest.fixture @scopes_disabled() def organizer(db): """ Create an organizer for widget tests. Reuses the same pattern as existing API tests. """ return Organizer.objects.create( name='Test Organizer', slug='testorg', plugins='pretix.plugins.banktransfer,pretix.plugins.stripe' ) @pytest.fixture @scopes_disabled() def event(organizer): """Create a basic event for widget tests.""" event = Event.objects.create( organizer=organizer, name='Test Event', slug='testevent', date_from=_future_dt(days=30, hour=10), date_to=_future_dt(days=30, hour=18), currency='EUR', live=True, testmode=False, plugins='pretix.plugins.banktransfer', ) event.set_defaults() event.settings.set('timezone', 'Europe/Berlin') event.settings.set('locale', 'en') event.settings.set('locales', ['en']) return event @pytest.fixture @scopes_disabled() def items(event): """Create basic test items/products.""" from pretix.base.models import ItemCategory items = [] # Create a proper category category = ItemCategory.objects.create( event=event, name='Tickets', position=0 ) # General Admission ticket item1 = Item.objects.create( event=event, category=category, name='General Admission', default_price=Decimal('50.00'), description='Standard entry ticket', active=True, ) items.append(item1) # VIP ticket item2 = Item.objects.create( event=event, category=category, name='VIP Ticket', default_price=Decimal('150.00'), description='VIP access with special perks', active=True, ) items.append(item2) # Create quotas for each item for item in items: quota = Quota.objects.create( event=event, name=f'{item.name} Quota', size=100, ) quota.items.add(item) return items # ============================================================================ # Test Data Fixtures - Items with Variations # ============================================================================ @pytest.fixture @scopes_disabled() def item_with_variations(event): """Create an item with size variations (S, M, L, XL).""" from pretix.base.models import ItemCategory # Create category for the item category = ItemCategory.objects.create( event=event, name='Merchandise', position=1 ) item = Item.objects.create( event=event, category=category, name='Event T-Shirt', default_price=Decimal('25.00'), description='Official event t-shirt', active=True, ) variations = [] sizes_and_prices = [ ('Small', Decimal('20.00')), ('Medium', Decimal('25.00')), ('Large', Decimal('25.00')), ('X-Large', Decimal('30.00')), ] for size, price in sizes_and_prices: var = ItemVariation.objects.create( item=item, value=size, default_price=price, ) variations.append(var) # Create quota for all variations quota = Quota.objects.create( event=event, name='T-Shirt Quota', size=50, ) # Add both the item AND the variations to the quota quota.items.add(item) for var in variations: quota.variations.add(var) return item, variations @pytest.fixture @scopes_disabled() def item_single_select(event): """Create an item with max_per_order=1 (should show checkbox).""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='VIP', position=2 ) item = Item.objects.create( event=event, category=category, name='VIP Pass', default_price=Decimal('500.00'), description='Limited VIP pass - one per customer', active=True, max_per_order=1, ) quota = Quota.objects.create( event=event, name='VIP Quota', size=10, ) quota.items.add(item) return item @pytest.fixture @scopes_disabled() def item_free_price(event): """Create an item with pay-what-you-want pricing.""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Donations', position=3 ) item = Item.objects.create( event=event, category=category, name='Donation', default_price=Decimal('10.00'), description='Support our cause', active=True, free_price=True, ) quota = Quota.objects.create( event=event, name='Donation Quota', size=999, ) quota.items.add(item) return item @pytest.fixture @scopes_disabled() def item_sold_out(event): """Create a sold out item.""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Early Bird', position=4 ) item = Item.objects.create( event=event, category=category, name='Early Bird Ticket', default_price=Decimal('30.00'), description='Sold out!', active=True, ) # Create quota with size=0 (sold out) quota = Quota.objects.create( event=event, name='Early Bird Quota', size=0, ) quota.items.add(item) return item @pytest.fixture @scopes_disabled() def item_free(event): """Create a free item (price = 0.00).""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Free Stuff', position=10 ) item = Item.objects.create( event=event, category=category, name='Free Gift', default_price=Decimal('0.00'), active=True, ) quota = Quota.objects.create( event=event, name='Free Gift Quota', size=100, ) quota.items.add(item) return item @pytest.fixture @scopes_disabled() def item_with_decimals(event): """Create an item with non-zero decimal price.""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Test Category', position=11 ) item = Item.objects.create( event=event, category=category, name='Half Price Item', default_price=Decimal('12.50'), active=True, ) quota = Quota.objects.create( event=event, name='Half Price Quota', size=50, ) quota.items.add(item) return item @pytest.fixture @scopes_disabled() def item_with_tax(event): """Create an item with tax rule.""" from pretix.base.models import ItemCategory, TaxRule # Create tax rule tax_rule = TaxRule.objects.create( event=event, name='VAT', rate=Decimal('19.00'), # 19% VAT ) category = ItemCategory.objects.create( event=event, name='Taxed Items', position=12 ) item = Item.objects.create( event=event, category=category, name='Taxed Product', default_price=Decimal('100.00'), tax_rule=tax_rule, active=True, ) quota = Quota.objects.create( event=event, name='Taxed Product Quota', size=50, ) quota.items.add(item) return item # ============================================================================ # Test Data Fixtures - Edge Cases # ============================================================================ @pytest.fixture @scopes_disabled() def item_min_order(event): """Create an item with min_per_order=2.""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Group Tickets', position=13 ) item = Item.objects.create( event=event, category=category, name='Group Pass', default_price=Decimal('40.00'), active=True, min_per_order=2, ) quota = Quota.objects.create( event=event, name='Group Pass Quota', size=50, ) quota.items.add(item) return item @pytest.fixture @scopes_disabled() def item_special_chars(event): """Create an item with special characters in the name.""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Spezial', position=14 ) item = Item.objects.create( event=event, category=category, name='Böhm & Söhne Konzert', default_price=Decimal('55.00'), active=True, ) quota = Quota.objects.create( event=event, name='Special Quota', size=50, ) quota.items.add(item) return item # ============================================================================ # Test Data Fixtures - Categories # ============================================================================ @pytest.fixture @scopes_disabled() def items_with_category_description(event): """Create items with a category that has a description.""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Tickets', description='Early bird tickets available', position=0 ) item = Item.objects.create( event=event, category=category, name='Early Bird', default_price=Decimal('35.00'), active=True, ) quota = Quota.objects.create( event=event, name='Early Bird Quota', size=100, ) quota.items.add(item) return [item] @pytest.fixture @scopes_disabled() def items_multiple_categories(event): """Create items in multiple categories to test grouping and ordering.""" from pretix.base.models import ItemCategory cat_music = ItemCategory.objects.create( event=event, name='Music', position=0 ) cat_food = ItemCategory.objects.create( event=event, name='Food & Drink', position=1 ) item1 = Item.objects.create( event=event, category=cat_music, name='Concert Ticket', default_price=Decimal('75.00'), active=True, ) item2 = Item.objects.create( event=event, category=cat_food, name='Food Pass', default_price=Decimal('25.00'), active=True, ) for item in [item1, item2]: quota = Quota.objects.create( event=event, name=f'{item.name} Quota', size=100, ) quota.items.add(item) return [item1, item2] # ============================================================================ # Test Data Fixtures - Vouchers # ============================================================================ @pytest.fixture @scopes_disabled() def voucher(event, items): """Create a voucher for the event.""" voucher = Voucher.objects.create( event=event, code='TESTCODE2024', max_usages=10, price_mode='none', ) # Clear the vouchers_exist cache so the widget picks it up event.get_cache().delete('vouchers_exist') return voucher @pytest.fixture @scopes_disabled() def voucher_with_item(event, items): """Create a voucher tied to a specific item.""" item = items[0] voucher = Voucher.objects.create( event=event, code='ITEMVOUCHER', max_usages=5, price_mode='percent', value=Decimal('20.00'), # 20% off item=item, ) event.get_cache().delete('vouchers_exist') return voucher # ============================================================================ # Test Data Fixtures - Waiting List # ============================================================================ @pytest.fixture @scopes_disabled() def item_sold_out_with_waitinglist(event): """Create a sold out item with waiting list enabled.""" from pretix.base.models import ItemCategory # Enable waiting list on the event event.settings.set('waiting_list_enabled', True) category = ItemCategory.objects.create( event=event, name='Sold Out', position=20 ) item = Item.objects.create( event=event, category=category, name='Sold Out Concert', default_price=Decimal('80.00'), active=True, allow_waitinglist=True, ) # Create quota with size=0 (sold out) quota = Quota.objects.create( event=event, name='Sold Out Quota', size=0, ) quota.items.add(item) return item # ============================================================================ # Test Data Fixtures - Event Series # ============================================================================ @pytest.fixture @scopes_disabled() def event_series(organizer): """Create an event series with multiple subevents, items, and quotas.""" from pretix.base.models import ItemCategory event = Event.objects.create( organizer=organizer, name='Concert Series', slug='concert-series', date_from=_future_dt(days=30, hour=19), has_subevents=True, currency='EUR', live=True, plugins='pretix.plugins.banktransfer', ) event.set_defaults() event.settings.set('timezone', 'Europe/Berlin') event.settings.set('locale', 'en') event.settings.set('locales', ['en']) category = ItemCategory.objects.create( event=event, name='Tickets', position=0 ) item = Item.objects.create( event=event, category=category, name='Concert Ticket', default_price=Decimal('45.00'), active=True, ) subevents = [] base_date = _future_dt(days=30, hour=19) for i in range(15): se = SubEvent.objects.create( event=event, name=f'Concert Night {i + 1}', date_from=base_date + timedelta(days=i * 2), date_to=base_date + timedelta(days=i * 2, hours=2), active=True, ) subevents.append(se) # Each subevent needs its own quota quota = Quota.objects.create( event=event, name=f'Concert {i + 1} Quota', size=100, subevent=se, ) quota.items.add(item) return event, subevents # ============================================================================ # Widget Helper Fixtures # ============================================================================ @pytest.fixture def widget_page(page): """ Enhanced page fixture with widget-specific helper methods. Provides convenience methods for common widget interactions. """ class WidgetPage: def __init__(self, page: Page): self.page = page def goto( self, live_server_url: str, org_slug: str, event_slug: str, wait=True, **widget_attrs ): """ Navigate to a test page with widget embedded and wait for it to load. Uses a Django view that serves an HTML page with the pretix widget embedded, simulating how it would be used on a customer's website. Extra keyword arguments are passed as query params to the view, which converts them to widget attributes. For boolean attributes (like disable-vouchers), pass an empty string as value. Set wait=False to skip waiting for the widget to load (useful for tests that need to observe loading/error states). """ # Navigate to the test view URL test_url = f"{live_server_url}/widget-test/{org_slug}/{event_slug}/" if widget_attrs: from urllib.parse import urlencode test_url += '?' + urlencode(widget_attrs) self.page.goto(test_url) if wait: self.wait_for_widget_load() return self def wait_for_widget_load(self): """Wait for widget to finish loading.""" self.page.wait_for_selector('.pretix-widget', timeout=15000) # Wait for loading spinner to be hidden (widget has rendered content) self.page.locator('.pretix-widget-loading').wait_for(state='hidden', timeout=15000) return self def wait_for_loading_indicator(self, timeout=15000): """Wait for the loading indicator to appear and then disappear (display: none).""" loading = self.page.locator('.pretix-widget-loading') loading.wait_for(state='visible', timeout=timeout) loading.wait_for(state='hidden', timeout=timeout) return self def select_item_quantity(self, item_name: str, quantity: int): """Select quantity for an item by name.""" # Find the item row item_row = self.page.locator(f'.pretix-widget-item:has-text("{item_name}")') # Find number input within that row number_input = item_row.locator('input[type="number"]').first number_input.wait_for(state='visible', timeout=5000) if number_input.count() > 0: number_input.fill(str(quantity)) number_input.dispatch_event('change') else: # Maybe it's a checkbox (order_max=1) checkbox = item_row.locator('input[type="checkbox"]').first if quantity > 0: checkbox.check() return self def select_variation_quantity(self, item_name: str, variation_name: str, quantity: int): """Select quantity for a specific variation.""" # Find item item = self.page.locator(f'.pretix-widget-item:has-text("{item_name}")') # Find variation within item using exact text match to avoid # "Large" matching "X-Large" variation = item.locator( f'.pretix-widget-variation:has(strong:text-is("{variation_name}"))' ) # Find input input_field = variation.locator('input[type="number"]').first input_field.fill(str(quantity)) input_field.dispatch_event('change') return self def click_buy_button(self): """Click the buy/register button.""" buy_button = self.page.locator(""" .pretix-widget-action button:has-text("Buy"), .pretix-widget-action button:has-text("Register") """) buy_button.first.click() return self def wait_for_iframe_checkout(self): """Wait for checkout iframe to appear.""" self.page.wait_for_selector('.pretix-widget-frame-shown', timeout=15000) # Wait for iframe to load self.page.wait_for_function( """() => { const iframe = document.querySelector('iframe[name^="pretix-widget-"]'); return iframe && iframe.src !== 'about:blank'; }""", timeout=15000 ) iframe = self.page.frame_locator('iframe[name^="pretix-widget-"]') return iframe def close_iframe(self): """Close the checkout iframe and wait for the widget to reload. The widget triggers a reload() when the iframe is closed (without incrementing the loading counter), so we wait for the XHR response to complete before returning. """ close_btn = self.page.locator('.pretix-widget-frame-close button') # Wait for the reload XHR that fires when the iframe closes with self.page.expect_response( lambda r: 'widget/product_list' in r.url, timeout=15000 ): close_btn.click() self.page.locator('.pretix-widget-frame-shown').wait_for( state='detached', timeout=5000 ) return self def wait_for_view(self, selector: str, timeout=15000): """Wait for a specific element to appear after a view switch.""" self.page.locator(selector).first.wait_for(state='visible', timeout=timeout) return self def expand_variations(self, item_name: str): """Click the 'Show variants' button for an item.""" item = self.page.locator(f'.pretix-widget-item:has-text("{item_name}")') toggle_btn = item.locator('button:has-text("Show variants"), button:has-text("variants")') toggle_btn.click() return self def goto_button_test_page( self, live_server_url: str, org_slug: str, event_slug: str, **query_params ): """Navigate to a test page with pretix-button embedded.""" from urllib.parse import urlencode test_url = f"{live_server_url}/button-test/{org_slug}/{event_slug}/" if query_params: test_url += '?' + urlencode(query_params) self.page.goto(test_url) return self return WidgetPage(page) # ============================================================================ # Test Data Fixtures - Availability States # ============================================================================ @pytest.fixture @scopes_disabled() def item_require_voucher(event): """Create an item that requires a voucher to purchase.""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Voucher Only', position=30 ) item = Item.objects.create( event=event, category=category, name='Exclusive Pass', default_price=Decimal('200.00'), active=True, require_voucher=True, ) quota = Quota.objects.create( event=event, name='Exclusive Quota', size=50, ) quota.items.add(item) return item @pytest.fixture @scopes_disabled() def item_low_stock(event): """Create an item with low stock (quota_left visible).""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Limited', position=31 ) item = Item.objects.create( event=event, category=category, name='Last Chance Ticket', default_price=Decimal('65.00'), active=True, ) quota = Quota.objects.create( event=event, name='Limited Quota', size=3, ) quota.items.add(item) # Enable "show quota left" on the event event.settings.set('show_quota_left', True) return item @pytest.fixture @scopes_disabled() def item_not_yet_available(event): """Create an item that is not yet available (future available_from).""" from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Coming Soon', position=32 ) item = Item.objects.create( event=event, category=category, name='Future Ticket', default_price=Decimal('45.00'), active=True, available_from=_future_dt(days=365), available_from_mode='info', # Show as "not yet available" instead of hiding ) quota = Quota.objects.create( event=event, name='Future Quota', size=100, ) quota.items.add(item) return item # ============================================================================ # Test Data Fixtures - Items with Pictures # ============================================================================ @pytest.fixture @scopes_disabled() def item_with_picture(event): """Create an item with a product picture.""" import io from django.core.files.uploadedfile import SimpleUploadedFile from PIL import Image as PILImage from pretix.base.models import ItemCategory category = ItemCategory.objects.create( event=event, name='Gallery Items', position=40 ) # Create a small test image (100x100 red square) img = PILImage.new('RGB', (100, 100), color='red') buf = io.BytesIO() img.save(buf, format='PNG') buf.seek(0) picture_file = SimpleUploadedFile( name='test_product.png', content=buf.read(), content_type='image/png' ) item = Item.objects.create( event=event, category=category, name='Art Print', default_price=Decimal('35.00'), description='Limited edition art print', active=True, picture=picture_file, ) quota = Quota.objects.create( event=event, name='Art Print Quota', size=50, ) quota.items.add(item) return item # ============================================================================ # Cross-Browser Testing # ============================================================================ @pytest.fixture(params=['chromium']) # Add 'firefox', 'webkit' when ready def cross_browser_page(request, playwright): """ Test across multiple browsers. Usage: def test_widget_works_everywhere(cross_browser_page): page = cross_browser_page page.goto("...") """ browser_type = getattr(playwright, request.param) browser = browser_type.launch() context = browser.new_context() page = context.new_page() yield page page.close() context.close() browser.close() @pytest.fixture(scope='session', autouse=True) def _register_widget_test_view(): """ Register a test view that serves an HTML page with widget embedded. This allows E2E tests to navigate to a real URL instead of using set_content, which causes CORS issues. """ from django.http import HttpResponse from django.urls import path from django.views import View from pretix.multidomain import maindomain_urlconf as urls class WidgetTestView(View): """Serve HTML page with widget embedded for E2E testing.""" # Widget attributes that can be passed as query params WIDGET_ATTRS = [ 'items', 'categories', 'voucher', 'disable-vouchers', 'disable-iframe', 'subevent', 'list-type', 'display-event-info', 'skip-ssl-check', ] def get(self, request, organizer, event): base_url = f"{request.scheme}://{request.get_host()}" event_url = f"{base_url}/{organizer}/{event}/" widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css" if os.environ.get("PRETIX_WIDGET_VITE_DEV"): script_tag = f'' else: widget_js = f"{base_url}/widget/v2.en.js" script_tag = f'' # Build extra attributes from query params extra_attrs = '' for attr in self.WIDGET_ATTRS: val = request.GET.get(attr) if val is not None: if val == '': # Boolean attribute (e.g., disable-vouchers) extra_attrs += f' {attr}' else: extra_attrs += f' {attr}="{val}"' # Always add skip-ssl-check so iframe checkout works on HTTP if 'skip-ssl-check' not in extra_attrs: extra_attrs += ' skip-ssl-check' html = f""" Widget Test {script_tag} """ resp = HttpResponse(html, content_type='text/html') resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'" return resp class ButtonTestView(View): """Serve HTML page with pretix-button element for E2E testing.""" def get(self, request, organizer, event): base_url = f"{request.scheme}://{request.get_host()}" event_url = f"{base_url}/{organizer}/{event}/" widget_css = f"{base_url}/{organizer}/{event}/widget/v2.css" if os.environ.get("PRETIX_WIDGET_VITE_DEV"): script_tag = f'' else: widget_js = f"{base_url}/widget/v2.en.js" script_tag = f'' # Build extra attributes from query params extra_attrs = '' for attr in ['items', 'voucher', 'subevent', 'disable-iframe']: val = request.GET.get(attr) if val is not None: if val == '': extra_attrs += f' {attr}' else: extra_attrs += f' {attr}="{val}"' button_text = request.GET.get('button-text', 'Buy tickets!') html = f""" Button Test {button_text} {script_tag} """ resp = HttpResponse(html, content_type='text/html') resp['Content-Security-Policy'] = "script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'" return resp # Add URL patterns test_pattern = path( 'widget-test///', WidgetTestView.as_view() ) button_pattern = path( 'button-test///', ButtonTestView.as_view() ) # Insert at beginning of URL patterns if hasattr(urls, 'urlpatterns'): urls.urlpatterns.insert(0, test_pattern) urls.urlpatterns.insert(0, button_pattern)