"""End-to-end tests for blog post lifecycle. Tests the complete flow from post creation through publishing and visibility verification across different user roles. """ from __future__ import annotations import uuid import pytest from playwright.sync_api import Page from pytfm.generators import PostDataGenerator from tests.e2e.pages import HomePage, PostDetailPage, PostFormPage def _unique_title(base: str) -> str: """Append a short UUID to a title to avoid slug collisions.""" return f"{base} {uuid.uuid4().hex[:8]}" @pytest.mark.e2e def test_user_creates_and_publishes_post_visible_to_guest_and_admin( user_page: Page, guest_page: Page, admin_page: Page, base_url: str, ) -> None: """Test positive scenario: user creates post, publishes it, and verifies visibility. Steps: 1. Generate unique post data. 2. Authenticated user opens home, clicks "Write a Post". 3. Fills form and publishes the post. 4. Verifies redirect to detail page with "Published" status. 5. Verifies post appears on home page for the user. 6. Verifies post is visible to guest (unauthenticated). 7. Verifies post detail is accessible to guest. 8. Verifies post is visible to admin. 9. Verifies post detail is accessible to admin. Args: user_page: Playwright page authenticated as regular user. guest_page: Unauthenticated Playwright page. admin_page: Playwright page authenticated as admin. base_url: Application base URL. """ generator = PostDataGenerator() post_data = generator.generate_post() title = _unique_title(str(post_data["title"])) content = str(post_data["content"]) tags = ", ".join(post_data["tags"]) home = HomePage(user_page, base_url) home.open() home.create_post() form = PostFormPage(user_page, base_url) form.fill_form(title, content, tags) with user_page.expect_navigation(wait_until="networkidle"): form.publish() current_url = user_page.url assert "new" not in current_url, f"Still on form page: {current_url}" slug = current_url.rstrip("/").split("/")[-1] user_page.wait_for_selector('[data-testid="post-detail-title"]') detail = PostDetailPage(user_page, base_url, slug) assert detail.get_title() == title assert detail.is_published() home.open() assert home.has_post_with_title(title) guest_home = HomePage(guest_page, base_url) guest_home.open() assert guest_home.has_post_with_title(title) guest_detail = PostDetailPage(guest_page, base_url, slug) guest_detail.open() assert guest_detail.get_title() == title assert guest_detail.is_published() admin_home = HomePage(admin_page, base_url) admin_home.open() assert admin_home.has_post_with_title(title) admin_detail = PostDetailPage(admin_page, base_url, slug) admin_detail.open() assert admin_detail.get_title() == title assert admin_detail.is_published() @pytest.mark.e2e def test_post_visibility_policies_across_users( user_page: Page, user2_page: Page, guest_page: Page, admin_page: Page, base_url: str, ) -> None: """Test visibility policies: drafts vs published posts across roles. Steps: 1. User creates a draft post. 2. User creates and publishes another post. 3. Verify user sees both posts on the home page. 4. Verify user2 sees only the published post. 5. Verify guest sees only the published post. 6. Verify admin sees both posts. 7. Verify user2 receives 404 when accessing the draft directly. Args: user_page: Playwright page authenticated as the first regular user. user2_page: Playwright page authenticated as the second regular user. guest_page: Unauthenticated Playwright page. admin_page: Playwright page authenticated as admin. base_url: Application base URL. """ generator = PostDataGenerator() draft_data = generator.generate_post() draft_title = _unique_title(str(draft_data["title"])) draft_content = str(draft_data["content"]) draft_tags = ", ".join(draft_data["tags"]) home = HomePage(user_page, base_url) home.open() home.create_post() form = PostFormPage(user_page, base_url) form.fill_form(draft_title, draft_content, draft_tags) with user_page.expect_navigation(wait_until="networkidle"): form.save_draft() draft_url = user_page.url assert "new" not in draft_url, f"Still on form page: {draft_url}" draft_slug = draft_url.rstrip("/").split("/")[-1] user_page.wait_for_selector('[data-testid="post-detail-title"]') draft_detail = PostDetailPage(user_page, base_url, draft_slug) assert draft_detail.get_title() == draft_title assert not draft_detail.is_published() published_data = generator.generate_post() published_title = _unique_title(str(published_data["title"])) published_content = str(published_data["content"]) published_tags = ", ".join(published_data["tags"]) home.open() home.create_post() form = PostFormPage(user_page, base_url) form.fill_form(published_title, published_content, published_tags) with user_page.expect_navigation(wait_until="networkidle"): form.publish() published_url = user_page.url assert "new" not in published_url, f"Still on form page: {published_url}" published_slug = published_url.rstrip("/").split("/")[-1] user_page.wait_for_selector('[data-testid="post-detail-title"]') published_detail = PostDetailPage(user_page, base_url, published_slug) assert published_detail.get_title() == published_title assert published_detail.is_published() home.open() assert home.has_post_with_title(draft_title) assert home.has_post_with_title(published_title) user2_home = HomePage(user2_page, base_url) user2_home.open() assert user2_home.has_no_post_with_title(draft_title) assert user2_home.has_post_with_title(published_title) guest_home = HomePage(guest_page, base_url) guest_home.open() assert guest_home.has_no_post_with_title(draft_title) assert guest_home.has_post_with_title(published_title) admin_home = HomePage(admin_page, base_url) admin_home.open() assert admin_home.has_post_with_title(draft_title) assert admin_home.has_post_with_title(published_title) user2_page.goto(f"{base_url}/web/posts/{draft_slug}") user2_page.wait_for_selector('[data-testid="error-code"]', timeout=10000) error_code = user2_page.locator('[data-testid="error-code"]').text_content() assert error_code == "404" @pytest.mark.e2e def test_user_saves_draft_then_publishes_via_edit( user_page: Page, guest_page: Page, base_url: str, ) -> None: """Test draft-to-publish flow through the web UI. Steps: 1. User creates a post and saves it as draft. 2. Verify the post shows Draft status and guest cannot see it. 3. User opens the edit form and clicks Update Post (publish). 4. Verify the post shows Published status and guest can now see it. Args: user_page: Playwright page authenticated as regular user. guest_page: Unauthenticated Playwright page. base_url: Application base URL. """ generator = PostDataGenerator() post_data = generator.generate_post() title = _unique_title(str(post_data["title"])) content = str(post_data["content"]) tags = ", ".join(post_data["tags"]) home = HomePage(user_page, base_url) home.open() home.create_post() form = PostFormPage(user_page, base_url) form.fill_form(title, content, tags) with user_page.expect_navigation(wait_until="networkidle"): form.save_draft() current_url = user_page.url assert "new" not in current_url, f"Still on form page: {current_url}" slug = current_url.rstrip("/").split("/")[-1] user_page.wait_for_selector('[data-testid="post-detail-title"]') detail = PostDetailPage(user_page, base_url, slug) assert detail.get_title() == title assert detail.get_status() == "Draft" guest_home = HomePage(guest_page, base_url) guest_home.open() assert guest_home.has_no_post_with_title(title) detail.edit() user_page.wait_for_url( lambda url: f"/web/posts/{slug}/edit" in url, timeout=15000, ) edit_form = PostFormPage(user_page, base_url) with user_page.expect_navigation(wait_until="networkidle"): edit_form.publish() user_page.wait_for_selector('[data-testid="post-detail-title"]') updated_detail = PostDetailPage(user_page, base_url, slug) assert updated_detail.get_title() == title assert updated_detail.get_status() == "Published" guest_home.open() assert guest_home.has_post_with_title(title)