Add standardized HTMX conventions, interaction patterns, and migration guide for ThrillWiki UX

This commit is contained in:
pacnpal
2025-12-22 16:56:27 -05:00
parent 2e35f8c5d9
commit ae31e889d7
144 changed files with 25792 additions and 4440 deletions

View File

@@ -0,0 +1,372 @@
"""
E2E tests for review submission and moderation flows.
These tests verify the complete user journey for submitting,
editing, and moderating reviews using Playwright for browser automation.
"""
import pytest
from playwright.sync_api import Page, expect
@pytest.mark.e2e
class TestReviewSubmission:
"""E2E tests for review submission flow."""
def test__review_form__displays_fields(self, auth_page: Page, live_server, parks_data):
"""Test review form displays all required fields."""
park = parks_data[0]
auth_page.goto(f"{live_server.url}/parks/{park.slug}/")
# Find and click reviews tab or section
reviews_tab = auth_page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
# Click write review button
write_review = auth_page.locator(
"button:has-text('Write Review'), a:has-text('Write Review')"
)
if write_review.count() > 0:
write_review.first.click()
# Verify form fields
expect(auth_page.locator("select[name='rating'], input[name='rating']").first).to_be_visible()
expect(auth_page.locator("input[name='title'], textarea[name='title']").first).to_be_visible()
expect(auth_page.locator("textarea[name='content'], textarea[name='review']").first).to_be_visible()
def test__review_submission__valid_data__creates_review(
self, auth_page: Page, live_server, parks_data
):
"""Test submitting a valid review creates it."""
park = parks_data[0]
auth_page.goto(f"{live_server.url}/parks/{park.slug}/")
# Navigate to reviews
reviews_tab = auth_page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
write_review = auth_page.locator(
"button:has-text('Write Review'), a:has-text('Write Review')"
)
if write_review.count() > 0:
write_review.first.click()
# Fill the form
rating_select = auth_page.locator("select[name='rating']")
if rating_select.count() > 0:
rating_select.select_option("5")
else:
# May be radio buttons or stars
auth_page.locator("input[name='rating'][value='5']").click()
auth_page.locator("input[name='title'], textarea[name='title']").first.fill(
"E2E Test Review Title"
)
auth_page.locator("textarea[name='content'], textarea[name='review']").first.fill(
"This is an E2E test review content."
)
auth_page.get_by_role("button", name="Submit").click()
# Should show success or redirect
auth_page.wait_for_timeout(500)
def test__review_submission__missing_rating__shows_error(
self, auth_page: Page, live_server, parks_data
):
"""Test submitting review without rating shows error."""
park = parks_data[0]
auth_page.goto(f"{live_server.url}/parks/{park.slug}/")
reviews_tab = auth_page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
write_review = auth_page.locator(
"button:has-text('Write Review'), a:has-text('Write Review')"
)
if write_review.count() > 0:
write_review.first.click()
# Fill only title and content, skip rating
auth_page.locator("input[name='title'], textarea[name='title']").first.fill(
"Missing Rating Review"
)
auth_page.locator("textarea[name='content'], textarea[name='review']").first.fill(
"Review without rating"
)
auth_page.get_by_role("button", name="Submit").click()
# Should show validation error
error = auth_page.locator(".error, .errorlist, [role='alert']")
expect(error.first).to_be_visible()
@pytest.mark.e2e
class TestReviewDisplay:
"""E2E tests for review display."""
def test__reviews_list__displays_reviews(self, page: Page, live_server, parks_data):
"""Test reviews list displays existing reviews."""
park = parks_data[0]
page.goto(f"{live_server.url}/parks/{park.slug}/")
# Navigate to reviews section
reviews_tab = page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
# Reviews should be displayed
reviews_section = page.locator(
"[data-testid='reviews-list'], .reviews-list, .review-item"
)
if reviews_section.count() > 0:
expect(reviews_section.first).to_be_visible()
def test__review__shows_rating(self, page: Page, live_server, test_review):
"""Test review displays rating."""
# test_review fixture creates a review
page.goto(f"{page.url}") # Stay on current page after fixture
# Rating should be visible (stars, number, etc.)
rating = page.locator(
".rating, .stars, [data-testid='rating']"
)
if rating.count() > 0:
expect(rating.first).to_be_visible()
def test__review__shows_author(self, page: Page, live_server, parks_data):
"""Test review displays author name."""
park = parks_data[0]
page.goto(f"{live_server.url}/parks/{park.slug}/")
reviews_tab = page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
# Author name should be visible in review
author = page.locator(
".review-author, .author, [data-testid='author']"
)
if author.count() > 0:
expect(author.first).to_be_visible()
@pytest.mark.e2e
class TestReviewEditing:
"""E2E tests for review editing."""
def test__own_review__shows_edit_button(self, auth_page: Page, live_server, test_review):
"""Test user's own review shows edit button."""
# Navigate to reviews after creating one
park_url = auth_page.url
# Look for edit button on own review
edit_button = auth_page.locator(
"button:has-text('Edit'), a:has-text('Edit Review')"
)
if edit_button.count() > 0:
expect(edit_button.first).to_be_visible()
def test__edit_review__updates_content(self, auth_page: Page, live_server, test_review):
"""Test editing review updates the content."""
# Find and click edit
edit_button = auth_page.locator(
"button:has-text('Edit'), a:has-text('Edit Review')"
)
if edit_button.count() > 0:
edit_button.first.click()
# Update content
content_field = auth_page.locator(
"textarea[name='content'], textarea[name='review']"
)
content_field.first.fill("Updated review content from E2E test")
auth_page.get_by_role("button", name="Save").click()
# Should show updated content
auth_page.wait_for_timeout(500)
expect(auth_page.get_by_text("Updated review content")).to_be_visible()
@pytest.mark.e2e
class TestReviewModeration:
"""E2E tests for review moderation."""
def test__moderator__sees_moderation_actions(
self, mod_page: Page, live_server, parks_data
):
"""Test moderator sees moderation actions on reviews."""
park = parks_data[0]
mod_page.goto(f"{live_server.url}/parks/{park.slug}/")
reviews_tab = mod_page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
# Moderator should see moderation buttons
mod_actions = mod_page.locator(
"button:has-text('Remove'), button:has-text('Flag'), [data-testid='mod-action']"
)
if mod_actions.count() > 0:
expect(mod_actions.first).to_be_visible()
def test__moderator__can_remove_review(self, mod_page: Page, live_server, parks_data):
"""Test moderator can remove a review."""
park = parks_data[0]
mod_page.goto(f"{live_server.url}/parks/{park.slug}/")
reviews_tab = mod_page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
remove_button = mod_page.locator("button:has-text('Remove')")
if remove_button.count() > 0:
remove_button.first.click()
# Confirm if dialog appears
confirm = mod_page.locator("button:has-text('Confirm')")
if confirm.count() > 0:
confirm.click()
mod_page.wait_for_timeout(500)
@pytest.mark.e2e
class TestReviewVoting:
"""E2E tests for review voting (helpful/not helpful)."""
def test__review__shows_vote_buttons(self, page: Page, live_server, parks_data):
"""Test reviews show vote buttons."""
park = parks_data[0]
page.goto(f"{live_server.url}/parks/{park.slug}/")
reviews_tab = page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
# Look for helpful/upvote buttons
vote_buttons = page.locator(
"button:has-text('Helpful'), button[aria-label*='helpful'], .vote-button"
)
if vote_buttons.count() > 0:
expect(vote_buttons.first).to_be_visible()
def test__vote__authenticated__registers_vote(
self, auth_page: Page, live_server, parks_data
):
"""Test authenticated user can vote on review."""
park = parks_data[0]
auth_page.goto(f"{live_server.url}/parks/{park.slug}/")
reviews_tab = auth_page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
helpful_button = auth_page.locator(
"button:has-text('Helpful'), button[aria-label*='helpful']"
)
if helpful_button.count() > 0:
helpful_button.first.click()
# Button should show voted state
auth_page.wait_for_timeout(500)
@pytest.mark.e2e
class TestRideReviews:
"""E2E tests for ride-specific reviews."""
def test__ride_page__shows_reviews(self, page: Page, live_server, rides_data):
"""Test ride page shows reviews section."""
ride = rides_data[0]
page.goto(f"{live_server.url}/rides/{ride.slug}/")
# Reviews section should be present
reviews_section = page.locator(
"[data-testid='reviews'], #reviews, .reviews-section"
)
if reviews_section.count() > 0:
expect(reviews_section.first).to_be_visible()
def test__ride_review__includes_ride_experience_fields(
self, auth_page: Page, live_server, rides_data
):
"""Test ride review form includes experience fields."""
ride = rides_data[0]
auth_page.goto(f"{live_server.url}/rides/{ride.slug}/")
write_review = auth_page.locator(
"button:has-text('Write Review'), a:has-text('Write Review')"
)
if write_review.count() > 0:
write_review.first.click()
# Ride-specific fields
intensity_field = auth_page.locator(
"select[name='intensity'], input[name='intensity']"
)
wait_time_field = auth_page.locator(
"input[name='wait_time'], select[name='wait_time']"
)
# At least one experience field should be present
if intensity_field.count() > 0:
expect(intensity_field.first).to_be_visible()
@pytest.mark.e2e
class TestReviewFiltering:
"""E2E tests for review filtering and sorting."""
def test__reviews__sort_by_date(self, page: Page, live_server, parks_data):
"""Test reviews can be sorted by date."""
park = parks_data[0]
page.goto(f"{live_server.url}/parks/{park.slug}/")
reviews_tab = page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
sort_select = page.locator(
"select[name='sort'], [data-testid='sort-reviews']"
)
if sort_select.count() > 0:
sort_select.first.select_option("date")
page.wait_for_timeout(500)
def test__reviews__filter_by_rating(self, page: Page, live_server, parks_data):
"""Test reviews can be filtered by rating."""
park = parks_data[0]
page.goto(f"{live_server.url}/parks/{park.slug}/")
reviews_tab = page.get_by_role("tab", name="Reviews")
if reviews_tab.count() > 0:
reviews_tab.click()
rating_filter = page.locator(
"select[name='rating'], [data-testid='rating-filter']"
)
if rating_filter.count() > 0:
rating_filter.first.select_option("5")
page.wait_for_timeout(500)