# ABOUTME: Playwright e2e tests for spec scenarios 1-5: Stats progression. # ABOUTME: Tests UI flows for cohort creation, feed, eggs, moves, and backdating. import pytest from playwright.sync_api import Page, expect pytestmark = pytest.mark.e2e class TestSpecBaseline: """Playwright e2e tests for spec scenarios 1-5. These tests verify that the UI flows work correctly for core operations. The exact stat calculations are verified by the service-layer tests; these tests focus on ensuring the UI forms work end-to-end. """ def test_cohort_creation_flow(self, page: Page, live_server): """Test 1a: Create a cohort through the UI.""" # Navigate to cohort creation form page.goto(f"{live_server.url}/actions/cohort") expect(page.locator("body")).to_be_visible() # Fill cohort form page.select_option("#species", "duck") page.select_option("#location_id", label="Strip 1") page.fill("#count", "10") page.select_option("#life_stage", "adult") page.select_option("#sex", "female") page.select_option("#origin", "purchased") page.fill("#notes", "E2E test cohort") # Submit page.click('button[type="submit"]') page.wait_for_load_state("networkidle") # Verify success (should redirect or show success message) # The form should not show an error body_text = page.locator("body").text_content() or "" assert "error" not in body_text.lower() or "View event" in body_text def test_feed_purchase_flow(self, page: Page, live_server): """Test 1b: Purchase feed through the UI.""" # Navigate to feed page page.goto(f"{live_server.url}/feed?tab=purchase") expect(page.locator("body")).to_be_visible() # Click purchase tab to ensure it's active (UIkit switcher) page.click('text="Purchase Feed"') page.wait_for_timeout(500) # Fill purchase form - use purchase-specific ID page.select_option("#purchase_feed_type_code", "layer") page.fill("#bag_size_kg", "20") page.fill("#bags_count", "2") page.fill("#bag_price_euros", "24") # Submit the purchase form page.click('form[action*="feed-purchased"] button[type="submit"]') page.wait_for_load_state("networkidle") # Verify success (check for toast or no error) body_text = page.locator("body").text_content() or "" # Should see either purchase success or recorded message assert "error" not in body_text.lower() or "Purchased" in body_text def test_feed_given_flow(self, page: Page, live_server): """Test 1c: Give feed through the UI.""" # First ensure there's feed purchased page.goto(f"{live_server.url}/feed") page.click('text="Purchase Feed"') page.wait_for_timeout(500) page.select_option("#purchase_feed_type_code", "layer") page.fill("#bag_size_kg", "20") page.fill("#bags_count", "1") page.fill("#bag_price_euros", "24") page.click('form[action*="feed-purchased"] button[type="submit"]') page.wait_for_load_state("networkidle") # Navigate to feed give tab page.goto(f"{live_server.url}/feed") expect(page.locator("body")).to_be_visible() # Click give tab to ensure it's active page.click('text="Give Feed"') page.wait_for_timeout(500) # Fill give form page.select_option("#location_id", label="Strip 1") page.select_option("#feed_type_code", "layer") page.fill("#amount_kg", "6") # Submit page.click('form[action*="feed-given"] button[type="submit"]') page.wait_for_load_state("networkidle") # Verify success body_text = page.locator("body").text_content() or "" assert "error" not in body_text.lower() or "Recorded" in body_text def test_egg_collection_flow(self, page: Page, live_server): """Test 1d: Collect eggs through the UI. Prerequisites: Must have ducks at Strip 1 (from previous tests or seeds). """ # Navigate to eggs page (home) page.goto(live_server.url) expect(page.locator("body")).to_be_visible() # Fill harvest form page.select_option("#location_id", label="Strip 1") page.fill("#quantity", "12") # Submit page.click('form[action*="product-collected"] button[type="submit"]') page.wait_for_load_state("networkidle") # Check result - either success or "No ducks at this location" error body_text = page.locator("body").text_content() or "" success = "Recorded" in body_text or "eggs" in body_text.lower() no_ducks = "No ducks" in body_text assert success or no_ducks, f"Unexpected response: {body_text[:200]}" def test_animal_move_flow(self, page: Page, live_server): """Test 3: Move animals between locations through the UI. Uses the Move Animals page with filter DSL. """ # Navigate to move page page.goto(f"{live_server.url}/move") expect(page.locator("body")).to_be_visible() # Set filter to select ducks at Strip 1 filter_input = page.locator("#filter") filter_input.fill('location:"Strip 1" sex:female') # Wait for selection preview page.keyboard.press("Tab") page.wait_for_load_state("networkidle") # Check if animals were found selection_container = page.locator("#selection-container") if selection_container.count() > 0: selection_text = selection_container.text_content() or "" if "0 animals" in selection_text.lower() or "no animals" in selection_text.lower(): pytest.skip("No animals found matching filter - skipping move test") # Select destination dest_select = page.locator("#to_location_id") if dest_select.count() > 0: page.select_option("#to_location_id", label="Strip 2") # Submit move page.click('button[type="submit"]') page.wait_for_load_state("networkidle") # Verify no error (or success) body_text = page.locator("body").text_content() or "" # Move should succeed or show mismatch (409) assert "error" not in body_text.lower() or "Move" in body_text class TestSpecDatabaseIsolation: """Tests that require fresh database state. These tests use the fresh_server fixture for isolation. """ def test_complete_baseline_flow(self, page: Page, fresh_server): """Test complete baseline flow with fresh database. This test runs through the complete Test #1 scenario: 1. Create 10 adult female ducks at Strip 1 2. Purchase 40kg feed @ EUR 1.20/kg 3. Give 6kg feed 4. Collect 12 eggs """ base_url = fresh_server.url # Step 1: Create cohort page.goto(f"{base_url}/actions/cohort") page.select_option("#species", "duck") page.select_option("#location_id", label="Strip 1") page.fill("#count", "10") page.select_option("#life_stage", "adult") page.select_option("#sex", "female") page.select_option("#origin", "purchased") page.click('button[type="submit"]') page.wait_for_load_state("networkidle") # Verify cohort created (no error) body_text = page.locator("body").text_content() or "" assert "Please select" not in body_text, "Cohort creation failed" # Step 2: Purchase feed (40kg = 2 bags of 20kg @ EUR 24 each) page.goto(f"{base_url}/feed") page.click('text="Purchase Feed"') page.wait_for_timeout(500) page.select_option("#purchase_feed_type_code", "layer") page.fill("#bag_size_kg", "20") page.fill("#bags_count", "2") page.fill("#bag_price_euros", "24") page.click('form[action*="feed-purchased"] button[type="submit"]') page.wait_for_load_state("networkidle") # Step 3: Give 6kg feed page.goto(f"{base_url}/feed") page.click('text="Give Feed"') page.wait_for_timeout(500) page.select_option("#location_id", label="Strip 1") page.select_option("#feed_type_code", "layer") page.fill("#amount_kg", "6") page.click('form[action*="feed-given"] button[type="submit"]') page.wait_for_load_state("networkidle") # Verify feed given (check for toast or success indicator) body_text = page.locator("body").text_content() or "" assert "Recorded" in body_text or "kg" in body_text.lower() # Step 4: Collect 12 eggs page.goto(base_url) page.select_option("#location_id", label="Strip 1") page.fill("#quantity", "12") page.click('form[action*="product-collected"] button[type="submit"]') page.wait_for_load_state("networkidle") # Verify eggs collected body_text = page.locator("body").text_content() or "" assert "Recorded" in body_text or "eggs" in body_text.lower() class TestSpecBackdating: """Tests for backdating functionality (Test #4).""" def test_harvest_form_has_datetime_picker_element(self, page: Page, live_server): """Test that the harvest form includes a datetime picker element. Verifies the datetime picker UI element exists in the DOM. The datetime picker is collapsed by default for simpler UX. Full backdating behavior is tested at the service layer. """ # Navigate to eggs page (harvest tab is default) page.goto(live_server.url) # Click the harvest tab to ensure it's active harvest_tab = page.locator('text="Harvest"') if harvest_tab.count() > 0: harvest_tab.click() page.wait_for_timeout(300) # The harvest form should be visible (use the form containing location) harvest_form = page.locator('form[action*="product-collected"]') expect(harvest_form).to_be_visible() # Look for location dropdown in harvest form location_select = harvest_form.locator("#location_id") expect(location_select).to_be_visible() # Verify datetime picker element exists in the DOM # (it may be collapsed/hidden by default, which is fine) datetime_picker = page.locator("[data-datetime-picker]") assert datetime_picker.count() > 0, "Datetime picker element should exist in form" class TestSpecEventEditing: """Tests for event editing functionality (Test #5). Note: Event editing through the UI may not be fully implemented, so these tests check what's available. """ def test_event_log_accessible(self, page: Page, live_server): """Test that event log page is accessible.""" page.goto(f"{live_server.url}/event-log") expect(page.locator("body")).to_be_visible() # Should show event log content body_text = page.locator("body").text_content() or "" # Event log might be empty or have events assert "Event" in body_text or "No events" in body_text or "log" in body_text.lower()