test(e2e): add TC-E2E-003/004/005/007/008/009/010 — delete, pagination, errors, profile, theme
- test_post_deletion.py: user delete own, admin delete any, 403 for other's - test_pagination.py: navigation across pages, boundary on last page - test_errors.py: 404 nonexistent post, 404 for other user's draft - test_post_lifecycle.py: draft-to-publish via edit flow - test_post_ownership.py: user can edit own post - test_profile_and_theme.py: profile page rendering, theme toggle with localStorage - fix(web): remove infinite pagination for USER role (routes.py) - fix(e2e): stabilize all publish() calls with expect_navigation - fix(e2e): add _unique_title() to avoid slug collisions at scale - docs: update FEATURE_POST_LIFECYCLE.md and TEST_MODEL.md coverage
This commit is contained in:
@@ -6,6 +6,8 @@ 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
|
||||
@@ -13,6 +15,11 @@ 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,
|
||||
@@ -41,7 +48,7 @@ def test_user_creates_and_publishes_post_visible_to_guest_and_admin(
|
||||
"""
|
||||
generator = PostDataGenerator()
|
||||
post_data = generator.generate_post()
|
||||
title = str(post_data["title"])
|
||||
title = _unique_title(str(post_data["title"]))
|
||||
content = str(post_data["content"])
|
||||
tags = ", ".join(post_data["tags"])
|
||||
|
||||
@@ -51,12 +58,8 @@ def test_user_creates_and_publishes_post_visible_to_guest_and_admin(
|
||||
|
||||
form = PostFormPage(user_page, base_url)
|
||||
form.fill_form(title, content, tags)
|
||||
form.publish()
|
||||
|
||||
user_page.wait_for_url(
|
||||
lambda url: "/web/posts/" in url and "new" not in url,
|
||||
timeout=15000,
|
||||
)
|
||||
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]
|
||||
@@ -117,7 +120,7 @@ def test_post_visibility_policies_across_users(
|
||||
generator = PostDataGenerator()
|
||||
|
||||
draft_data = generator.generate_post()
|
||||
draft_title = str(draft_data["title"])
|
||||
draft_title = _unique_title(str(draft_data["title"]))
|
||||
draft_content = str(draft_data["content"])
|
||||
draft_tags = ", ".join(draft_data["tags"])
|
||||
|
||||
@@ -127,12 +130,8 @@ def test_post_visibility_policies_across_users(
|
||||
|
||||
form = PostFormPage(user_page, base_url)
|
||||
form.fill_form(draft_title, draft_content, draft_tags)
|
||||
form.save_draft()
|
||||
|
||||
user_page.wait_for_url(
|
||||
lambda url: "/web/posts/" in url and "new" not in url,
|
||||
timeout=15000,
|
||||
)
|
||||
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]
|
||||
@@ -143,7 +142,7 @@ def test_post_visibility_policies_across_users(
|
||||
assert not draft_detail.is_published()
|
||||
|
||||
published_data = generator.generate_post()
|
||||
published_title = str(published_data["title"])
|
||||
published_title = _unique_title(str(published_data["title"]))
|
||||
published_content = str(published_data["content"])
|
||||
published_tags = ", ".join(published_data["tags"])
|
||||
|
||||
@@ -152,12 +151,8 @@ def test_post_visibility_policies_across_users(
|
||||
|
||||
form = PostFormPage(user_page, base_url)
|
||||
form.fill_form(published_title, published_content, published_tags)
|
||||
form.publish()
|
||||
|
||||
user_page.wait_for_url(
|
||||
lambda url: "/web/posts/" in url and "new" not in url,
|
||||
timeout=15000,
|
||||
)
|
||||
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]
|
||||
@@ -190,3 +185,68 @@ def test_post_visibility_policies_across_users(
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user