# retoor <retoor@molodetz.nl>
from datetime import datetime, timedelta, timezone
from playwright.sync_api import expect
from tests.conftest import BASE_URL
def reset_farm(username, coins=100000):
from devplacepy.database import get_table
from devplacepy.services.game import store
user = get_table("users").find_one(username=username)
uid = user["uid"]
farm = store.get_farm(uid)
if farm:
get_table("game_plots").delete(farm_uid=farm["uid"])
get_table("game_quests").delete(farm_uid=farm["uid"])
get_table("game_farms").delete(uid=farm["uid"])
get_table("game_cosmetics").delete(user_uid=uid)
get_table("game_steals").delete(thief_uid=uid)
get_table("game_steals").delete(owner_uid=uid)
get_table("game_market_ticks").delete()
clear_game_caches()
farm = store.ensure_farm(uid)
get_table("game_farms").update({"uid": farm["uid"], "coins": coins}, ["uid"])
return {"uid": uid, "username": username}
def warp_ready(username):
from devplacepy.database import get_table
from devplacepy.services.game import store
user = get_table("users").find_one(username=username)
farm = store.get_farm(user["uid"])
past = (datetime.now(timezone.utc) - timedelta(seconds=5)).isoformat()
for plot in store.get_plots(farm["uid"]):
if plot.get("crop_key"):
get_table("game_plots").update(
{"uid": plot["uid"], "ready_at": past}, ["uid"]
)
def set_farm_xp(username, xp):
from devplacepy.database import get_table
from devplacepy.services.game import store
user = get_table("users").find_one(username=username)
farm = store.get_farm(user["uid"])
get_table("game_farms").update({"uid": farm["uid"], "xp": xp}, ["uid"])
def set_farm(username, **fields):
from devplacepy.database import get_table
from devplacepy.services.game import store
user = get_table("users").find_one(username=username)
farm = store.get_farm(user["uid"])
get_table("game_farms").update({"uid": farm["uid"], **fields}, ["uid"])
def clear_game_caches():
from devplacepy.services.game.store import farm as farm_store
from devplacepy.services.game.store import market as market_store
farm_store._leaderboard_cache.clear()
farm_store._board_cache.clear()
market_store._saturation_cache.clear()
def open_game(page):
page.goto(f"{BASE_URL}/game", wait_until="domcontentloaded")
page.locator("[data-game-grid]").first.wait_for(state="visible")
page.wait_for_timeout(800)
def coins(page):
return int(page.locator("[data-hud-coins]").inner_text())
def test_game_page_loads(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
assert page.locator(".game-header h1").is_visible()
expect(page.locator("[data-hud-coins]")).to_have_text("100000")
expect(page.locator("[data-game-grid] [data-slot]")).to_have_count(4)
expect(page.locator("[data-perk-host] .perk-card")).to_have_count(4)
expect(page.locator(".quest-item")).to_have_count(3)
def test_guest_redirected_to_login(page):
page.goto(f"{BASE_URL}/game", wait_until="domcontentloaded")
page.wait_for_timeout(500)
assert "/auth/login" in page.url
def test_plant_crop(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
form = page.locator("form[data-game-action='plant']").first
form.locator("select[name='crop']").select_option("shell")
form.locator("button").click()
expect(page.locator(".game-plot-growing")).to_have_count(1)
expect(page.locator("[data-hud-coins]")).to_have_text("99995")
def test_harvest_crop(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
form = page.locator("form[data-game-action='plant']").first
form.locator("select[name='crop']").select_option("shell")
form.locator("button").click()
expect(page.locator(".game-plot-growing")).to_have_count(1)
warp_ready("alice_test")
open_game(page)
page.locator("form[data-game-action='harvest'] button").first.click()
expect(page.locator("[data-hud-coins]")).to_have_text("100006")
expect(page.locator(".game-plot-growing")).to_have_count(0)
def test_fertilize_spends_coins(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
form = page.locator("form[data-game-action='plant']").first
form.locator("select[name='crop']").select_option("python")
form.locator("button").click()
expect(page.locator("[data-hud-coins]")).to_have_text("99985")
fert = page.locator("form[data-game-action='fertilize'] button").first
fert.wait_for(state="visible")
fert.click()
expect(page.locator("[data-hud-coins]")).not_to_have_text("99985")
assert coins(page) < 99985
def test_buy_plot(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
page.locator("form[data-game-action='buy-plot'] button").click()
expect(page.locator("[data-game-grid] [data-slot]")).to_have_count(5)
expect(page.locator("[data-hud-coins]")).to_have_text("99900")
def test_upgrade_ci(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
page.locator("form[data-game-action='upgrade'] button").click()
expect(page.locator("[data-hud-ci]")).to_have_text("2")
expect(page.locator("[data-hud-coins]")).to_have_text("99850")
def test_claim_daily(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
page.locator("form[data-game-action='daily'] button").click()
expect(page.locator("[data-hud-coins]")).to_have_text("100020")
expect(page.locator("[data-daily-host] .daily-claimed")).to_be_visible()
def test_upgrade_perk(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
growth = page.locator(
"form[data-game-action='perk']:has(input[value='growth']) button"
)
growth.click()
expect(page.locator("[data-hud-coins]")).to_have_text("99850")
expect(
page.locator(".perk-card:has-text('Build Cache') .perk-level")
).to_have_text("Lv 1/10")
def _set_farm_stars(username, stars):
from devplacepy.database import get_table
from devplacepy.services.game import store
user = get_table("users").find_one(username=username)
farm = store.get_farm(user["uid"])
get_table("game_farms").update({"uid": farm["uid"], "stars": stars}, ["uid"])
def test_upgrade_legacy_spends_stars(alice):
page, _ = alice
reset_farm("alice_test")
_set_farm_stars("alice_test", 1000)
open_game(page)
buy = page.locator(
"form[data-game-action='legacy']:has(input[value='multiplier']) button"
)
buy.wait_for(state="visible")
buy.click()
expect(
page.locator(".legacy-card:has-text('Tech Debt Payoff') .perk-level")
).to_have_text("Lv 1/10")
def test_claim_quest(alice):
page, _ = alice
from devplacepy.database import get_table
from devplacepy.services.game import store
farm_info = reset_farm("alice_test")
open_game(page)
farm = store.get_farm(farm_info["uid"])
quest = list(get_table("game_quests").find(farm_uid=farm["uid"]))[0]
get_table("game_quests").update(
{"uid": quest["uid"], "progress": quest["goal"]}, ["uid"]
)
open_game(page)
page.locator("form[data-game-action='claim-quest'] button").first.click()
expect(page.locator("[data-hud-coins]")).not_to_have_text("100000")
assert coins(page) > 100000
def test_prestige_hidden_below_level(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
assert page.locator("form[data-game-action='prestige']").count() == 0
assert page.is_visible("text=Reach level 10 to refactor")
def test_prestige_resets_farm(alice):
page, _ = alice
from devplacepy.services.game import economy
reset_farm("alice_test")
set_farm_xp("alice_test", economy.xp_threshold(economy.PRESTIGE_MIN_LEVEL))
fee = economy.refactor_cost(0, 100000)
carried = economy.refactor_carryover(100000, fee, 0)
open_game(page)
prestige = page.locator("form[data-game-action='prestige'] button")
prestige.wait_for(state="visible")
prestige.click()
confirm_btn = page.locator(".dialog-confirm")
confirm_btn.wait_for(state="visible")
confirm_btn.click()
expect(page.locator("[data-hud-prestige]")).to_have_text("1")
expect(page.locator("[data-hud-coins]")).to_have_text(
str(economy.STARTING_COINS + carried)
)
assert page.locator("form[data-game-action='prestige']").count() == 0
def test_prestige_unaffordable_shows_cost_without_button(alice):
page, _ = alice
from devplacepy.services.game import economy
reset_farm("alice_test", coins=100)
set_farm_xp("alice_test", economy.xp_threshold(economy.PRESTIGE_MIN_LEVEL))
open_game(page)
assert page.locator("form[data-game-action='prestige']").count() == 0
assert page.is_visible("text=keep farming to afford it")
def test_grant_section_claim(alice):
page, _ = alice
from devplacepy.database import get_table
from devplacepy.services.game import economy, store
from devplacepy.services.game.store import _iso_week, _now
reset_farm("alice_test", coins=100)
store.ensure_treasury()
get_table("game_treasury").update(
{"uid": "treasury-main", "balance": 100000}, ["uid"]
)
set_farm(
"alice_test",
harvests_week=economy.GRANT_MIN_WEEK_HARVESTS,
harvests_week_start=_iso_week(_now()),
)
open_game(page)
claim = page.locator("form[data-game-action='grant'] button")
claim.wait_for(state="visible")
claim.click()
expect(page.locator("[data-hud-coins]")).to_have_text(
str(100 + economy.GRANT_CAP)
)
assert page.locator("form[data-game-action='grant']").count() == 0
def test_leaderboard_panel_populates(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
page.locator(".game-lb-row").first.wait_for(state="visible")
assert page.is_visible("a.game-lb-name:has-text('alice_test')")
def test_infrastructure_hidden_below_prestige_requirement(alice):
page, _ = alice
reset_farm("alice_test", coins=100_000_000)
open_game(page)
assert page.is_visible("text=Requires prestige 3")
assert page.locator("form[data-game-action='infrastructure']").count() == 0
def test_infrastructure_buy_succeeds_when_eligible(alice):
page, _ = alice
reset_farm("alice_test", coins=100_000_000)
set_farm("alice_test", prestige=5)
open_game(page)
buy = page.locator("form[data-game-action='infrastructure'] button").first
buy.wait_for(state="visible")
buy.click()
confirm_btn = page.locator(".dialog-confirm")
confirm_btn.wait_for(state="visible")
confirm_btn.click()
expect(page.locator("form[data-game-action='infrastructure']")).to_have_count(0)
assert coins(page) == 100_000_000 - 25_000_000
def test_defense_upgrade_spends_coins(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
upgrade = page.locator("form[data-game-action='defense'] button").first
upgrade.wait_for(state="visible")
upgrade.click()
expect(page.locator("[data-hud-coins]")).to_have_text("95000")
def test_cosmetics_buy_and_equip_flow(alice):
page, _ = alice
reset_farm("alice_test", coins=1_000_000)
open_game(page)
buy = page.locator("form[data-game-action='cosmetic-buy']").first
buy.locator("button").wait_for(state="visible")
buy.locator("button").click()
confirm_btn = page.locator(".dialog-confirm")
confirm_btn.wait_for(state="visible")
confirm_btn.click()
equip = page.locator("form[data-game-action='cosmetic-equip'] button").first
equip.wait_for(state="visible")
equip.click()
expect(page.locator(".game-cosmetics")).to_contain_text("Equipped")
def test_mastery_panel_hidden_without_mastery_points(alice):
page, _ = alice
reset_farm("alice_test")
open_game(page)
assert page.locator(".game-mastery").count() == 0
def test_mastery_panel_visible_and_functional_once_unlocked(alice):
page, _ = alice
reset_farm("alice_test")
set_farm("alice_test", mastery_points=1000, mastery_points_earned_total=1)
open_game(page)
upgrade = page.locator(
"form[data-game-action='mastery']:has(input[value='autoreplant']) button"
)
upgrade.wait_for(state="visible")
upgrade.click()
expect(
page.locator(".perk-card:has-text('Continuous Delivery') .perk-level")
).to_have_text("Lv 1/1")
def test_leaderboard_board_selector_switches_boards(alice):
page, _ = alice
reset_farm("alice_test")
set_farm("alice_test", prestige=7)
open_game(page)
page.locator(".game-lb-row").first.wait_for(state="visible")
page.select_option("[data-lb-board]", "prestige")
page.wait_for_timeout(400)
page.locator(".game-lb-row").first.wait_for(state="visible")
assert page.is_visible("a.game-lb-name:has-text('alice_test')")
def test_underdog_banner_shows_after_raiding_a_much_richer_farm(bob):
page, _ = bob
from devplacepy.database import get_table
from devplacepy.services.game import store
reset_farm("alice_test", coins=1_000_000)
reset_farm("bob_test", coins=0)
alice_user = get_table("users").find_one(username="alice_test")
bob_user = get_table("users").find_one(username="bob_test")
store.plant({"uid": alice_user["uid"], "username": "alice_test"}, 0, "shell")
past = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat()
farm = store.get_farm(alice_user["uid"])
plot = store._plot_at(farm["uid"], 0)
get_table("game_plots").update(
{"uid": plot["uid"], "planted_at": past, "ready_at": past}, ["uid"]
)
store.steal({"uid": bob_user["uid"], "username": "bob_test"}, alice_user, 0)
page.goto(f"{BASE_URL}/game", wait_until="domcontentloaded")
page.locator("[data-underdog-banner]").wait_for(state="visible")
def test_weekly_contract_appears_and_can_be_claimed(alice):
page, _ = alice
from devplacepy.database import get_table
from devplacepy.services.game import store
from devplacepy.services.game.store.common import _iso_week
from devplacepy.services.game.store.quests import ensure_weekly_contract
reset_farm("alice_test")
set_farm("alice_test", mastery_points=1000)
user = get_table("users").find_one(username="alice_test")
store.upgrade_mastery({"uid": user["uid"], "username": "alice_test"}, "contracts")
farm = store.get_farm(user["uid"])
iso_week = _iso_week(datetime.now(timezone.utc))
ensure_weekly_contract(farm, iso_week)
row = get_table("game_quests").find_one(
farm_uid=farm["uid"], day=iso_week, scope="weekly"
)
get_table("game_quests").update(
{"uid": row["uid"], "progress": row["goal"]}, ["uid"]
)
open_game(page)
claim = page.locator(
"form[data-game-action='claim-quest']:has(input[value='weekly']) button"
)
claim.wait_for(state="visible")
claim.click()
page.wait_for_timeout(400)
farm = store.get_farm(user["uid"])
assert int(farm["stars"]) > 0
def test_crop_shows_saturated_label_when_overfarmed(alice):
page, _ = alice
from devplacepy.database import get_table
from devplacepy.services.game.store import market as market_store
from devplacepy.utils import generate_uid
reset_farm("alice_test")
now = datetime.now(timezone.utc)
get_table("game_market_ticks").insert(
{
"uid": generate_uid(),
"crop_key": "shell",
"hour_bucket": market_store._hour_bucket(now),
"harvests": 600,
"updated_at": now.isoformat(),
}
)
open_game(page)
option = page.locator("form[data-game-action='plant'] select option[value='shell']").first
option.wait_for(state="attached")
deadline = datetime.now(timezone.utc) + timedelta(seconds=40)
while "saturated" not in option.inner_text() and datetime.now(timezone.utc) < deadline:
page.wait_for_timeout(2000)
page.reload(wait_until="domcontentloaded")
page.locator("[data-game-grid]").first.wait_for(state="visible")
option = page.locator(
"form[data-game-action='plant'] select option[value='shell']"
).first
option.wait_for(state="attached")
assert "saturated" in option.inner_text()
def test_mobile_no_horizontal_overflow(mobile_page):
page, _ = mobile_page
reset_farm("alice_test")
open_game(page)
scroll_w = page.evaluate("document.documentElement.scrollWidth")
client_w = page.evaluate("document.documentElement.clientWidth")
assert scroll_w - client_w <= 1, f"horizontal overflow: {scroll_w} vs {client_w}"
assert page.locator("[data-game-grid]").first.is_visible()