568 lines
23 KiB
Python
568 lines
23 KiB
Python
|
|
from django.conf import settings as django_settings
|
|||
|
|
from selenium import webdriver
|
|||
|
|
from selenium.webdriver.common.by import By
|
|||
|
|
|
|||
|
|
from .base import FunctionalTest, ChannelsFunctionalTest
|
|||
|
|
from .management.commands.create_session import create_pre_authenticated_session
|
|||
|
|
from apps.applets.models import Applet
|
|||
|
|
from apps.epic.models import Room, GateSlot, TableSeat
|
|||
|
|
from apps.lyric.models import User
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _fill_room_via_orm(room, emails):
|
|||
|
|
"""Fill all 6 gate slots and set gate_status=OPEN. Returns list of gamers."""
|
|||
|
|
gamers = []
|
|||
|
|
for i, email in enumerate(emails, start=1):
|
|||
|
|
gamer, _ = User.objects.get_or_create(email=email)
|
|||
|
|
slot = room.gate_slots.get(slot_number=i)
|
|||
|
|
slot.gamer = gamer
|
|||
|
|
slot.status = GateSlot.FILLED
|
|||
|
|
slot.save()
|
|||
|
|
gamers.append(gamer)
|
|||
|
|
room.gate_status = Room.OPEN
|
|||
|
|
room.save()
|
|||
|
|
return gamers
|
|||
|
|
|
|||
|
|
|
|||
|
|
class RoleSelectTest(FunctionalTest):
|
|||
|
|
|
|||
|
|
def setUp(self):
|
|||
|
|
super().setUp()
|
|||
|
|
Applet.objects.get_or_create(
|
|||
|
|
slug="new-game", defaults={"name": "New Game", "context": "gameboard"}
|
|||
|
|
)
|
|||
|
|
Applet.objects.get_or_create(
|
|||
|
|
slug="my-games", defaults={"name": "My Games", "context": "gameboard"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 1 — PICK ROLES dismisses gatekeeper and reveals the table #
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_pick_roles_dismisses_gatekeeper_and_reveals_table(self):
|
|||
|
|
# 1. Founder logs in, creates room via UI, fills remaining slots via ORM
|
|||
|
|
self.create_pre_authenticated_session("founder@test.io")
|
|||
|
|
self.browser.get(self.live_server_url + "/gameboard/")
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.ID, "id_new_game_name")
|
|||
|
|
).send_keys("Dragon's Den")
|
|||
|
|
self.browser.find_element(By.ID, "id_create_game_btn").click()
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.assertIn("/gate/", self.browser.current_url)
|
|||
|
|
)
|
|||
|
|
room_url = self.browser.current_url
|
|||
|
|
room = Room.objects.get(name="Dragon's Den")
|
|||
|
|
|
|||
|
|
# Fill founder's slot via UI (slot 1)
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, "button.token-rails")
|
|||
|
|
).click()
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".btn-confirm")
|
|||
|
|
).click()
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-slot.filled")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Fill slots 2–6 via ORM
|
|||
|
|
emails = ["amigo@test.io", "bud@test.io", "pal@test.io", "dude@test.io", "bro@test.io"]
|
|||
|
|
for i, email in enumerate(emails, start=2):
|
|||
|
|
gamer, _ = User.objects.get_or_create(email=email)
|
|||
|
|
slot = room.gate_slots.get(slot_number=i)
|
|||
|
|
slot.gamer = gamer
|
|||
|
|
slot.status = GateSlot.FILLED
|
|||
|
|
slot.save()
|
|||
|
|
room.gate_status = Room.OPEN
|
|||
|
|
room.save()
|
|||
|
|
|
|||
|
|
# 2. Browser sees the PICK ROLES button (gate is now open)
|
|||
|
|
self.browser.refresh()
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".launch-game-btn")
|
|||
|
|
).click()
|
|||
|
|
|
|||
|
|
# 3. Gatekeeper overlay is gone
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.assertEqual(
|
|||
|
|
len(self.browser.find_elements(By.CSS_SELECTOR, ".gate-overlay")), 0
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 4. Table is visible and prominent
|
|||
|
|
table = self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.ID, "id_game_table")
|
|||
|
|
)
|
|||
|
|
self.assertTrue(table.is_displayed())
|
|||
|
|
|
|||
|
|
# 5. Card stack is present in the table centre
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".card-stack")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 6. Six seat portraits are visible around the table
|
|||
|
|
seats = self.browser.find_elements(By.CSS_SELECTOR, ".table-seat")
|
|||
|
|
self.assertEqual(len(seats), 6)
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 2 — Card stack signals eligibility to each gamer #
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_card_stack_glows_for_first_gamer_only(self):
|
|||
|
|
# Two browsers: founder (slot 1, eligible) and friend (slot 2, not yet)
|
|||
|
|
founder, _ = User.objects.get_or_create(email="founder@test.io")
|
|||
|
|
friend, _ = User.objects.get_or_create(email="friend@test.io")
|
|||
|
|
room = Room.objects.create(name="Signal Test", owner=founder)
|
|||
|
|
_fill_room_via_orm(room, [
|
|||
|
|
"founder@test.io", "friend@test.io",
|
|||
|
|
"bud@test.io", "pal@test.io", "dude@test.io", "bro@test.io",
|
|||
|
|
])
|
|||
|
|
room.table_status = Room.ROLE_SELECT
|
|||
|
|
room.save()
|
|||
|
|
room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/"
|
|||
|
|
|
|||
|
|
# Founder's browser
|
|||
|
|
self.create_pre_authenticated_session("founder@test.io")
|
|||
|
|
self.browser.get(room_url)
|
|||
|
|
stack = self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".card-stack")
|
|||
|
|
)
|
|||
|
|
self.assertIn("eligible", stack.get_attribute("data-state"))
|
|||
|
|
self.assertEqual(
|
|||
|
|
len(self.browser.find_elements(By.CSS_SELECTOR, ".card-stack .fa-ban")), 0
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Friend's browser
|
|||
|
|
self.browser2 = webdriver.Firefox()
|
|||
|
|
try:
|
|||
|
|
self.browser2.get(self.live_server_url + "/404_no_such_url/")
|
|||
|
|
from django.conf import settings
|
|||
|
|
session_key = __import__(
|
|||
|
|
"functional_tests.management.commands.create_session",
|
|||
|
|
fromlist=["create_pre_authenticated_session"]
|
|||
|
|
).create_pre_authenticated_session("friend@test.io")
|
|||
|
|
self.browser2.add_cookie(dict(
|
|||
|
|
name=settings.SESSION_COOKIE_NAME, value=session_key, path="/"
|
|||
|
|
))
|
|||
|
|
self.browser2.get(room_url)
|
|||
|
|
stack2 = self.wait_for(
|
|||
|
|
lambda: self.browser2.find_element(By.CSS_SELECTOR, ".card-stack")
|
|||
|
|
)
|
|||
|
|
self.assertIn("ineligible", stack2.get_attribute("data-state"))
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser2.find_element(
|
|||
|
|
By.CSS_SELECTOR, ".card-stack .fa-ban"
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
finally:
|
|||
|
|
self.browser2.quit()
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 3 — Active gamer fans cards, inspects, selects a role #
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_active_gamer_fans_cards_and_selects_role(self):
|
|||
|
|
founder, _ = User.objects.get_or_create(email="founder@test.io")
|
|||
|
|
room = Room.objects.create(name="Fan Test", owner=founder)
|
|||
|
|
_fill_room_via_orm(room, [
|
|||
|
|
"founder@test.io", "amigo@test.io", "bud@test.io",
|
|||
|
|
"pal@test.io", "dude@test.io", "bro@test.io",
|
|||
|
|
])
|
|||
|
|
room.table_status = Room.ROLE_SELECT
|
|||
|
|
room.save()
|
|||
|
|
room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/"
|
|||
|
|
|
|||
|
|
self.create_pre_authenticated_session("founder@test.io")
|
|||
|
|
self.browser.get(room_url)
|
|||
|
|
|
|||
|
|
# 1. Click the card stack
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".card-stack")
|
|||
|
|
).click()
|
|||
|
|
|
|||
|
|
# 2. Role Select modal opens with 6 cards
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.ID, "id_role_select")
|
|||
|
|
)
|
|||
|
|
cards = self.browser.find_elements(By.CSS_SELECTOR, "#id_role_select .card")
|
|||
|
|
self.assertEqual(len(cards), 6)
|
|||
|
|
|
|||
|
|
# 3. Blur backdrop is present
|
|||
|
|
self.browser.find_element(By.CSS_SELECTOR, ".role-select-backdrop")
|
|||
|
|
|
|||
|
|
# 4. Hover over first card — it flips to reveal front
|
|||
|
|
from selenium.webdriver.common.action_chains import ActionChains
|
|||
|
|
ActionChains(self.browser).move_to_element(cards[0]).perform()
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(
|
|||
|
|
By.CSS_SELECTOR, "#id_role_select .card.flipped"
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 5. Click first card to select it
|
|||
|
|
cards[0].click()
|
|||
|
|
|
|||
|
|
# 6. Modal closes
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.assertEqual(
|
|||
|
|
len(self.browser.find_elements(By.ID, "id_role_select")), 0
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 7. Role card appears in inventory
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(
|
|||
|
|
By.CSS_SELECTOR, "#id_inv_role_card .card"
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 8. Card stack returns to table centre
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".card-stack")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 3b — Chosen role absent from next gamer's fan #
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_chosen_role_absent_from_next_gamer_fan(self):
|
|||
|
|
from apps.epic.models import TableSeat
|
|||
|
|
founder, _ = User.objects.get_or_create(email="founder@test.io")
|
|||
|
|
friend, _ = User.objects.get_or_create(email="friend@test.io")
|
|||
|
|
room = Room.objects.create(name="Pool Test", owner=founder)
|
|||
|
|
_fill_room_via_orm(room, [
|
|||
|
|
"founder@test.io", "friend@test.io",
|
|||
|
|
"bud@test.io", "pal@test.io", "dude@test.io", "bro@test.io",
|
|||
|
|
])
|
|||
|
|
room.table_status = Room.ROLE_SELECT
|
|||
|
|
room.save()
|
|||
|
|
|
|||
|
|
# Simulate pick_roles: create a TableSeat per filled slot
|
|||
|
|
for slot in room.gate_slots.order_by("slot_number"):
|
|||
|
|
TableSeat.objects.create(
|
|||
|
|
room=room, gamer=slot.gamer, slot_number=slot.slot_number,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Slot 1 (founder) has already chosen PC
|
|||
|
|
TableSeat.objects.filter(room=room, slot_number=1).update(role="PC")
|
|||
|
|
|
|||
|
|
# Slot 2 (friend) is now the active gamer
|
|||
|
|
room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/"
|
|||
|
|
self.create_pre_authenticated_session("friend@test.io")
|
|||
|
|
self.browser.get(room_url)
|
|||
|
|
|
|||
|
|
# Card stack is eligible for slot 2
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(
|
|||
|
|
By.CSS_SELECTOR, ".card-stack[data-state='eligible']"
|
|||
|
|
)
|
|||
|
|
).click()
|
|||
|
|
|
|||
|
|
# Fan opens — only 5 cards (PC is taken)
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.ID, "id_role_select")
|
|||
|
|
)
|
|||
|
|
cards = self.browser.find_elements(By.CSS_SELECTOR, "#id_role_select .card")
|
|||
|
|
self.assertEqual(len(cards), 5)
|
|||
|
|
|
|||
|
|
# Specifically, no PC card in the fan
|
|||
|
|
self.assertEqual(
|
|||
|
|
len(self.browser.find_elements(
|
|||
|
|
By.CSS_SELECTOR, "#id_role_select .card[data-role='PC']"
|
|||
|
|
)),
|
|||
|
|
0,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 3c — Card stack stays eligible after re-entering mid-session #
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_card_stack_remains_eligible_after_re_entering_mid_selection(self):
|
|||
|
|
"""A gamer holding multiple slots should still see an eligible card
|
|||
|
|
stack when they re-enter the room after having already chosen a role
|
|||
|
|
for their earlier slot."""
|
|||
|
|
from apps.epic.models import TableSeat
|
|||
|
|
founder, _ = User.objects.get_or_create(email="founder@test.io")
|
|||
|
|
room = Room.objects.create(name="Re-entry Test", owner=founder)
|
|||
|
|
# Founder holds slots 1 and 2; others fill the rest
|
|||
|
|
_fill_room_via_orm(room, [
|
|||
|
|
"founder@test.io", "founder@test.io",
|
|||
|
|
"bud@test.io", "pal@test.io", "dude@test.io", "bro@test.io",
|
|||
|
|
])
|
|||
|
|
room.table_status = Room.ROLE_SELECT
|
|||
|
|
room.save()
|
|||
|
|
|
|||
|
|
for slot in room.gate_slots.order_by("slot_number"):
|
|||
|
|
TableSeat.objects.create(
|
|||
|
|
room=room, gamer=slot.gamer, slot_number=slot.slot_number,
|
|||
|
|
)
|
|||
|
|
# Founder's first slot has already chosen PC
|
|||
|
|
TableSeat.objects.filter(room=room, slot_number=1).update(role="PC")
|
|||
|
|
|
|||
|
|
# Founder re-enters the room (simulating a page reload / re-navigation)
|
|||
|
|
room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/"
|
|||
|
|
self.create_pre_authenticated_session("founder@test.io")
|
|||
|
|
self.browser.get(room_url)
|
|||
|
|
|
|||
|
|
# Card stack must be eligible — slot 2 (also founder's) is the active seat
|
|||
|
|
stack = self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".card-stack")
|
|||
|
|
)
|
|||
|
|
self.assertEqual(stack.get_attribute("data-state"), "eligible")
|
|||
|
|
|
|||
|
|
# Fan shows 5 cards — PC already taken
|
|||
|
|
stack.click()
|
|||
|
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_role_select"))
|
|||
|
|
cards = self.browser.find_elements(By.CSS_SELECTOR, "#id_role_select .card")
|
|||
|
|
self.assertEqual(len(cards), 5)
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 3d — Previously selected roles appear in inventory on re-entry#
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_previously_selected_roles_shown_in_inventory_on_re_entry(self):
|
|||
|
|
"""A multi-slot gamer who already chose some roles should see those
|
|||
|
|
role cards pre-populated in the inventory when they re-enter the room."""
|
|||
|
|
from apps.epic.models import TableSeat
|
|||
|
|
founder, _ = User.objects.get_or_create(email="founder@test.io")
|
|||
|
|
room = Room.objects.create(name="Inventory Re-entry Test", owner=founder)
|
|||
|
|
_fill_room_via_orm(room, [
|
|||
|
|
"founder@test.io", "founder@test.io",
|
|||
|
|
"bud@test.io", "pal@test.io", "dude@test.io", "bro@test.io",
|
|||
|
|
])
|
|||
|
|
room.table_status = Room.ROLE_SELECT
|
|||
|
|
room.save()
|
|||
|
|
|
|||
|
|
for slot in room.gate_slots.order_by("slot_number"):
|
|||
|
|
TableSeat.objects.create(
|
|||
|
|
room=room, gamer=slot.gamer, slot_number=slot.slot_number,
|
|||
|
|
)
|
|||
|
|
# Founder's first slot has already chosen BC
|
|||
|
|
TableSeat.objects.filter(room=room, slot_number=1).update(role="BC")
|
|||
|
|
|
|||
|
|
room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/"
|
|||
|
|
self.create_pre_authenticated_session("founder@test.io")
|
|||
|
|
self.browser.get(room_url)
|
|||
|
|
|
|||
|
|
# Inventory should contain exactly one pre-rendered card for BC
|
|||
|
|
inv_cards = self.wait_for(
|
|||
|
|
lambda: self.browser.find_elements(
|
|||
|
|
By.CSS_SELECTOR, "#id_inv_role_card .card"
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
self.assertEqual(len(inv_cards), 1)
|
|||
|
|
self.assertIn(
|
|||
|
|
"BUILDER",
|
|||
|
|
inv_cards[0].text.upper(),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 4 — Click-away dismisses fan without selecting #
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_click_away_dismisses_card_fan_without_selecting(self):
|
|||
|
|
founder, _ = User.objects.get_or_create(email="founder@test.io")
|
|||
|
|
room = Room.objects.create(name="Dismiss Test", owner=founder)
|
|||
|
|
_fill_room_via_orm(room, [
|
|||
|
|
"founder@test.io", "amigo@test.io", "bud@test.io",
|
|||
|
|
"pal@test.io", "dude@test.io", "bro@test.io",
|
|||
|
|
])
|
|||
|
|
room.table_status = Room.ROLE_SELECT
|
|||
|
|
room.save()
|
|||
|
|
room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/"
|
|||
|
|
|
|||
|
|
self.create_pre_authenticated_session("founder@test.io")
|
|||
|
|
self.browser.get(room_url)
|
|||
|
|
|
|||
|
|
# Open the fan
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".card-stack")
|
|||
|
|
).click()
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.ID, "id_role_select")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Click the backdrop (outside the fan)
|
|||
|
|
self.browser.find_element(By.CSS_SELECTOR, ".role-select-backdrop").click()
|
|||
|
|
|
|||
|
|
# Modal closes; stack still present; inventory still empty
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.assertEqual(
|
|||
|
|
len(self.browser.find_elements(By.ID, "id_role_select")), 0
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
self.browser.find_element(By.CSS_SELECTOR, ".card-stack")
|
|||
|
|
self.assertEqual(
|
|||
|
|
len(self.browser.find_elements(By.CSS_SELECTOR, "#id_inv_role_card .card")),
|
|||
|
|
0
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 7 — All roles revealed simultaneously after all gamers select #
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_roles_revealed_simultaneously_after_all_select(self):
|
|||
|
|
founder, _ = User.objects.get_or_create(email="founder@test.io")
|
|||
|
|
room = Room.objects.create(name="Reveal Test", owner=founder)
|
|||
|
|
_fill_room_via_orm(room, [
|
|||
|
|
"founder@test.io", "amigo@test.io", "bud@test.io",
|
|||
|
|
"pal@test.io", "dude@test.io", "bro@test.io",
|
|||
|
|
])
|
|||
|
|
room.table_status = Room.ROLE_SELECT
|
|||
|
|
room.save()
|
|||
|
|
room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/"
|
|||
|
|
|
|||
|
|
self.create_pre_authenticated_session("founder@test.io")
|
|||
|
|
self.browser.get(room_url)
|
|||
|
|
|
|||
|
|
# Assign all roles via ORM (simulating all gamers having chosen)
|
|||
|
|
from apps.epic.models import TableSeat
|
|||
|
|
roles = ["PC", "BC", "SC", "AC", "NC", "EC"]
|
|||
|
|
for i, slot in enumerate(room.gate_slots.order_by("slot_number")):
|
|||
|
|
TableSeat.objects.create(
|
|||
|
|
room=room,
|
|||
|
|
gamer=slot.gamer,
|
|||
|
|
slot_number=slot.slot_number,
|
|||
|
|
role=roles[i],
|
|||
|
|
role_revealed=True,
|
|||
|
|
)
|
|||
|
|
room.table_status = Room.SIG_SELECT
|
|||
|
|
room.save()
|
|||
|
|
|
|||
|
|
self.browser.refresh()
|
|||
|
|
|
|||
|
|
# All role cards in inventory are face-up
|
|||
|
|
face_up_cards = self.wait_for(
|
|||
|
|
lambda: self.browser.find_elements(
|
|||
|
|
By.CSS_SELECTOR, "#id_inv_role_card .card.face-up"
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
self.assertGreater(len(face_up_cards), 0)
|
|||
|
|
|
|||
|
|
# Partner indicator is visible
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".partner-indicator")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class RoleSelectChannelsTest(ChannelsFunctionalTest):
|
|||
|
|
|
|||
|
|
def setUp(self):
|
|||
|
|
super().setUp()
|
|||
|
|
Applet.objects.get_or_create(
|
|||
|
|
slug="new-game", defaults={"name": "New Game", "context": "gameboard"}
|
|||
|
|
)
|
|||
|
|
Applet.objects.get_or_create(
|
|||
|
|
slug="my-games", defaults={"name": "My Games", "context": "gameboard"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 6 — Observer sees seat arc move via WebSocket #
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_observer_sees_seat_arc_during_selection(self):
|
|||
|
|
founder, _ = User.objects.get_or_create(email="founder@test.io")
|
|||
|
|
User.objects.get_or_create(email="watcher@test.io")
|
|||
|
|
room = Room.objects.create(name="Arc Test", owner=founder)
|
|||
|
|
_fill_room_via_orm(room, [
|
|||
|
|
"founder@test.io", "watcher@test.io",
|
|||
|
|
"bud@test.io", "pal@test.io", "dude@test.io", "bro@test.io",
|
|||
|
|
])
|
|||
|
|
room.table_status = Room.ROLE_SELECT
|
|||
|
|
room.save()
|
|||
|
|
for slot in room.gate_slots.order_by("slot_number"):
|
|||
|
|
TableSeat.objects.create(
|
|||
|
|
room=room, gamer=slot.gamer, slot_number=slot.slot_number,
|
|||
|
|
)
|
|||
|
|
room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/"
|
|||
|
|
|
|||
|
|
# 1. Watcher loads the room — slot 1 is active on initial render
|
|||
|
|
self.create_pre_authenticated_session("watcher@test.io")
|
|||
|
|
self.browser.get(room_url)
|
|||
|
|
self.wait_for(lambda: self.browser.find_element(
|
|||
|
|
By.CSS_SELECTOR, ".table-seat.active[data-slot='1']"
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
# 2. Founder picks a role in second browser
|
|||
|
|
self.browser2 = self._make_browser2("founder@test.io")
|
|||
|
|
try:
|
|||
|
|
self.browser2.get(room_url)
|
|||
|
|
self.wait_for(lambda: self.browser2.find_element(
|
|||
|
|
By.CSS_SELECTOR, ".card-stack[data-state='eligible']"
|
|||
|
|
))
|
|||
|
|
self.browser2.find_element(By.CSS_SELECTOR, ".card-stack").click()
|
|||
|
|
self.wait_for(lambda: self.browser2.find_element(By.ID, "id_role_select"))
|
|||
|
|
self.browser2.find_element(By.CSS_SELECTOR, "#id_role_select .card").click()
|
|||
|
|
|
|||
|
|
# 3. Watcher's seat arc moves to slot 2 — no page refresh
|
|||
|
|
self.wait_for(lambda: self.browser.find_element(
|
|||
|
|
By.CSS_SELECTOR, ".table-seat.active[data-slot='2']"
|
|||
|
|
))
|
|||
|
|
self.assertEqual(
|
|||
|
|
len(self.browser.find_elements(
|
|||
|
|
By.CSS_SELECTOR, ".table-seat.active[data-slot='1']"
|
|||
|
|
)),
|
|||
|
|
0,
|
|||
|
|
)
|
|||
|
|
finally:
|
|||
|
|
self.browser2.quit()
|
|||
|
|
|
|||
|
|
def _make_browser2(self, email):
|
|||
|
|
"""Spin up a second Firefox, authenticate email, return the browser."""
|
|||
|
|
session_key = create_pre_authenticated_session(email)
|
|||
|
|
b = webdriver.Firefox()
|
|||
|
|
b.get(self.live_server_url + "/404_no_such_url/")
|
|||
|
|
b.add_cookie(dict(
|
|||
|
|
name=django_settings.SESSION_COOKIE_NAME,
|
|||
|
|
value=session_key,
|
|||
|
|
path="/",
|
|||
|
|
))
|
|||
|
|
return b
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
# Test 5 — Turn passes to next gamer via WebSocket after selection #
|
|||
|
|
# ------------------------------------------------------------------ #
|
|||
|
|
|
|||
|
|
def test_turn_passes_after_selection(self):
|
|||
|
|
founder, _ = User.objects.get_or_create(email="founder@test.io")
|
|||
|
|
User.objects.get_or_create(email="friend@test.io")
|
|||
|
|
room = Room.objects.create(name="Turn Test", owner=founder)
|
|||
|
|
_fill_room_via_orm(room, [
|
|||
|
|
"founder@test.io", "friend@test.io",
|
|||
|
|
"bud@test.io", "pal@test.io", "dude@test.io", "bro@test.io",
|
|||
|
|
])
|
|||
|
|
room.table_status = Room.ROLE_SELECT
|
|||
|
|
room.save()
|
|||
|
|
for slot in room.gate_slots.order_by("slot_number"):
|
|||
|
|
TableSeat.objects.create(
|
|||
|
|
room=room, gamer=slot.gamer, slot_number=slot.slot_number,
|
|||
|
|
)
|
|||
|
|
room_url = f"{self.live_server_url}/gameboard/room/{room.id}/gate/"
|
|||
|
|
|
|||
|
|
# 1. Founder (slot 1) — eligible
|
|||
|
|
self.create_pre_authenticated_session("founder@test.io")
|
|||
|
|
self.browser.get(room_url)
|
|||
|
|
self.wait_for(lambda: self.browser.find_element(
|
|||
|
|
By.CSS_SELECTOR, ".card-stack[data-state='eligible']"
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
# 2. Friend (slot 2) — ineligible in second browser
|
|||
|
|
self.browser2 = self._make_browser2("friend@test.io")
|
|||
|
|
try:
|
|||
|
|
self.browser2.get(room_url)
|
|||
|
|
self.wait_for(lambda: self.browser2.find_element(
|
|||
|
|
By.CSS_SELECTOR, ".card-stack[data-state='ineligible']"
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
# 3. Founder picks a role
|
|||
|
|
self.browser.find_element(By.CSS_SELECTOR, ".card-stack").click()
|
|||
|
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_role_select"))
|
|||
|
|
self.browser.find_element(By.CSS_SELECTOR, "#id_role_select .card").click()
|
|||
|
|
|
|||
|
|
# 4. Friend's stack becomes eligible via WebSocket — no page refresh
|
|||
|
|
self.wait_for(lambda: self.browser2.find_element(
|
|||
|
|
By.CSS_SELECTOR, ".card-stack[data-state='eligible']"
|
|||
|
|
))
|
|||
|
|
finally:
|
|||
|
|
self.browser2.quit()
|