import re
import time
from uuid import uuid4
from datetime import datetime, timedelta, timezone
from playwright.sync_api import expect
from tests.conftest import BASE_URL, assert_share_copies
from devplacepy.database import get_table
from devplacepy.utils import make_combined_slug
def _seed_gists(count):
owner = str(uuid4())
get_table("users").insert({
"uid": owner, "username": f"pag_{owner[:8]}", "email": f"{owner[:8]}@test.devplace",
"password_hash": "x", "role": "Member", "is_active": True,
"created_at": datetime.now(timezone.utc).isoformat(),
})
base = datetime(2026, 1, 1, tzinfo=timezone.utc)
gists = get_table("gists")
for i in range(count):
uid = str(uuid4())
title = f"Pag Gist {i}"
gists.insert({
"uid": uid, "user_uid": owner, "slug": make_combined_slug(title, uid),
"title": title, "language": "python", "description": "paginated", "stars": 0,
"created_at": (base - timedelta(seconds=i)).isoformat(),
})
return owner
def _set_cm_value(page, value):
page.evaluate(f'''() => {{
const cm = document.querySelector(".CodeMirror")?.CodeMirror;
if (cm) {{
cm.setValue({repr(value)});
cm.save();
}}
}}''')
def _create_gist(page, title="Test Gist", description="Test description", language="python", source_code="print('hello')"):
page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
page.locator("#create-gist-btn").wait_for(state="visible", timeout=10000)
page.locator("#create-gist-btn").click()
page.wait_for_timeout(800)
page.fill("#gist-title", title)
page.fill("#gist-description", description)
page.select_option("#gist-language", language)
page.wait_for_timeout(300)
_set_cm_value(page, source_code)
page.wait_for_timeout(300)
page.locator("button.btn-primary:has-text('Create Gist')").click()
page.wait_for_timeout(2000)
current = page.url
if "/gists/" in current and current != f"{BASE_URL}/gists":
return
page.wait_for_url("**/gists/*", timeout=15000, wait_until="domcontentloaded")
def test_gist_listing_loads(page, app_server):
page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
assert page.is_visible("h1:has-text('Gists')")
assert page.is_visible("text=Share and discover code snippets")
def test_gist_listing_empty(page, app_server):
page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
assert page.is_visible("text=No gists found")
def test_gist_listing_shows_sidebar(page, app_server):
page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
assert page.is_visible("text=Languages")
assert page.is_visible("text=All")
def test_guest_cannot_create(page, app_server):
page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
assert not page.is_visible("#create-gist-btn")
def test_create_gist(alice, app_server):
page, _ = alice
title = f"Create Test {int(time.time())}"
_create_gist(page, title=title, source_code="print('create test')")
assert page.is_visible(f"text={title}")
assert page.is_visible("text=Python")
assert page.is_visible("text=print('create test')")
def test_gist_detail_shows_all_sections(alice, app_server):
page, _ = alice
title = f"Detail Test {int(time.time())}"
_create_gist(page, title=title, description="See all sections", source_code="def hello(): pass")
assert page.is_visible("text=See all sections")
assert page.is_visible("text=def hello(): pass")
assert page.is_visible("text=Copy")
assert page.is_visible("text=Back to Gists")
def test_gist_detail_back_link(alice, app_server):
page, _ = alice
title = f"Back Link Test {int(time.time())}"
_create_gist(page, title=title, source_code="x = 1")
page.click("text=Back to Gists")
page.wait_for_url(f"{BASE_URL}/gists", timeout=10000, wait_until="domcontentloaded")
assert page.is_visible("h1:has-text('Gists')")
def test_edit_gist(alice, app_server):
page, _ = alice
title = f"Edit Test Original {int(time.time())}"
_create_gist(page, title=title, source_code="x = 1")
page.locator("button:has-text('Edit')").wait_for(state="visible", timeout=5000)
page.click("button:has-text('Edit')")
page.wait_for_timeout(800)
new_title = f"Edit Test Updated {int(time.time())}"
page.fill("#edit-gist-title", new_title)
page.select_option("#edit-gist-language", "javascript")
_set_cm_value(page, "const x = 1;")
page.wait_for_timeout(300)
page.locator("button.btn-primary:has-text('Save Changes')").click()
page.wait_for_url("**/gists/*", timeout=15000, wait_until="domcontentloaded")
assert page.is_visible(f"text={new_title}")
assert page.is_visible("text=JavaScript")
def test_non_owner_cannot_edit_or_delete(bob, alice, app_server):
bob_page, _ = bob
alice_page, alice_user = alice
title = f"Owner Check {int(time.time())}"
_create_gist(alice_page, title=title, source_code="owner_only = True")
slug = alice_page.url.split("/gists/")[-1]
bob_page.goto(f"{BASE_URL}/gists/{slug}", wait_until="domcontentloaded")
assert not bob_page.is_visible("button:has-text('Edit')")
assert not bob_page.is_visible("button:has-text('Delete')")
def test_delete_gist(alice, app_server):
page, _ = alice
title = f"Delete Test {int(time.time())}"
_create_gist(page, title=title, source_code="delete_me = True")
page.locator("button:has-text('Delete')").wait_for(state="visible", timeout=5000)
page.once("dialog", lambda dialog: dialog.accept())
page.click("button:has-text('Delete')")
page.wait_for_url(f"{BASE_URL}/gists", timeout=10000, wait_until="domcontentloaded")
def test_gist_voting(alice, app_server):
page, _ = alice
title = f"Vote Test {int(time.time())}"
_create_gist(page, title=title, source_code="vote_me = True")
star_btn = page.locator("form[action*='/votes/gist/'] button").first
original_text = star_btn.text_content()
original_stars = int(original_text.strip("\u2606 "))
star_btn.click()
page.wait_for_timeout(500)
star_btn = page.locator("form[action*='/votes/gist/'] button").first
new_text = star_btn.text_content()
new_stars = int(new_text.strip("\u2606 "))
assert new_stars == original_stars + 1
def test_gist_detail_has_sourcecode_schema(alice, app_server):
page, _ = alice
_create_gist(page, title=f"Schema Gist {int(time.time())}", source_code="x = 1")
scripts = page.locator('script[type="application/ld+json"]')
text = " ".join(scripts.nth(i).text_content() for i in range(scripts.count()))
assert "SoftwareSourceCode" in text
def test_gist_detail_share_button(alice, app_server):
page, _ = alice
_create_gist(page, title=f"Share Gist {int(time.time())}", source_code="x = 1")
assert_share_copies(page, "/gists/")
def test_gist_code_copy_button(alice, app_server):
from playwright.sync_api import expect
page, _ = alice
_create_gist(page, title=f"Copy Gist {int(time.time())}", source_code="copyme = 42")
btn = page.locator("button[data-copy='gist-code-content']")
btn.click()
expect(btn).to_have_text("Copied!", timeout=3000)
def test_profile_gists_tab(alice, app_server):
page, alice_user = alice
title = f"Profile Tab Test {int(time.time())}"
_create_gist(page, title=title, source_code="profile_tab = True")
page.goto(f"{BASE_URL}/profile/{alice_user['username']}?tab=gists", wait_until="domcontentloaded")
assert page.is_visible(f"text={title}")
def test_language_filter(alice, app_server):
page, _ = alice
title = f"Lang Filter Test {int(time.time())}"
_create_gist(page, title=title, language="go", source_code="package main")
page.goto(f"{BASE_URL}/gists?language=go", wait_until="domcontentloaded")
assert page.is_visible(f"text={title}")
page.goto(f"{BASE_URL}/gists?language=python", wait_until="domcontentloaded")
assert not page.is_visible(f"text={title}")
def test_gist_comments(alice, app_server):
page, _ = alice
title = f"Comment Test {int(time.time())}"
_create_gist(page, title=title, source_code="has_comments = True")
comment_text = f"Nice gist! {int(time.time())}"
comment_input = page.locator("textarea[name='content']").first
comment_input.wait_for(state="visible", timeout=5000)
comment_input.fill(comment_text)
page.locator("button.comment-form-submit").first.click()
page.wait_for_url("**/gists/*", timeout=10000, wait_until="domcontentloaded")
assert page.is_visible(f"text={comment_text}")
def test_gist_listing_shows_created_gist(alice, app_server):
page, _ = alice
title = f"Listing Shows {int(time.time())}"
_create_gist(page, title=title, source_code="show_in_list = True")
page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
assert page.is_visible(f"text={title}")
def test_gist_pagination_first_page(alice):
page, _ = alice
owner = _seed_gists(26)
page.goto(f"{BASE_URL}/gists?user_uid={owner}", wait_until="domcontentloaded")
assert page.locator(".gist-card").count() == 25
assert page.is_visible(".load-more-wrap")
def test_gist_pagination_load_more(alice):
page, _ = alice
owner = _seed_gists(26)
page.goto(f"{BASE_URL}/gists?user_uid={owner}", wait_until="domcontentloaded")
page.click(".load-more-wrap a")
page.wait_for_url(lambda url: "before=" in url, wait_until="domcontentloaded")
assert f"user_uid={owner}" in page.url
assert page.locator(".gist-card").count() == 1
assert not page.is_visible(".load-more-wrap")
def test_gist_no_pagination_below_page_size(alice):
page, _ = alice
owner = _seed_gists(10)
page.goto(f"{BASE_URL}/gists?user_uid={owner}", wait_until="domcontentloaded")
assert page.locator(".gist-card").count() == 10
assert not page.is_visible(".load-more-wrap")
def test_gist_voted_state_persists(alice):
page, _ = alice
_create_gist(page, title="Voted State Gist")
star = "form[action*='/votes/gist/'] button"
page.locator(star).first.click()
page.wait_for_timeout(500)
page.reload(wait_until="domcontentloaded")
expect(page.locator(star).first).to_have_class(re.compile(r"\bvoted\b"))