Something went wrong. Try again.
Paper Mario atproto mod hosting site starhaven.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138"""What the end-to-end tests act on: an actor using the app."""
from dataclasses import dataclass
import requestsfrom playwright.sync_api import Page, expect
@dataclass(frozen=True)class Actor: """An account on the dev PDS, and a browser to act through."""
page: Page app_url: str pds_url: str handle: str password: str did: str
def login(self) -> None: self.page.goto(f"{self.app_url}/login") self.page.get_by_label("Internet handle").fill(self.handle) self.page.get_by_role("button", name="Login").click()
# PDS sign in self.page.get_by_role("textbox", name="Password").fill(self.password) self.page.get_by_role("button", name="Sign in").click()
# The Authorize button starts disabled while the PDS fetches and # validates client metadata; wait for it to be ready before clicking. authorize_btn = self.page.get_by_role("button", name="Authorize") expect(authorize_btn).to_be_enabled(timeout=15000) authorize_btn.click()
expect(self.page).to_have_url(f"{self.app_url}/")
def create_mod( self, slug: str, title: str = "Test Mod", game: str = "Paper Mario", ) -> None: """Creates a mod listing, leaving the page on the new mod.
`slug` must match what the title slugifies to - the create form derives it rather than asking for it. """ self.page.goto(f"{self.app_url}/create") self.page.get_by_label("Title").fill(title) self.page.get_by_label(game, exact=True).check() self.page.get_by_role("button", name="Create mod").click()
expect(self.page).to_have_url(f"{self.app_url}/{self.handle}/{slug}")
def edit_mod(self, slug: str, title: str) -> None: """Edits a mod listing's title from its settings page.
Leaves the page on the mod itself, where saving redirects to. """ self.page.goto(f"{self.app_url}/{self.handle}/{slug}/settings") self.page.get_by_label("Title").fill(title) self.page.get_by_role("button", name="Save").click()
expect(self.page).to_have_url(f"{self.app_url}/{self.handle}/{slug}")
def delete_mod(self, slug: str) -> None: """Deletes one of the actor's mods, leaving the page on the mod list.""" self.page.goto(f"{self.app_url}/{self.handle}/{slug}/settings") self.page.get_by_role("button", name="Delete this mod forever").click()
expect(self.page).to_have_url(f"{self.app_url}/")
def submit_version( self, slug: str, name: str, description: str, deliverable: bytes = b"a patch file", filename: str = "mod.bps", goto: bool = True, ) -> None: """Fills in and submits the version form, without waiting for a result.
Separate from `publish_version` so a test can submit a version it expects to be refused. Pass `goto=False` when already on the tab and how you got there is the point. """ if goto: self.page.goto(f"{self.app_url}/{self.handle}/{slug}/versions") self.page.get_by_role("button", name="Upload a new version").click() self.page.get_by_label("Version", exact=True).fill(name) self.page.get_by_label("What changed").fill(description) self.page.get_by_label("File").set_input_files( files=[ { "name": filename, "mimeType": "application/octet-stream", "buffer": deliverable, } ] ) self.page.get_by_role("button", name="Publish version").click()
def publish_version( self, slug: str, name: str, description: str, deliverable: bytes = b"a patch file", filename: str = "mod.bps", goto: bool = True, ) -> None: """Publishes a version of one of the actor's mods.
The file never touches the appview: the browser uploads it to object storage using a signed URL, so this waits for the page to come back rather than for a form post to return. """ self.submit_version(slug, name, description, deliverable, filename, goto)
expect(self.page.get_by_role("heading", name=name)).to_be_visible()
def version_error(self) -> str: """The message summarized at the top of the form when one is refused.""" error = self.page.locator("#version-dialog-summary") expect(error).to_be_visible() return error.inner_text()
def records(self, collection: str) -> list[dict]: """Records in the actor's repo, read from the PDS, not the appview.""" response = requests.get( f"{self.pds_url}/xrpc/com.atproto.repo.listRecords", params={"repo": self.did, "collection": collection}, timeout=5, ) response.raise_for_status() return response.json()["records"]