test(sveta): Extend vote-state tests to assert the non-color cue
Some checks failed
DevPlace CI / test (pull_request) Failing after 1h23m46s

Outcome: done
Changed: tests/conftest.py:293-300, tests/e2e/post.py:5,74-157, tests/e2e/posts/index.py:17,396-418, tests/e2e/notifications/index.py:313,799, tests/e2e/leaderboard.py:42
Verified by: python -m pytest tests/e2e/post.py tests/e2e/posts/index.py tests/e2e/notifications/index.py tests/e2e/leaderboard.py -> 68 passed (verify() passed); make test full suite -> 3344 passed, 1 skipped, 10 failed (9x tests/api/projects/workspace.py: docker binary absent in this environment, same as prior sibling run; 1x tests/e2e/game/farm.py::test_victim_notification_names_the_raider_and_the_amount, passes standalone and 9/9 in its file, game code has no vote markup)
Findings: New vote_glyph helper (tests/conftest.py:293) reads getComputedStyle(el,'::before').content and normalizes CSS escapes; glyph assertions cover post upvote (+ -> U+2295), post downvote (U+2212 -> U+2296), and comment upvote (+ -> U+2295), each also asserting unvoted glyph absence and toggle-off restoration to the unvoted glyph. New test fails against pre-change code: on 72e11db, test_post_upvote_voted_state_persists fails with "assert 'none' == '+'" (no ::before rule existed; buttons held literal text). Fixed all three has_text="+" vote locators (notifications/index.py:313,799, leaderboard.py:42) to class-based button.post-action-btn.vote-up; the three tests now pass. test_comment_voted_state_persists was made deterministic by wrapping the click in expect_response (pre-existing reload race); it now passes reliably

Typosaurus-Run: c36faeceb55d45b6bf38a45eec4f47ab
Typosaurus-Node: 9b6a5ec294a2472e800fbfeed775b27b
Typosaurus-Agent: @sveta
Refs: #162
This commit is contained in:
typosaurus 2026-08-15 03:01:50 +00:00
parent 95c4233802
commit c14bf98414
5 changed files with 75 additions and 30 deletions

View File

