Implemented. Sign in, then open Experience or Skills to:
- Add, edit, and delete experience entries and project links. - Manage skill categories and individual skill labels. - Edit the skills introduction and “How I work” text. Changes save directly to Supabase—no code edits or Git push needed. Existing content is preserved.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
"""Browser checks against isolated local data: python -m scripts.test_portfolio_editors."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
import uvicorn
|
||||
from playwright.sync_api import expect, sync_playwright
|
||||
from backend import main
|
||||
|
||||
|
||||
def run():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
for source in main.DATA_DIR.glob("*.json"):
|
||||
if source.name != "media-deletions.json":
|
||||
shutil.copyfile(source, root / source.name)
|
||||
main.DATA_DIR = root
|
||||
main.POSTS_PATH = root / "posts.json"
|
||||
main.get_supabase = lambda: None
|
||||
main.read_data.cache_clear()
|
||||
os.environ.update(JOURNAL_ADMIN_PASSWORD="browser-test", JOURNAL_TOKEN_SECRET="browser-test-secret")
|
||||
server = uvicorn.Server(uvicorn.Config(main.app, host="127.0.0.1", port=8011, log_level="error"))
|
||||
thread = threading.Thread(target=server.run, daemon=True); thread.start()
|
||||
for _ in range(100):
|
||||
if server.started: break
|
||||
time.sleep(.05)
|
||||
assert server.started
|
||||
try:
|
||||
with sync_playwright() as playwright:
|
||||
browser = playwright.chromium.launch(channel="msedge", headless=True)
|
||||
page = browser.new_page(viewport={"width": 1280, "height": 1000})
|
||||
errors = []
|
||||
page.on("pageerror", lambda error: errors.append(str(error)))
|
||||
page.goto("http://127.0.0.1:8011/experience")
|
||||
expect(page.locator(".timeline-card").first).to_be_visible()
|
||||
expect(page.get_by_role("button", name="Add experience", exact=True)).to_have_count(0)
|
||||
|
||||
def sign_in():
|
||||
dialog = page.get_by_role("dialog", name="Admin sign in", exact=True)
|
||||
dialog.get_by_label("Admin password").fill("browser-test")
|
||||
dialog.get_by_role("button", name="Sign in", exact=True).click()
|
||||
expect(dialog).to_have_count(0)
|
||||
|
||||
page.get_by_role("button", name="Admin sign in", exact=True).click(); sign_in()
|
||||
page.get_by_role("button", name="Add experience", exact=True).click()
|
||||
dialog = page.get_by_role("dialog", name="Add experience", exact=True)
|
||||
for label, value in [("Role", "Browser engineer"), ("Company", "Example team"), ("Location", "Remote"), ("Start date", "2026-09"), ("Summary", "Deliver useful software and maintain reliable systems.")]:
|
||||
dialog.get_by_label(label, exact=True).fill(value)
|
||||
dialog.get_by_label("Highlights", exact=False).fill("First achievement\nSecond achievement")
|
||||
dialog.get_by_label("Technologies", exact=True).fill("Python")
|
||||
dialog.get_by_label("Technologies", exact=True).press("Enter")
|
||||
expect(dialog.get_by_role("button", name="Remove Python", exact=True)).to_be_visible()
|
||||
dialog.get_by_role("button", name="Add project", exact=True).click()
|
||||
dialog.get_by_label("Project name", exact=True).fill("Demo")
|
||||
dialog.get_by_label("Project URL", exact=True).fill("https://example.com")
|
||||
dialog.get_by_role("button", name="Add experience", exact=True).click()
|
||||
expect(page.get_by_role("heading", name="Browser engineer", exact=True)).to_be_visible()
|
||||
page.get_by_role("button", name="Edit Browser engineer at Example team", exact=True).click()
|
||||
dialog = page.get_by_role("dialog", name="Edit experience", exact=True)
|
||||
dialog.get_by_label("Role", exact=True).fill("Lead browser engineer")
|
||||
dialog.get_by_label("I currently work here").uncheck()
|
||||
dialog.get_by_label("End date", exact=True).fill("2027-01")
|
||||
dialog.get_by_role("button", name="Save changes", exact=True).click()
|
||||
expect(dialog).to_have_count(0)
|
||||
page.reload()
|
||||
expect(page.get_by_role("heading", name="Lead browser engineer", exact=True)).to_be_visible()
|
||||
page.get_by_role("button", name="Delete Lead browser engineer at Example team", exact=True).click()
|
||||
page.get_by_role("dialog").get_by_role("button", name="Cancel", exact=True).click()
|
||||
expect(page.get_by_role("heading", name="Lead browser engineer", exact=True)).to_be_visible()
|
||||
page.get_by_role("button", name="Delete Lead browser engineer at Example team", exact=True).click()
|
||||
page.get_by_role("dialog").get_by_role("button", name="Delete entry", exact=True).click()
|
||||
expect(page.get_by_role("heading", name="Lead browser engineer", exact=True)).to_have_count(0)
|
||||
print("Experience create, edit, reload persistence, project links, labels, and confirmed deletion passed.", flush=True)
|
||||
|
||||
page.goto("http://127.0.0.1:8011/skills")
|
||||
page.get_by_role("button", name="Add category", exact=True).click()
|
||||
dialog = page.get_by_role("dialog", name="Add skill category", exact=True)
|
||||
dialog.get_by_label("Category name", exact=True).fill("Browser tools")
|
||||
dialog.get_by_label("Description", exact=True).fill("Tools used in a browser check.")
|
||||
dialog.get_by_label("Skills", exact=True).fill("Python")
|
||||
dialog.get_by_label("Skills", exact=True).press("Enter")
|
||||
dialog.get_by_role("button", name="Add category", exact=True).click()
|
||||
expect(page.get_by_role("heading", name="Browser tools", exact=True)).to_be_visible()
|
||||
page.get_by_role("button", name="Edit Browser tools", exact=True).click()
|
||||
dialog = page.get_by_role("dialog", name="Edit skill category", exact=True)
|
||||
dialog.get_by_role("button", name="Remove Python", exact=True).click()
|
||||
dialog.get_by_label("Skills", exact=True).fill("TypeScript")
|
||||
dialog.get_by_label("Skills", exact=True).press("Enter")
|
||||
dialog.get_by_label("Category name", exact=True).fill("Updated browser tools")
|
||||
failed = [False]
|
||||
def expire_once(route):
|
||||
if route.request.method == "PUT" and not failed[0]:
|
||||
failed[0] = True
|
||||
route.fulfill(status=401, content_type="application/json", body=json.dumps({"detail": "Your session ended."}))
|
||||
else: route.continue_()
|
||||
page.route("**/api/skills/categories/*", expire_once)
|
||||
dialog.get_by_role("button", name="Save changes", exact=True).click()
|
||||
expect(dialog).to_contain_text("Your edits are still here")
|
||||
dialog.get_by_role("button", name="Sign in", exact=True).click(); sign_in()
|
||||
expect(dialog.get_by_label("Category name", exact=True)).to_have_value("Updated browser tools")
|
||||
dialog.get_by_role("button", name="Save changes", exact=True).click()
|
||||
expect(dialog).to_have_count(0)
|
||||
page.reload()
|
||||
card = page.locator(".skill-card").filter(has=page.get_by_role("heading", name="Updated browser tools", exact=True))
|
||||
expect(card.locator(".skill-tags")).to_have_text("TypeScript")
|
||||
page.get_by_role("button", name="Edit overview", exact=True).click()
|
||||
dialog = page.get_by_role("dialog", name="Edit skills overview", exact=True)
|
||||
dialog.get_by_label("Introduction", exact=True).fill("A toolkit that evolves with my work.")
|
||||
dialog.get_by_label("How I work", exact=False).fill("Keep learning\nShip thoughtfully")
|
||||
dialog.get_by_role("button", name="Save changes", exact=True).click()
|
||||
expect(dialog).to_have_count(0)
|
||||
page.reload()
|
||||
expect(page.locator(".principles-list")).to_contain_text("Ship thoughtfully")
|
||||
page.get_by_role("button", name="Delete Updated browser tools", exact=True).click()
|
||||
page.get_by_role("dialog").get_by_role("button", name="Delete entry", exact=True).click()
|
||||
expect(page.get_by_role("heading", name="Updated browser tools", exact=True)).to_have_count(0)
|
||||
page.set_viewport_size({"width": 390, "height": 844})
|
||||
page.get_by_role("button", name="Add category", exact=True).click()
|
||||
page.get_by_role("dialog").screenshot(path=".venv/skills-editor-mobile.png")
|
||||
assert page.evaluate("document.documentElement.scrollWidth <= innerWidth")
|
||||
page.get_by_role("dialog").get_by_role("button", name="Cancel", exact=True).click()
|
||||
page.get_by_role("button", name="Admin account", exact=True).click()
|
||||
page.get_by_role("button", name="Sign out", exact=True).click()
|
||||
expect(page.get_by_role("button", name="Add category", exact=True)).to_have_count(0)
|
||||
expect(page.locator(".portfolio-item-actions")).to_have_count(0)
|
||||
assert not errors, errors
|
||||
print("Skill category/label CRUD, overview editing, session recovery, mobile layout, and public-only controls passed.", flush=True)
|
||||
browser.close()
|
||||
finally:
|
||||
server.should_exit = True; thread.join(timeout=5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
Reference in New Issue
Block a user