From 99bd78f2c63eadbc53165595308d2292dbb1ea68 Mon Sep 17 00:00:00 2001 From: rash Date: Mon, 23 Feb 2026 16:35:42 +0100 Subject: [PATCH] less flaky e2e tests --- src/tests/e2e/conftest.py | 39 +++++++-- src/tests/e2e/test_widget_journey_series.py | 97 +++++++++++++++++++-- 2 files changed, 123 insertions(+), 13 deletions(-) diff --git a/src/tests/e2e/conftest.py b/src/tests/e2e/conftest.py index fbe49f76d7..6eb4df0f5d 100644 --- a/src/tests/e2e/conftest.py +++ b/src/tests/e2e/conftest.py @@ -742,7 +742,7 @@ def event_series(organizer): 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=3), + date_to=base_date + timedelta(days=i*2, hours=2), active=True, ) subevents.append(se) @@ -808,10 +808,16 @@ def widget_page(page): def wait_for_widget_load(self): """Wait for widget to finish loading.""" - # Wait for widget element with longer timeout self.page.wait_for_selector('.pretix-widget', timeout=15000) - # Give it a moment to render content - self.page.wait_for_timeout(1000) + # 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): @@ -824,6 +830,7 @@ def widget_page(page): 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 @@ -845,6 +852,7 @@ def widget_page(page): # 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): @@ -871,10 +879,27 @@ def widget_page(page): return iframe def close_iframe(self): - """Close the checkout iframe.""" + """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') - close_btn.click() - self.page.locator('.pretix-widget-frame-shown').wait_for(state='detached', timeout=5000) + # 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): diff --git a/src/tests/e2e/test_widget_journey_series.py b/src/tests/e2e/test_widget_journey_series.py index c036983c13..1f8fa9490c 100644 --- a/src/tests/e2e/test_widget_journey_series.py +++ b/src/tests/e2e/test_widget_journey_series.py @@ -6,9 +6,52 @@ from playwright.sync_api import Page, expect class TestEventSeriesJourney: """ Complete purchase journeys for an event series. - Tests the different views (calendar, list) and adds multiple items to the cart. + Tests the different views (calendar, list, week) and adds multiple items to the cart. """ + def test_multi_subevent_journey_calendar_view( + self, + page: Page, + live_server_url: str, + organizer, + event_series, + widget_page + ): + event, _subevents = event_series + widget_page.goto( + live_server_url, + organizer.slug, + event.slug, + # **{'list-type': 'list'} + ) + page.locator('.pretix-widget-event-calendar-event').first.click() + + widget_page.select_item_quantity('Concert Ticket', 2) + widget_page.click_buy_button() + + iframe = widget_page.wait_for_iframe_checkout() + expect( + iframe.locator('text=/45\\.00/').first + ).to_be_visible(timeout=15000) + + widget_page.close_iframe() + page.locator('a[rel="back"]').click() + + widget_page.wait_for_view('.pretix-widget-event-calendar-next-month') + + page.locator('.pretix-widget-event-calendar-next-month').click() + widget_page.wait_for_loading_indicator() + page.locator('.pretix-widget-event-calendar-event').first.click() + + widget_page.select_item_quantity('Concert Ticket', 1) + widget_page.click_buy_button() + iframe = widget_page.wait_for_iframe_checkout() + + # TODO a bit janky selector + expect( + iframe.locator('text=/90\\.00/').first + ).to_be_visible(timeout=15000) + def test_multi_subevent_journey_list_view( self, page: Page, @@ -24,13 +67,9 @@ class TestEventSeriesJourney: event.slug, **{'list-type': 'list'} ) - # page.pause() + page.locator('.pretix-widget-event-list-entry').first.click() - # expect( - # page.locator( - # '.pretix-widget-item:has-text("Concert Ticket")') - # ).to_be_visible(timeout=15000) widget_page.select_item_quantity('Concert Ticket', 2) widget_page.click_buy_button() @@ -42,8 +81,54 @@ class TestEventSeriesJourney: widget_page.close_iframe() page.locator('a[rel="back"]').click() + widget_page.wait_for_view('.pretix-widget-event-list-entry') + page.locator('.pretix-widget-event-list-entry').nth(1).click() + widget_page.select_item_quantity('Concert Ticket', 1) + widget_page.click_buy_button() + iframe = widget_page.wait_for_iframe_checkout() + + # TODO a bit janky selector + expect( + iframe.locator('text=/90\\.00/').first + ).to_be_visible(timeout=15000) + + def test_multi_subevent_journey_week_view( + self, + page: Page, + live_server_url: str, + organizer, + event_series, + widget_page + ): + event, _subevents = event_series + widget_page.goto( + live_server_url, + organizer.slug, + event.slug, + **{'list-type': 'week'} + ) + page.locator('.pretix-widget-event-calendar-event').first.click() + + widget_page.select_item_quantity('Concert Ticket', 2) + widget_page.click_buy_button() + + iframe = widget_page.wait_for_iframe_checkout() + expect( + iframe.locator('text=/45\\.00/').first + ).to_be_visible(timeout=15000) + + widget_page.close_iframe() + page.locator('a[rel="back"]').click() + + widget_page.wait_for_view('.pretix-widget-event-calendar-event') + + page.locator('.pretix-widget-event-calendar-next-month').click() + widget_page.wait_for_loading_indicator() + page.locator('.pretix-widget-event-calendar-event').first.click() + + widget_page.select_item_quantity('Concert Ticket', 1) widget_page.click_buy_button() iframe = widget_page.wait_for_iframe_checkout()