@ -290,6 +290,14 @@ def assert_share_copies(page, expected_fragment):
) )
def vote_glyph(locator):
value = locator.evaluate("el => getComputedStyle(el, '::before').content")
value = value.strip().strip('"').strip("'")
if value.startswith("\\") and len(value) == 5 and value[1:].isdigit():
value = chr(int(value[1:], 16))
return value
def assert_no_horizontal_overflow(page, layout): def assert_no_horizontal_overflow(page, layout):
report = page.evaluate( report = page.evaluate(
"""(selector) => { """(selector) => {

View File

@ -39,7 +39,7 @@ def test_leaderboard_ranks_after_upvote(app_server, browser, seeded_db):
pa.goto(post_url, wait_until="domcontentloaded") pa.goto(post_url, wait_until="domcontentloaded")
pa.wait_for_timeout(1000) pa.wait_for_timeout(1000)
vote_btn = pa.locator("button.post-action-btn").filter(has_text="+").first vote_btn = pa.locator("button.post-action-btn.vote-up").first
vote_btn.wait_for(state="visible", timeout=10000) vote_btn.wait_for(state="visible", timeout=10000)
vote_btn.click() vote_btn.click()
pa.wait_for_timeout(1500) pa.wait_for_timeout(1500)

View File

@ -310,7 +310,7 @@ def test_vote_notification_on_post(app_server, browser, seeded_db):
pa.goto(post_url, wait_until="domcontentloaded") pa.goto(post_url, wait_until="domcontentloaded")
pa.wait_for_timeout(1000) pa.wait_for_timeout(1000)
vote_btn = pa.locator("button.post-action-btn").filter(has_text="+").first vote_btn = pa.locator("button.post-action-btn.vote-up").first
vote_btn.wait_for(state="visible", timeout=10000) vote_btn.wait_for(state="visible", timeout=10000)
vote_btn.click() vote_btn.click()
pa.wait_for_timeout(1500) pa.wait_for_timeout(1500)
@ -796,7 +796,7 @@ def test_vote_notification_click_opens_target(app_server, browser, seeded_db):
pa.goto(f"{BASE_URL}{post_path}", wait_until="domcontentloaded") pa.goto(f"{BASE_URL}{post_path}", wait_until="domcontentloaded")
pa.wait_for_timeout(1000) pa.wait_for_timeout(1000)
vote_btn = pa.locator("button.post-action-btn").filter(has_text="+").first vote_btn = pa.locator("button.post-action-btn.vote-up").first
vote_btn.wait_for(state="visible", timeout=10000) vote_btn.wait_for(state="visible", timeout=10000)
vote_btn.click() vote_btn.click()
pa.wait_for_timeout(1500) pa.wait_for_timeout(1500)

View File

@ -2,7 +2,7 @@
import re import re
from playwright.sync_api import expect from playwright.sync_api import expect
from tests.conftest import BASE_URL, assert_share_copies from tests.conftest import BASE_URL, assert_share_copies, vote_glyph
def create_post(page, topic="random", content="Test post content", title=None): def create_post(page, topic="random", content="Test post content", title=None):
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.locator(".feed-fab").first.wait_for(state="visible", timeout=10000) page.locator(".feed-fab").first.wait_for(state="visible", timeout=10000)
@ -74,35 +74,57 @@ def test_post_vote_increment(alice):
def test_post_upvote_voted_state_persists(alice): def test_post_upvote_voted_state_persists(alice):
page, _ = alice page, _ = alice
create_post(page, "showcase", "Upvote persistence test") create_post(page, "showcase", "Upvote persistence test")
vote_up = page.locator(".post-action-btn.vote-up").first
assert vote_glyph(vote_up) == "+"
with page.expect_response( with page.expect_response(
lambda r: "/votes/" in r.url and r.request.method == "POST" lambda r: "/votes/" in r.url and r.request.method == "POST"
): ):
page.locator(".post-action-btn.vote-up").first.click() vote_up.click()
expect(page.locator(".post-vote-count").first).to_have_text("1") expect(page.locator(".post-vote-count").first).to_have_text("1")
expect(vote_up).to_have_class(re.compile(r"\bvoted\b"))
assert vote_glyph(vote_up) == "\u2295"
page.reload(wait_until="domcontentloaded") page.reload(wait_until="domcontentloaded")
expect(page.locator(".post-action-btn.vote-up").first).to_have_class( vote_up = page.locator(".post-action-btn.vote-up").first
re.compile(r"\bvoted\b") expect(vote_up).to_have_class(re.compile(r"\bvoted\b"))
) assert vote_glyph(vote_up) == "\u2295"
assert "voted" not in ( vote_down = page.locator(".post-action-btn.vote-down").first
page.locator(".post-action-btn.vote-down").first.get_attribute("class") or "" assert "voted" not in (vote_down.get_attribute("class") or "")
) assert vote_glyph(vote_down) == "\u2212"
with page.expect_response(
lambda r: "/votes/" in r.url and r.request.method == "POST"
):
vote_up.click()
expect(page.locator(".post-vote-count").first).to_have_text("0")
expect(vote_up).not_to_have_class(re.compile(r"\bvoted\b"))
assert vote_glyph(vote_up) == "+"
def test_post_downvote_voted_state_persists(alice): def test_post_downvote_voted_state_persists(alice):
page, _ = alice page, _ = alice
create_post(page, "showcase", "Downvote persistence test") create_post(page, "showcase", "Downvote persistence test")
vote_down = page.locator(".post-action-btn.vote-down").first
assert vote_glyph(vote_down) == "\u2212"
with page.expect_response( with page.expect_response(
lambda r: "/votes/" in r.url and r.request.method == "POST" lambda r: "/votes/" in r.url and r.request.method == "POST"
): ):
page.locator(".post-action-btn.vote-down").first.click() vote_down.click()
expect(page.locator(".post-vote-count").first).to_have_text("-1") expect(page.locator(".post-vote-count").first).to_have_text("-1")
expect(vote_down).to_have_class(re.compile(r"\bvoted\b"))
assert vote_glyph(vote_down) == "\u2296"
page.reload(wait_until="domcontentloaded") page.reload(wait_until="domcontentloaded")
expect(page.locator(".post-action-btn.vote-down").first).to_have_class( vote_down = page.locator(".post-action-btn.vote-down").first
re.compile(r"\bvoted\b") expect(vote_down).to_have_class(re.compile(r"\bvoted\b"))
) assert vote_glyph(vote_down) == "\u2296"
assert "voted" not in ( vote_up = page.locator(".post-action-btn.vote-up").first
page.locator(".post-action-btn.vote-up").first.get_attribute("class") or "" assert "voted" not in (vote_up.get_attribute("class") or "")
) assert vote_glyph(vote_up) == "+"
with page.expect_response(
lambda r: "/votes/" in r.url and r.request.method == "POST"
):
vote_down.click()
expect(page.locator(".post-vote-count").first).to_have_text("0")
expect(vote_down).not_to_have_class(re.compile(r"\bvoted\b"))
assert vote_glyph(vote_down) == "\u2212"
def test_comment_voted_state_persists(alice): def test_comment_voted_state_persists(alice):
@ -114,14 +136,25 @@ def test_comment_voted_state_persists(alice):
expect( expect(
page.locator(".comment-text:has-text('Comment whose vote should persist')") page.locator(".comment-text:has-text('Comment whose vote should persist')")
).to_be_visible() ).to_be_visible()
page.locator(".comment-vote-btn").first.click() comment_vote = page.locator(".comment-vote-btn.vote-up").first
expect(page.locator(".comment-vote-btn").first).to_have_class( assert vote_glyph(comment_vote) == "+"
re.compile(r"\bvoted\b") with page.expect_response(
) lambda r: "/votes/" in r.url and r.request.method == "POST"
):
comment_vote.click()
expect(comment_vote).to_have_class(re.compile(r"\bvoted\b"))
assert vote_glyph(comment_vote) == "\u2295"
page.reload(wait_until="domcontentloaded") page.reload(wait_until="domcontentloaded")
expect(page.locator(".comment-vote-btn").first).to_have_class( comment_vote = page.locator(".comment-vote-btn.vote-up").first
re.compile(r"\bvoted\b") expect(comment_vote).to_have_class(re.compile(r"\bvoted\b"))
) assert vote_glyph(comment_vote) == "\u2295"
with page.expect_response(
lambda r: "/votes/" in r.url and r.request.method == "POST"
):
comment_vote.click()
expect(page.locator(".comment-vote-count").first).to_have_text("0")
expect(comment_vote).not_to_have_class(re.compile(r"\bvoted\b"))
assert vote_glyph(comment_vote) == "+"
def test_mention_first_match_highlighted(alice): def test_mention_first_match_highlighted(alice):

View File

@ -14,7 +14,7 @@ def _create_plain_post_engagement_ui(page, content):
page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded") page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded")
from uuid import uuid4 from uuid import uuid4
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from tests.conftest import BASE_URL, assert_share_copies from tests.conftest import BASE_URL, assert_share_copies, vote_glyph
from devplacepy.database import get_table from devplacepy.database import get_table
from devplacepy.utils import make_combined_slug from devplacepy.utils import make_combined_slug
def _seed_posts(count): def _seed_posts(count):
@ -404,15 +404,19 @@ def test_feed_vote_voted_state_persists(alice):
card = page.locator( card = page.locator(
".post-card", has_text="Feed vote persistence test content" ".post-card", has_text="Feed vote persistence test content"
).first ).first
card.locator(".post-action-btn.vote-up").click() vote_up = card.locator(".post-action-btn.vote-up")
assert vote_glyph(vote_up) == "+"
vote_up.click()
expect(card.locator(".post-vote-count")).to_have_text("1") expect(card.locator(".post-vote-count")).to_have_text("1")
expect(vote_up).to_have_class(re.compile(r"\bvoted\b"))
assert vote_glyph(vote_up) == "\u2295"
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
card = page.locator( card = page.locator(
".post-card", has_text="Feed vote persistence test content" ".post-card", has_text="Feed vote persistence test content"
).first ).first
expect(card.locator(".post-action-btn.vote-up")).to_have_class( vote_up = card.locator(".post-action-btn.vote-up")
re.compile(r"\bvoted\b") expect(vote_up).to_have_class(re.compile(r"\bvoted\b"))
) assert vote_glyph(vote_up) == "\u2295"
def test_feed_card_share_button(alice): def test_feed_card_share_button(alice):