feat: implement basic version of the application with core functionality
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import os, sys, tempfile, subprocess, time
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
BASE_URL = "http://127.0.0.1:10501"
|
||||
SCREENSHOT_DIR = Path("/tmp/devplace_test_screenshots")
|
||||
|
||||
|
||||
def save_failure_screenshot(page, test_name):
|
||||
SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
if page and not page.is_closed():
|
||||
path = str(SCREENSHOT_DIR / f"{test_name}.png")
|
||||
page.screenshot(path=path, full_page=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
outcome = yield
|
||||
report = outcome.get_result()
|
||||
if report.when == "call" and report.failed:
|
||||
for name in ("page", "alice", "bob"):
|
||||
if name in item.funcargs:
|
||||
try:
|
||||
obj = item.funcargs[name]
|
||||
if name in ("alice", "bob"):
|
||||
obj = obj[0]
|
||||
save_failure_screenshot(obj, item.nodeid.replace("::", "_").replace("/", "_"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_db_path():
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
|
||||
tmp.close()
|
||||
yield tmp.name
|
||||
try:
|
||||
os.unlink(tmp.name)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def app_server(test_db_path):
|
||||
env = os.environ.copy()
|
||||
env["DEVPLACE_DATABASE_URL"] = f"sqlite:///{test_db_path}"
|
||||
env["SECRET_KEY"] = "test-secret-key"
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, "-m", "uvicorn", "devplacepy.main:app",
|
||||
"--host", "127.0.0.1", "--port", "10501"],
|
||||
cwd=str(PROJECT_ROOT), env=env,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
)
|
||||
deadline = time.time() + 20
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
import urllib.request
|
||||
urllib.request.urlopen(f"{BASE_URL}/", timeout=2)
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
proc.terminate(); proc.wait()
|
||||
raise RuntimeError("Server did not start")
|
||||
yield proc
|
||||
proc.terminate()
|
||||
proc.wait(timeout=10)
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def playwright_instance():
|
||||
from playwright.sync_api import sync_playwright
|
||||
with sync_playwright() as p:
|
||||
yield p
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def browser(playwright_instance):
|
||||
headless = os.environ.get("PLAYWRIGHT_HEADLESS", "1") == "1"
|
||||
b = playwright_instance.chromium.launch(
|
||||
headless=headless, slow_mo=300, args=["--window-size=1400,900"],
|
||||
)
|
||||
yield b
|
||||
b.close()
|
||||
|
||||
@pytest.fixture
|
||||
def browser_context(browser):
|
||||
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
|
||||
yield ctx
|
||||
ctx.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def page(browser_context):
|
||||
p = browser_context.new_page()
|
||||
p.set_default_timeout(10000)
|
||||
yield p
|
||||
p.close()
|
||||
|
||||
def signup_user(page, user):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", user["username"])
|
||||
page.fill("#email", user["email"])
|
||||
page.fill("#password", user["password"])
|
||||
page.fill("#confirm_password", user["password"])
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
|
||||
def login_user(page, user):
|
||||
page.goto(f"{BASE_URL}/auth/login")
|
||||
page.fill("#email", user["email"])
|
||||
page.fill("#password", user["password"])
|
||||
page.click("button:has-text('Sign in')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def seeded_db(app_server):
|
||||
"""Create test users once at session level using requests directly."""
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
users = [
|
||||
("alice_test", "alice@test.devplace", "secret123"),
|
||||
("bob_test", "bob@test.devplace", "secret456"),
|
||||
]
|
||||
for username, email, pw in users:
|
||||
data = urllib.parse.urlencode({
|
||||
"username": username, "email": email,
|
||||
"password": pw, "confirm_password": pw,
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{BASE_URL}/auth/signup",
|
||||
data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
urllib.request.urlopen(req)
|
||||
return {
|
||||
"alice": {"username": "alice_test", "email": "alice@test.devplace", "password": "secret123"},
|
||||
"bob": {"username": "bob_test", "email": "bob@test.devplace", "password": "secret456"},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def alice(page, seeded_db):
|
||||
login_user(page, seeded_db["alice"])
|
||||
return page, seeded_db["alice"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bob(browser, seeded_db):
|
||||
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
|
||||
p = ctx.new_page()
|
||||
p.set_default_timeout(10000)
|
||||
login_user(p, seeded_db["bob"])
|
||||
yield p, seeded_db["bob"]
|
||||
p.close()
|
||||
ctx.close()
|
||||
@@ -0,0 +1,206 @@
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
|
||||
def test_signup_page_loads(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
assert page.is_visible("h2:has-text('Join the Community')")
|
||||
assert page.is_visible("#username")
|
||||
assert page.is_visible("#email")
|
||||
assert page.is_visible("#password")
|
||||
assert page.is_visible("#confirm_password")
|
||||
|
||||
|
||||
def test_signup_success(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "fresh_user")
|
||||
page.fill("#email", "fresh@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
assert page.is_visible("text=fresh_user")
|
||||
|
||||
|
||||
def test_signup_existing_username(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "dup_user")
|
||||
page.fill("#email", "dup1@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
page.context.clear_cookies()
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "dup_user")
|
||||
page.fill("#email", "dup2@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_timeout(300)
|
||||
assert page.is_visible("text=Username already taken")
|
||||
|
||||
|
||||
def test_signup_existing_email(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "email_dup1")
|
||||
page.fill("#email", "sameemail@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
page.context.clear_cookies()
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "email_dup2")
|
||||
page.fill("#email", "sameemail@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_timeout(300)
|
||||
assert page.is_visible("text=Email already registered")
|
||||
|
||||
|
||||
def test_signup_password_mismatch(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "mismatch_user")
|
||||
page.fill("#email", "mismatch@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "different456")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_timeout(300)
|
||||
assert page.is_visible("text=Passwords do not match")
|
||||
|
||||
|
||||
def test_signup_short_password(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "shortpw_user")
|
||||
page.fill("#email", "shortpw@test.devplace")
|
||||
page.fill("#password", "ab")
|
||||
page.fill("#confirm_password", "ab")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_timeout(300)
|
||||
assert page.is_visible("text=Password must be at least 6 characters")
|
||||
|
||||
|
||||
def test_signup_invalid_username(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "ab")
|
||||
page.fill("#email", "shortname@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_timeout(300)
|
||||
assert page.is_visible("text=Username must be between 3 and 32 characters")
|
||||
|
||||
|
||||
def test_login_page_loads(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/login")
|
||||
assert page.is_visible("h2:has-text('Welcome Back')")
|
||||
assert page.is_visible("#email")
|
||||
assert page.is_visible("#password")
|
||||
assert page.is_visible("button:has-text('Sign in')")
|
||||
|
||||
|
||||
def test_login_success(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "login_user")
|
||||
page.fill("#email", "login@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
page.goto(f"{BASE_URL}/auth/logout")
|
||||
|
||||
page.goto(f"{BASE_URL}/auth/login")
|
||||
page.fill("#email", "login@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.click("button:has-text('Sign in')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
assert page.is_visible("text=login_user")
|
||||
|
||||
|
||||
def test_login_wrong_password(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "wrongpw_user")
|
||||
page.fill("#email", "wrongpw@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
page.goto(f"{BASE_URL}/auth/logout")
|
||||
|
||||
page.goto(f"{BASE_URL}/auth/login")
|
||||
page.fill("#email", "wrongpw@test.devplace")
|
||||
page.fill("#password", "badpassword")
|
||||
page.click("button:has-text('Sign in')")
|
||||
page.wait_for_timeout(300)
|
||||
assert page.is_visible("text=Invalid email or password")
|
||||
|
||||
|
||||
def test_login_nonexistent_email(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/login")
|
||||
page.fill("#email", "nobody@nowhere.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.click("button:has-text('Sign in')")
|
||||
page.wait_for_timeout(300)
|
||||
assert page.is_visible("text=Invalid email or password")
|
||||
|
||||
|
||||
def test_login_remember_me(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "remember_user")
|
||||
page.fill("#email", "remember@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
page.goto(f"{BASE_URL}/auth/logout")
|
||||
|
||||
page.goto(f"{BASE_URL}/auth/login")
|
||||
page.fill("#email", "remember@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.check("input[name='remember_me']")
|
||||
page.click("button:has-text('Sign in')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
assert page.is_visible("text=remember_user")
|
||||
|
||||
|
||||
def test_logout(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "logout_user")
|
||||
page.fill("#email", "logout@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
page.goto(f"{BASE_URL}/auth/logout")
|
||||
page.wait_for_url("**/")
|
||||
assert page.is_visible("text=Join DevPlace Free")
|
||||
|
||||
|
||||
def test_signup_link_from_login(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/login")
|
||||
page.click("a:has-text('Create new account')")
|
||||
page.wait_for_url("**/auth/signup")
|
||||
|
||||
|
||||
def test_login_link_from_signup(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.click("a:has-text('Sign in instead')")
|
||||
page.wait_for_url("**/auth/login")
|
||||
|
||||
|
||||
def test_password_toggle(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
pw_input = page.locator("#password")
|
||||
toggle = page.locator(".auth-toggle-pw").first
|
||||
assert pw_input.get_attribute("type") == "password"
|
||||
toggle.click()
|
||||
assert pw_input.get_attribute("type") == "text"
|
||||
toggle.click()
|
||||
assert pw_input.get_attribute("type") == "password"
|
||||
@@ -0,0 +1,399 @@
|
||||
import re
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
|
||||
def create_post(page, content, topic="random", title=None):
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.locator(".feed-fab").click()
|
||||
page.check(f"input[value='{topic}']")
|
||||
page.fill("#post-content", content)
|
||||
if title:
|
||||
page.fill("#post-title", title)
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_url("**/posts/*", timeout=10000)
|
||||
|
||||
|
||||
def add_comment(page, text):
|
||||
textarea = page.locator("textarea[name='content']")
|
||||
textarea.fill(text)
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
|
||||
def signup(page, username, email, password):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", username)
|
||||
page.fill("#email", email)
|
||||
page.fill("#password", password)
|
||||
page.fill("#confirm_password", password)
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
|
||||
def test_absurd_full_journey(browser, app_server):
|
||||
page_a = browser.new_page()
|
||||
page_a.set_default_timeout(10000)
|
||||
page_b = browser.new_page()
|
||||
page_b.set_default_timeout(10000)
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 1 — LANDING PAGE & DISCOVERY
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(f"{BASE_URL}/")
|
||||
assert page_a.is_visible("text=Devplace.net")
|
||||
assert page_a.is_visible("text=The Developer Social Network")
|
||||
assert page_a.is_visible("text=Join DevPlace Free")
|
||||
assert page_a.is_visible("text=100% Free Forever")
|
||||
assert page_a.is_visible("text=Zero Ads")
|
||||
assert page_a.is_visible("text=No Censorship")
|
||||
assert page_a.is_visible("text=No Payments")
|
||||
assert page_a.is_visible("text=Daily Topic")
|
||||
assert page_a.is_visible("text=Read More")
|
||||
assert page_a.is_visible("text=Source")
|
||||
|
||||
page_b.goto(f"{BASE_URL}")
|
||||
page_b.click("text=Log In")
|
||||
page_b.wait_for_url("**/auth/login")
|
||||
page_b.click("text=Create new account")
|
||||
page_b.wait_for_url("**/auth/signup")
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 2 — TWO USERS SIGN UP
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
signup(page_a, "alice_demo", "alice_demo@test.dev", "alice_pass_1")
|
||||
signup(page_b, "bob_demo", "bob_demo@test.dev", "bob_pass_1")
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 3 — ALICE EXPLORES FEED
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(f"{BASE_URL}/feed")
|
||||
assert page_a.is_visible("text=Topics")
|
||||
assert page_a.is_visible("text=All")
|
||||
assert page_a.is_visible("text=Trending")
|
||||
assert page_a.is_visible("text=Recent")
|
||||
assert page_a.is_visible("text=Following")
|
||||
assert page_a.is_visible("text=Devlog")
|
||||
assert page_a.is_visible("text=Showcase")
|
||||
assert page_a.is_visible("text=Question")
|
||||
assert page_a.is_visible("text=Rant")
|
||||
assert page_a.is_visible("text=Fun")
|
||||
assert page_a.is_visible("text=Total Members")
|
||||
assert page_a.is_visible("text=Top Authors")
|
||||
|
||||
# Switch tabs
|
||||
page_a.click("a:has-text('Trending')")
|
||||
page_a.wait_for_url("**/feed?tab=trending")
|
||||
page_a.click("a:has-text('Recent')")
|
||||
page_a.wait_for_url("**/feed?tab=recent")
|
||||
page_a.click("a:has-text('All')")
|
||||
page_a.wait_for_url("**/feed")
|
||||
|
||||
# Click topic filters
|
||||
page_a.click("a:has-text('Showcase')")
|
||||
page_a.wait_for_url("**/feed?topic=showcase")
|
||||
page_a.click("a:has-text('Question')")
|
||||
page_a.wait_for_url("**/feed?topic=question")
|
||||
page_a.click("a:has-text('Rant')")
|
||||
page_a.wait_for_url("**/feed?topic=rant")
|
||||
page_a.click("a:has-text('Fun')")
|
||||
page_a.wait_for_url("**/feed?topic=fun")
|
||||
page_a.click("a:has-text('All')")
|
||||
page_a.wait_for_url("**/feed")
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 4 — ALICE CREATES 4 POSTS
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
create_post(page_a, "Just shipped a new feature for my REST API! Took me 3 days but it was worth it.", "devlog", "API Progress Update")
|
||||
post1_url = page_a.url
|
||||
|
||||
create_post(page_a, "Check out this pixel art editor I built in WASM. Runs at 60fps!", "showcase", "Pixel Editor WASM")
|
||||
post2_url = page_a.url
|
||||
|
||||
create_post(page_a, "Does anyone else think that tabs are objectively better than spaces? Fight me.", "rant", "Tabs vs Spaces")
|
||||
|
||||
create_post(page_a, "Why do we call it 'tech debt' when really it's just 'we cut corners and now we pay'?", "fun")
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 5 — ALICE CREATES A PROJECT
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(f"{BASE_URL}/projects")
|
||||
page_a.locator("#create-project-btn").click()
|
||||
page_a.fill("#title", "N8Nme")
|
||||
page_a.fill("#description", "Just powerful automation at your fingertips. Visual workflow builder with 300+ integrations.")
|
||||
page_a.fill("#release_date", "2026-04-15")
|
||||
page_a.check("input[value='software']")
|
||||
page_a.locator("#platforms-input").fill("Linux")
|
||||
page_a.locator("#platforms-input").press("Enter")
|
||||
page_a.locator("#platforms-input").fill("Docker")
|
||||
page_a.locator("#platforms-input").press("Enter")
|
||||
page_a.locator("#platforms-input").fill("Web")
|
||||
page_a.locator("#platforms-input").press("Enter")
|
||||
page_a.click("button:has-text('Create Project')")
|
||||
page_a.wait_for_timeout(500)
|
||||
assert page_a.is_visible("text=N8Nme")
|
||||
|
||||
# Create a second project
|
||||
page_a.locator("#create-project-btn").click()
|
||||
page_a.fill("#title", "DWN")
|
||||
page_a.fill("#description", "Decentralized web node implementation in Rust. Secure, private, and fast.")
|
||||
page_a.fill("#release_date", "2026-05-01")
|
||||
page_a.check("input[value='software']")
|
||||
page_a.locator("#platforms-input").fill("Linux")
|
||||
page_a.locator("#platforms-input").press("Enter")
|
||||
page_a.locator("#platforms-input").fill("Mac")
|
||||
page_a.locator("#platforms-input").press("Enter")
|
||||
page_a.check("input[value='Released']")
|
||||
page_a.click("button:has-text('Create Project')")
|
||||
page_a.wait_for_timeout(500)
|
||||
assert page_a.is_visible("text=DWN")
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 6 — ALICE EDITS HER PROFILE
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(f"{BASE_URL}/profile/alice_demo")
|
||||
assert page_a.is_visible("text=alice_demo")
|
||||
assert page_a.is_visible("text=Member")
|
||||
assert page_a.is_visible("text=Posts")
|
||||
assert page_a.is_visible("text=Level")
|
||||
assert page_a.is_visible("text=Stars")
|
||||
assert page_a.is_visible("text=Progress to next level")
|
||||
assert page_a.is_visible("text=Bio")
|
||||
|
||||
# Edit bio, location, git link, website
|
||||
page_a.fill("textarea[name='bio']", "Full-stack developer and open source enthusiast. Building the future one commit at a time.")
|
||||
page_a.fill("input[name='location']", "Berlin, Germany")
|
||||
page_a.fill("input[name='git_link']", "https://github.com/alice_demo")
|
||||
page_a.fill("input[name='website']", "https://alicedemo.dev")
|
||||
page_a.click("button:has-text('Save Changes')")
|
||||
page_a.wait_for_timeout(500)
|
||||
assert page_a.is_visible("text=Full-stack developer")
|
||||
assert page_a.is_visible("text=Berlin, Germany")
|
||||
assert page_a.is_visible("text=https://github.com/alice_demo")
|
||||
assert page_a.is_visible("text=https://alicedemo.dev")
|
||||
|
||||
# Check profile tabs
|
||||
page_a.goto(f"{BASE_URL}/profile/alice_demo?tab=projects")
|
||||
page_a.wait_for_timeout(300)
|
||||
assert page_a.is_visible("text=N8Nme")
|
||||
assert page_a.is_visible("text=DWN")
|
||||
|
||||
page_a.goto(f"{BASE_URL}/profile/alice_demo?tab=posts")
|
||||
page_a.wait_for_timeout(300)
|
||||
assert page_a.is_visible("text=API Progress Update")
|
||||
assert page_a.is_visible("text=Pixel Editor WASM")
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 7 — BOB EXPLORES ALICE'S STUFF
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
# Bob views Alice's profile
|
||||
page_b.goto(f"{BASE_URL}/profile/alice_demo")
|
||||
assert page_b.is_visible("text=alice_demo")
|
||||
assert page_b.is_visible("text=Full-stack developer")
|
||||
assert page_b.is_visible("text=Berlin, Germany")
|
||||
|
||||
# Bob checks Alice's projects
|
||||
page_b.goto(f"{BASE_URL}/profile/alice_demo?tab=projects")
|
||||
assert page_b.is_visible("text=N8Nme")
|
||||
assert page_b.is_visible("text=DWN")
|
||||
|
||||
# Bob browses projects page
|
||||
page_b.goto(f"{BASE_URL}/projects")
|
||||
assert page_b.is_visible("text=Recently Released")
|
||||
assert page_b.is_visible("text=Most Popular")
|
||||
assert page_b.is_visible("text=N8Nme")
|
||||
assert page_b.is_visible("text=DWN")
|
||||
|
||||
# Bob searches for a project
|
||||
page_b.fill("input[placeholder='Search projects...']", "N8Nme")
|
||||
page_b.locator("input[placeholder='Search projects...']").press("Enter")
|
||||
page_b.wait_for_timeout(500)
|
||||
assert page_b.is_visible("text=N8Nme")
|
||||
page_b.fill("input[placeholder='Search projects...']", "dwn")
|
||||
page_b.locator("input[placeholder='Search projects...']").press("Enter")
|
||||
page_b.wait_for_timeout(500)
|
||||
assert page_b.is_visible("text=DWN")
|
||||
|
||||
# Bob creates his own project
|
||||
page_b.locator("#create-project-btn").click()
|
||||
page_b.fill("#title", "ClaudeCode")
|
||||
page_b.fill("#description", "AI-powered code generation tool for the terminal. Write code with natural language.")
|
||||
page_b.check("input[value='software']")
|
||||
page_b.check("input[value='Released']")
|
||||
page_b.locator("#platforms-input").fill("Linux")
|
||||
page_b.locator("#platforms-input").press("Enter")
|
||||
page_b.locator("#platforms-input").fill("Mac")
|
||||
page_b.locator("#platforms-input").press("Enter")
|
||||
page_b.click("button:has-text('Create Project')")
|
||||
page_b.wait_for_timeout(500)
|
||||
assert page_b.is_visible("text=ClaudeCode")
|
||||
|
||||
# Bob views Alice's post
|
||||
page_b.goto(post1_url)
|
||||
assert page_b.is_visible("text=API Progress Update")
|
||||
assert page_b.is_visible("text=Just shipped a new feature")
|
||||
|
||||
# Bob votes on Alice's post
|
||||
vote_btn = page_b.locator("button").filter(has_text=re.compile(r"\+")).first
|
||||
vote_btn.click()
|
||||
page_b.wait_for_timeout(500)
|
||||
|
||||
# Bob comments on Alice's post
|
||||
page_b.fill("textarea[name='content']", "Nice work! What framework did you use for the API?")
|
||||
page_b.click("button:has-text('Post')")
|
||||
page_b.wait_for_timeout(500)
|
||||
assert page_b.is_visible("text=Nice work! What framework did you use for the API?")
|
||||
|
||||
# Bob votes on Alice's showcase post
|
||||
page_b.goto(post2_url)
|
||||
assert page_b.is_visible("text=Pixel Editor WASM")
|
||||
page_b.fill("textarea[name='content']", "This is incredible! 60fps in WASM is impressive.")
|
||||
page_b.click("button:has-text('Post')")
|
||||
page_b.wait_for_timeout(500)
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 8 — ALICE REPLIES TO BOB'S COMMENT
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(post1_url)
|
||||
assert page_a.is_visible("text=Nice work! What framework did you use for the API?")
|
||||
|
||||
# Write a reply comment
|
||||
page_a.fill("textarea[name='content']", "Thanks! I used FastAPI with SQLAlchemy for the backend. The frontend is vanilla JS.")
|
||||
page_a.click("button:has-text('Post')")
|
||||
page_a.wait_for_timeout(500)
|
||||
assert page_a.is_visible("text=Thanks! I used FastAPI")
|
||||
|
||||
# Alice votes on Bob's comment
|
||||
vote_btns = page_a.locator(".comment-vote-btn")
|
||||
upvote_count = vote_btns.count()
|
||||
if upvote_count > 0:
|
||||
vote_btns.first.click()
|
||||
page_a.wait_for_timeout(500)
|
||||
|
||||
# Alice votes on her own post
|
||||
vote_btn_a = page_a.locator("button").filter(has_text=re.compile(r"\+")).first
|
||||
vote_btn_a.click()
|
||||
page_a.wait_for_timeout(500)
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 9 — BOB SENDS ALICE A MESSAGE
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
# Bob needs Alice's UID to message her. We can find it via the profile URL.
|
||||
# Use the search in messages to find Alice
|
||||
page_b.goto(f"{BASE_URL}/messages?search=alice_demo")
|
||||
page_b.wait_for_timeout(500)
|
||||
|
||||
page_b.fill("input[name='content']", "Hey Alice! I saw your pixel editor project, it looks amazing!")
|
||||
send_btn = page_b.locator("button:has-text('➤')")
|
||||
if send_btn.is_visible():
|
||||
send_btn.click()
|
||||
page_b.wait_for_timeout(500)
|
||||
|
||||
page_b.fill("input[name='content']", "Would you be interested in collaborating on a game project?")
|
||||
page_b.locator("button:has-text('➤')").click()
|
||||
page_b.wait_for_timeout(500)
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 10 — ALICE CHECKS NOTIFICATIONS
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(f"{BASE_URL}/notifications")
|
||||
assert page_a.is_visible("h2:has-text('Notifications')")
|
||||
assert page_a.is_visible("text=bob_demo") or page_a.is_visible("text=commented")
|
||||
|
||||
# Mark all as read
|
||||
mark_all = page_a.locator("button:has-text('Mark all read')")
|
||||
if mark_all.is_visible():
|
||||
mark_all.click()
|
||||
page_a.wait_for_timeout(500)
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 11 — ALICE CHECKS HER MESSAGES
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(f"{BASE_URL}/messages")
|
||||
assert page_a.is_visible(".messages-layout")
|
||||
|
||||
# Conversation list should show Bob
|
||||
page_a.goto(f"{BASE_URL}/messages?search=bob_demo")
|
||||
page_a.wait_for_timeout(500)
|
||||
|
||||
# Reply to Bob
|
||||
msg_input = page_a.locator("input[name='content']")
|
||||
if msg_input.is_visible():
|
||||
msg_input.fill("Hey Bob! Thanks for the kind words! I'd love to collaborate.")
|
||||
send_btn_a = page_a.locator("button:has-text('➤')")
|
||||
if send_btn_a.is_visible():
|
||||
send_btn_a.click()
|
||||
page_a.wait_for_timeout(500)
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 12 — BOB CHECKS HIS NOTIFICATIONS
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_b.goto(f"{BASE_URL}/notifications")
|
||||
assert page_b.is_visible("h2:has-text('Notifications')")
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 13 — ALICE BACK ON FEED, UPDATES POST
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(post1_url)
|
||||
assert page_a.is_visible("text=API Progress Update")
|
||||
|
||||
# Share button exists
|
||||
share = page_a.locator("button:has-text('Share')")
|
||||
assert share.is_visible()
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 14 — LOGOUT BOTH USERS
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(f"{BASE_URL}/auth/logout")
|
||||
page_a.wait_for_url("**/")
|
||||
assert page_a.is_visible("text=Join DevPlace Free")
|
||||
|
||||
# Login form still works
|
||||
page_a.click("text=Log In")
|
||||
page_a.wait_for_url("**/auth/login")
|
||||
assert page_a.is_visible("h2:has-text('Welcome Back')")
|
||||
|
||||
page_a.fill("#email", "alice_demo@test.dev")
|
||||
page_a.fill("#password", "alice_pass_1")
|
||||
page_a.click("button:has-text('Sign in')")
|
||||
page_a.wait_for_url("**/feed", timeout=10000)
|
||||
assert page_a.is_visible("text=alice_demo")
|
||||
|
||||
page_b.goto(f"{BASE_URL}/auth/logout")
|
||||
page_b.wait_for_url("**/")
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# ACT 15 — FINAL CHECKS
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
page_a.goto(f"{BASE_URL}/")
|
||||
page_a.wait_for_url("**/feed")
|
||||
assert page_a.is_visible("text=alice_demo")
|
||||
|
||||
page_a.goto(f"{BASE_URL}/notifications")
|
||||
assert page_a.is_visible("h2:has-text('Notifications')")
|
||||
|
||||
# Back to the feed, everything loads
|
||||
page_a.goto(f"{BASE_URL}/feed")
|
||||
assert page_a.is_visible("text=Topics")
|
||||
|
||||
# Cleanup
|
||||
page_a.close()
|
||||
page_b.close()
|
||||
|
||||
print("\n═══════════════════════════════════════════════════")
|
||||
print(" ABSURD DEMO COMPLETE — 15 ACTS, 0 MISTAKES")
|
||||
print("═══════════════════════════════════════════════════")
|
||||
@@ -0,0 +1,141 @@
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
|
||||
def test_feed_page_loads(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
assert page.is_visible("text=Topics")
|
||||
assert page.is_visible("text=All")
|
||||
assert page.is_visible("text=Devlog")
|
||||
assert page.is_visible("text=Showcase")
|
||||
|
||||
|
||||
def test_feed_nav_tabs(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
assert page.is_visible("a:has-text('All')")
|
||||
assert page.is_visible("a:has-text('Trending')")
|
||||
assert page.is_visible("a:has-text('Recent')")
|
||||
assert page.is_visible("a:has-text('Following')")
|
||||
|
||||
|
||||
def test_feed_tab_switching(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.click("a:has-text('Trending')")
|
||||
page.wait_for_url(f"{BASE_URL}/feed?tab=trending")
|
||||
page.click("a:has-text('Recent')")
|
||||
page.wait_for_url(f"{BASE_URL}/feed?tab=recent")
|
||||
|
||||
|
||||
def test_feed_topic_filter_sidebar(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
topics = ["Devlog", "Showcase", "Question", "Rant", "Fun"]
|
||||
for topic in topics:
|
||||
link = page.locator(f"a:has-text('{topic}')").first
|
||||
assert link.is_visible()
|
||||
|
||||
|
||||
def test_feed_topic_filter_navigation(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.click("a:has-text('Devlog')")
|
||||
page.wait_for_url(f"{BASE_URL}/feed?topic=devlog")
|
||||
|
||||
|
||||
def test_feed_community_stats(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
assert page.is_visible("text=Total Members")
|
||||
assert page.is_visible("text=Posts Today")
|
||||
assert page.is_visible("text=Total Projects")
|
||||
|
||||
|
||||
def test_feed_top_authors(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
assert page.is_visible("text=Top Authors")
|
||||
|
||||
|
||||
def test_feed_daily_topic(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
assert page.is_visible("text=Daily Topic")
|
||||
|
||||
|
||||
def test_create_post_fab(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
fab = page.locator(".feed-fab")
|
||||
assert fab.is_visible()
|
||||
fab.click()
|
||||
assert page.is_visible("text=Create New Post")
|
||||
assert page.is_visible("#post-content")
|
||||
assert page.is_visible("button:has-text('Post')")
|
||||
|
||||
|
||||
def test_create_post_devlog(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.locator(".feed-fab").click()
|
||||
page.check("input[value='devlog']")
|
||||
page.fill("#post-content", "This is a test devlog post created by Playwright")
|
||||
page.fill("#post-title", "Test Devlog Title")
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_url(f"{BASE_URL}/posts/*")
|
||||
assert page.is_visible("text=Test Devlog Title")
|
||||
assert page.is_visible("text=This is a test devlog post created by Playwright")
|
||||
|
||||
|
||||
def test_create_post_showcase(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.locator(".feed-fab").click()
|
||||
page.check("input[value='showcase']")
|
||||
page.fill("#post-content", "Check out my new project showcase")
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_url(f"{BASE_URL}/posts/*")
|
||||
|
||||
|
||||
def test_create_post_without_title(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.locator(".feed-fab").click()
|
||||
page.check("input[value='rant']")
|
||||
page.fill("#post-content", "Rant without a title")
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_url(f"{BASE_URL}/posts/*")
|
||||
assert page.is_visible("text=Rant without a title")
|
||||
|
||||
|
||||
def test_feed_topnav_navigation(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.click("a:has-text('Projects')")
|
||||
page.wait_for_url(f"{BASE_URL}/projects")
|
||||
page.click("a:has-text('Home')")
|
||||
page.wait_for_url(f"{BASE_URL}/feed")
|
||||
|
||||
|
||||
def test_topnav_notification_bell(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
bell = page.locator(".topnav-icon").first
|
||||
assert bell.is_visible()
|
||||
|
||||
|
||||
def test_topnav_user_menu(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
assert page.is_visible(f"text={user['username']}")
|
||||
|
||||
|
||||
def test_create_post_cancel_modal(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.locator(".feed-fab").click()
|
||||
cancel = page.locator("button:has-text('Cancel')").first
|
||||
cancel.click()
|
||||
modal = page.locator("#create-post-modal")
|
||||
assert not modal.is_visible()
|
||||
@@ -0,0 +1,82 @@
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
|
||||
def test_landing_page_loads(page, app_server):
|
||||
page.goto(f"{BASE_URL}/")
|
||||
assert "DevPlace" in page.title()
|
||||
assert page.is_visible("text=Devplace.net")
|
||||
assert page.is_visible("text=The Developer Social Network")
|
||||
|
||||
|
||||
def test_landing_tagline(page, app_server):
|
||||
page.goto(f"{BASE_URL}/")
|
||||
tagline = page.text_content("p")
|
||||
assert "Track industry shifts" in tagline
|
||||
|
||||
|
||||
def test_landing_join_button(page, app_server):
|
||||
page.goto(f"{BASE_URL}/")
|
||||
btn = page.locator("text=Join DevPlace Free")
|
||||
assert btn.is_visible()
|
||||
assert btn.get_attribute("href") == "/auth/signup"
|
||||
|
||||
|
||||
def test_landing_features(page, app_server):
|
||||
page.goto(f"{BASE_URL}/")
|
||||
features = [
|
||||
"100% Free Forever",
|
||||
"Zero Ads",
|
||||
"No Censorship",
|
||||
"No Payments",
|
||||
]
|
||||
for feature in features:
|
||||
assert page.is_visible(f"text={feature}")
|
||||
|
||||
|
||||
def test_landing_daily_topic(page, app_server):
|
||||
page.goto(f"{BASE_URL}/")
|
||||
assert page.is_visible("text=Daily Topic")
|
||||
assert page.is_visible("text=Read More")
|
||||
assert page.is_visible("text=Source")
|
||||
|
||||
|
||||
def test_landing_nav_links(page, app_server):
|
||||
page.goto(f"{BASE_URL}/")
|
||||
assert page.is_visible("a:has-text('Home')")
|
||||
assert page.is_visible("a:has-text('Projects')")
|
||||
assert page.is_visible("a:has-text('Login')")
|
||||
assert page.is_visible("a:has-text('Sign Up')")
|
||||
|
||||
|
||||
def test_landing_nav_to_login(page, app_server):
|
||||
page.goto(f"{BASE_URL}/")
|
||||
page.click("a:has-text('Login')")
|
||||
page.wait_for_url(f"{BASE_URL}/auth/login")
|
||||
assert page.is_visible("h2:has-text('Welcome Back')")
|
||||
|
||||
|
||||
def test_landing_nav_to_signup(page, app_server):
|
||||
page.goto(f"{BASE_URL}/")
|
||||
page.click("a:has-text('Sign Up')")
|
||||
page.wait_for_url(f"{BASE_URL}/auth/signup")
|
||||
assert page.is_visible("h2:has-text('Join the Community')")
|
||||
|
||||
|
||||
def test_landing_log_in_link(page, app_server):
|
||||
page.goto(f"{BASE_URL}/")
|
||||
page.click("text=Log In")
|
||||
page.wait_for_url(f"{BASE_URL}/auth/login")
|
||||
|
||||
|
||||
def test_landing_authenticated_redirects_to_feed(page, app_server):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", "redirect_user")
|
||||
page.fill("#email", "redirect@test.devplace")
|
||||
page.fill("#password", "secret123")
|
||||
page.fill("#confirm_password", "secret123")
|
||||
with page.expect_navigation(timeout=10000):
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
page.goto(f"{BASE_URL}/")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
@@ -0,0 +1,33 @@
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
|
||||
def test_messages_page_loads(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/messages")
|
||||
assert page.is_visible(".messages-layout")
|
||||
|
||||
|
||||
def test_messages_search_input(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/messages")
|
||||
search = page.locator("#message-search")
|
||||
assert search.is_visible()
|
||||
assert search.get_attribute("placeholder") == "Search conversations..."
|
||||
|
||||
|
||||
def test_messages_empty_state(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/messages")
|
||||
empty = page.locator("text=No conversations yet")
|
||||
if empty.is_visible():
|
||||
pass
|
||||
else:
|
||||
assert page.is_visible(".messages-layout")
|
||||
|
||||
|
||||
def test_messages_search_for_bob(alice):
|
||||
page, _ = alice
|
||||
page.fill("#message-search", "bob_test")
|
||||
page.locator("#message-search").press("Enter")
|
||||
page.wait_for_timeout(500)
|
||||
assert page.is_visible(".messages-layout")
|
||||
@@ -0,0 +1,30 @@
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
|
||||
def test_notifications_page_loads(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/notifications")
|
||||
assert page.is_visible("h2:has-text('Notifications')")
|
||||
|
||||
|
||||
def test_mark_all_read(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/notifications")
|
||||
mark_all = page.locator("button:has-text('Mark all read')")
|
||||
if mark_all.is_visible():
|
||||
mark_all.click()
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
|
||||
def test_notifications_navigation(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.goto(f"{BASE_URL}/notifications")
|
||||
assert page.is_visible("h2:has-text('Notifications')")
|
||||
|
||||
|
||||
def test_notifications_bell_visible(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
bell = page.locator(".topnav-icon").first
|
||||
assert bell.is_visible()
|
||||
@@ -0,0 +1,154 @@
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
|
||||
def create_post(page, topic="random", content="Test post content", title=None):
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.locator(".feed-fab").click()
|
||||
page.check(f"input[value='{topic}']")
|
||||
page.fill("#post-content", content)
|
||||
if title:
|
||||
page.fill("#post-title", title)
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_url(f"{BASE_URL}/posts/*")
|
||||
|
||||
|
||||
def test_post_detail_page(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "random", "Detail page test content")
|
||||
assert page.is_visible("text=Detail page test content")
|
||||
assert page.is_visible("text=Back to Feed")
|
||||
|
||||
|
||||
def test_post_topic_badge(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "question", "Question post for badge check")
|
||||
badge = page.locator(".badge-question")
|
||||
assert badge.is_visible()
|
||||
|
||||
|
||||
def test_post_author_info(alice):
|
||||
page, user = alice
|
||||
create_post(page, "fun", "Post with author check")
|
||||
assert page.is_visible(f"text={user['username']}")
|
||||
|
||||
|
||||
def test_post_vote_button(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "devlog", "Votable post")
|
||||
vote_btn = page.locator("button:has-text('+0')").first
|
||||
assert vote_btn.is_visible()
|
||||
|
||||
|
||||
def test_post_vote_increment(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "showcase", "Vote increment test")
|
||||
vote_btn = page.locator("button").filter(has_text="+").first
|
||||
vote_btn.click()
|
||||
page.wait_for_timeout(500)
|
||||
content = page.locator(".post-detail-actions").text_content()
|
||||
assert "+1" in content
|
||||
|
||||
|
||||
def test_post_comments_section(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "rant", "Comment section test")
|
||||
assert page.is_visible("text=Comments")
|
||||
assert page.is_visible("textarea[placeholder='Your opinion goes here...']")
|
||||
|
||||
|
||||
def test_add_comment(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "random", "Post for commenting")
|
||||
textarea = page.locator("textarea[name='content']")
|
||||
textarea.fill("This is a test comment from Playwright")
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_timeout(500)
|
||||
assert page.is_visible("text=This is a test comment from Playwright")
|
||||
|
||||
|
||||
def test_comment_voting(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "devlog", "Post for comment voting")
|
||||
textarea = page.locator("textarea[name='content']")
|
||||
textarea.fill("Votable comment")
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_timeout(500)
|
||||
vote_btns = page.locator(".comment-vote-btn")
|
||||
upvote = vote_btns.first
|
||||
upvote.click()
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
|
||||
def test_delete_own_comment(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "fun", "Post for comment deletion")
|
||||
textarea = page.locator("textarea[name='content']")
|
||||
textarea.fill("Comment to delete")
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_timeout(500)
|
||||
delete_btn = page.locator("button:has-text('Delete')")
|
||||
if delete_btn.is_visible():
|
||||
delete_btn.click()
|
||||
page.wait_for_timeout(500)
|
||||
assert not delete_btn.is_visible()
|
||||
|
||||
|
||||
def test_comment_form_elements(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "random", "Post for checking comment form")
|
||||
assert page.is_visible("textarea[placeholder='Your opinion goes here...']")
|
||||
|
||||
|
||||
def test_share_button(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "showcase", "Share button check")
|
||||
share = page.locator("button:has-text('Share')")
|
||||
assert share.is_visible()
|
||||
|
||||
|
||||
def test_back_to_feed_link(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "random", "Back link test")
|
||||
back = page.locator("a:has-text('Back to Feed')")
|
||||
assert back.is_visible()
|
||||
back.click()
|
||||
page.wait_for_url(f"{BASE_URL}/feed")
|
||||
|
||||
|
||||
def test_multiple_comments_on_post(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "devlog", "Post with many comments")
|
||||
for i in range(3):
|
||||
page.locator("textarea[name='content']").fill(f"Comment number {i + 1}")
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_timeout(300)
|
||||
assert page.is_visible("text=Comment number 1")
|
||||
assert page.is_visible("text=Comment number 3")
|
||||
|
||||
|
||||
def test_comment_and_vote_then_delete(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "showcase", "Full comment lifecycle post")
|
||||
page.locator("textarea[name='content']").fill("Lifecycle comment")
|
||||
page.click("button:has-text('Post')")
|
||||
page.wait_for_timeout(300)
|
||||
assert page.is_visible("text=Lifecycle comment")
|
||||
|
||||
vote_up = page.locator(".comment-vote-btn").first
|
||||
vote_up.click()
|
||||
page.wait_for_timeout(300)
|
||||
|
||||
delete_btn = page.locator("button:has-text('Delete')")
|
||||
if delete_btn.is_visible():
|
||||
delete_btn.click()
|
||||
page.wait_for_timeout(300)
|
||||
|
||||
|
||||
def test_post_across_all_topics(alice):
|
||||
page, _ = alice
|
||||
topics = ["devlog", "showcase", "question", "rant", "fun"]
|
||||
for topic in topics:
|
||||
create_post(page, topic, f"Topic test post for {topic}")
|
||||
badge = page.locator(f".badge-{topic}")
|
||||
assert badge.is_visible()
|
||||
page.wait_for_timeout(200)
|
||||
@@ -0,0 +1,139 @@
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
|
||||
def test_profile_page_loads(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
assert page.is_visible(f"text={user['username']}")
|
||||
|
||||
|
||||
def test_profile_stats(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
assert page.is_visible("text=Posts")
|
||||
assert page.is_visible("text=Level")
|
||||
assert page.is_visible("text=Stars")
|
||||
|
||||
|
||||
def test_profile_level_bar(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
assert page.is_visible("text=Progress to next level")
|
||||
level_bar = page.locator(".bar")
|
||||
assert level_bar.is_visible()
|
||||
|
||||
|
||||
def test_profile_badges(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
badges = page.locator(".profile-badge")
|
||||
assert badges.count() >= 1
|
||||
|
||||
|
||||
def test_profile_tabs(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
tabs = ["Posts", "Projects", "Activity"]
|
||||
for tab in tabs:
|
||||
assert page.is_visible(f"a:has-text('{tab}')")
|
||||
|
||||
|
||||
def test_profile_posts_tab(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}?tab=posts")
|
||||
|
||||
|
||||
def test_profile_projects_tab_content(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}?tab=projects")
|
||||
|
||||
|
||||
def test_profile_info_section(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
assert page.is_visible("text=Bio")
|
||||
assert page.is_visible("text=Location")
|
||||
|
||||
|
||||
def test_profile_back_link(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
back = page.locator("a:has-text('Back')").first
|
||||
assert back.is_visible()
|
||||
back.click()
|
||||
page.wait_for_url(f"{BASE_URL}/feed")
|
||||
|
||||
|
||||
def test_profile_role_display(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
assert page.is_visible("text=Member")
|
||||
|
||||
|
||||
def test_profile_avatar(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
avatar = page.locator(".avatar-lg")
|
||||
assert avatar.is_visible()
|
||||
|
||||
|
||||
def test_profile_nav_from_topbar(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/feed")
|
||||
page.locator(".topnav-user").click()
|
||||
page.wait_for_timeout(300)
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
assert page.is_visible(f"text={user['username']}")
|
||||
|
||||
|
||||
def test_profile_edit_bio(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
page.evaluate("document.getElementById('input-bio').style.display = 'block'")
|
||||
page.locator("textarea[name='bio']").fill("Test bio for testing", force=True)
|
||||
page.click("button:has-text('Save Changes')")
|
||||
page.wait_for_timeout(500)
|
||||
assert page.is_visible("text=Test bio for testing")
|
||||
|
||||
|
||||
def test_profile_edit_location(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
page.evaluate("document.getElementById('input-location').style.display = 'block'")
|
||||
page.locator("input[name='location']").fill("Amsterdam", force=True)
|
||||
page.click("button:has-text('Save Changes')")
|
||||
page.wait_for_timeout(500)
|
||||
assert page.is_visible("text=Amsterdam")
|
||||
|
||||
|
||||
def test_profile_edit_all_fields(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}")
|
||||
page.evaluate("""
|
||||
document.getElementById('input-bio').style.display = 'block';
|
||||
document.getElementById('input-location').style.display = 'block';
|
||||
document.getElementById('input-git_link').style.display = 'block';
|
||||
document.getElementById('input-website').style.display = 'block';
|
||||
""")
|
||||
page.locator("textarea[name='bio']").fill("Full stack dev", force=True)
|
||||
page.locator("input[name='location']").fill("London, UK", force=True)
|
||||
page.locator("input[name='git_link']").fill("https://git.example.com/alice", force=True)
|
||||
page.locator("input[name='website']").fill("https://alice.example.com", force=True)
|
||||
page.click("button:has-text('Save Changes')")
|
||||
page.wait_for_timeout(500)
|
||||
assert page.is_visible("text=Full stack dev")
|
||||
assert page.is_visible("text=London, UK")
|
||||
assert page.is_visible("text=https://git.example.com/alice")
|
||||
assert page.is_visible("text=https://alice.example.com")
|
||||
|
||||
|
||||
def test_profile_viewing_other_user(bob, alice):
|
||||
page_b, user_b = bob
|
||||
page_b.goto(f"{BASE_URL}/profile/alice_test")
|
||||
assert page_b.is_visible("text=alice_test")
|
||||
|
||||
|
||||
def test_profile_activity_tab(alice):
|
||||
page, user = alice
|
||||
page.goto(f"{BASE_URL}/profile/{user['username']}?tab=activity")
|
||||
page.wait_for_timeout(300)
|
||||
@@ -0,0 +1,128 @@
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
|
||||
def test_projects_page_loads(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
assert page.is_visible("h2:has-text('Projects')")
|
||||
assert page.is_visible("text=Discover amazing projects")
|
||||
|
||||
|
||||
def test_projects_search_bar(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
search = page.locator("input[placeholder='Search projects...']")
|
||||
assert search.is_visible()
|
||||
|
||||
|
||||
def test_projects_tabs(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
tabs = ["Recently Released", "Most Popular", "New This Week"]
|
||||
for tab in tabs:
|
||||
assert page.is_visible(f"text={tab}")
|
||||
|
||||
|
||||
def test_projects_tab_switch(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
page.click("a:has-text('Most Popular')")
|
||||
page.wait_for_url(f"{BASE_URL}/projects?tab=popular")
|
||||
page.click("a:has-text('New This Week')")
|
||||
page.wait_for_url(f"{BASE_URL}/projects?tab=new")
|
||||
|
||||
|
||||
def test_create_project_button(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
fab = page.locator("#create-project-btn")
|
||||
assert fab.is_visible()
|
||||
fab.click()
|
||||
assert page.is_visible("h3:has-text('Create Project')")
|
||||
|
||||
|
||||
def test_create_project_full(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
page.locator("#create-project-btn").click()
|
||||
page.fill("#title", "Playwright Test Game")
|
||||
page.fill("#description", "A game created by Playwright integration tests")
|
||||
page.fill("#release_date", "2026-06-01")
|
||||
page.check("input[value='game']")
|
||||
page.locator("#platforms-input").fill("PC")
|
||||
page.locator("#platforms-input").press("Enter")
|
||||
page.click("button:has-text('Create Project')")
|
||||
page.wait_for_timeout(500)
|
||||
assert page.is_visible("text=Playwright Test Game")
|
||||
|
||||
|
||||
def test_create_project_software(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
page.locator("#create-project-btn").click()
|
||||
page.fill("#title", "CLI Tool")
|
||||
page.fill("#description", "A command line tool")
|
||||
page.check("input[value='software']")
|
||||
page.check("input[value='Released']")
|
||||
page.click("button:has-text('Create Project')")
|
||||
page.wait_for_timeout(500)
|
||||
assert page.is_visible("text=CLI Tool")
|
||||
|
||||
|
||||
def test_create_project_mobile_app(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
page.locator("#create-project-btn").click()
|
||||
page.fill("#title", "Mobile Messenger")
|
||||
page.fill("#description", "A cross-platform messaging app")
|
||||
page.check("input[value='mobile_app']")
|
||||
page.locator("#platforms-input").fill("iOS")
|
||||
page.locator("#platforms-input").press("Enter")
|
||||
page.locator("#platforms-input").fill("Android")
|
||||
page.locator("#platforms-input").press("Enter")
|
||||
page.click("button:has-text('Create Project')")
|
||||
page.wait_for_timeout(500)
|
||||
assert page.is_visible("text=Mobile Messenger")
|
||||
|
||||
|
||||
def test_project_search(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
page.locator("#create-project-btn").click()
|
||||
page.fill("#title", "SearchableProject")
|
||||
page.fill("#description", "Find me via search")
|
||||
page.click("button:has-text('Create Project')")
|
||||
page.wait_for_timeout(500)
|
||||
page.fill("input[placeholder='Search projects...']", "SearchableProject")
|
||||
page.locator("input[placeholder='Search projects...']").press("Enter")
|
||||
page.wait_for_timeout(500)
|
||||
assert page.is_visible("text=SearchableProject")
|
||||
|
||||
|
||||
def test_project_cancel_modal(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
page.locator("#create-project-btn").click()
|
||||
cancel = page.locator("button:has-text('Cancel')").first
|
||||
cancel.click()
|
||||
modal = page.locator("#create-project-modal")
|
||||
assert not modal.is_visible()
|
||||
|
||||
|
||||
def test_project_platform_presets(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
page.locator("#create-project-btn").click()
|
||||
presets = page.locator(".platform-preset")
|
||||
assert presets.count() >= 5
|
||||
presets.first.click()
|
||||
tag = page.locator("#platforms-tags .platform-tag")
|
||||
assert tag.is_visible()
|
||||
|
||||
|
||||
def test_projects_count(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/projects")
|
||||
count_text = page.text_content(".projects-count")
|
||||
assert "Showing" in count_text
|
||||
assert "projects" in count_text
|
||||
Reference in New Issue
Block a user