functional_tests + CI: rename pass + structural consolidations + parallel test-FTs split — every FT file now starts with one of 6 prefixes (test_admin_* / test_bill_* / test_core_* / test_dash_* / test_game_room_* / test_trinket_*) plus the 4 page-roots test_billboard / test_dashboard / test_gameboard / test_jasmine, so the partition is unambiguous and stable for tooling (the previous mix of test_applet_*, test_room_*, test_component_*, ad-hoc names had no consistent grouping); session-side: merged test_gatekeeper_bud_btn.py into test_bud_btn.py (then user renamed to test_core_bud_btn.py) — both files drove the same #id_bud_btn UI in two contexts (post-share + gatekeeper invite) and shared the bud-btn.js skeleton, so consolidation was overdue; split test_component_cards_tarot.py into test_admin_tarot.py (just TarotAdminTest, sitting next to test_admin / test_admin_post_readonly) + 3 classes (TarotDeckTest / GameKitDeckSelectionTest / GameKitPageTest) appended to test_game_room_tray.py; updated stale test_bud_btn.py references in the test_core_bud_btn.py docstring + test_admin_post_readonly.py comment to point at the new filename; user-driven renames (22 files): test_applet_my_notes/posts → test_bill_my_*, test_applet_new_post[_line_validation] → test_bill_new_post[_line_validation], test_applet_my_sky → test_dash_my_sky, test_applet_palette → test_dash_palette, test_wallet → test_dash_wallet, test_login → test_core_login, test_navbar → test_core_navbar, test_sharing → test_core_sharing, test_layout_and_styling → test_core_styling, test_my_buds → test_bill_my_buds, test_bud_btn → test_core_bud_btn, test_deck_contribution → test_game_room_deck_contrib, test_game_invite → test_game_room_invite, test_room_gatekeeper → test_game_room_gatekeeper, test_room_role_select → test_game_room_select_role, test_room_sea_select → test_game_room_select_sea, test_room_sig_select → test_game_room_select_sig, test_room_sky_select → test_game_room_select_sky, test_room_tray → test_game_room_tray, test_component_tray_tooltip → test_game_room_tray_tooltip; the post_page.py / room_page.py helper modules from the May-12 sprint absorbed the cross-file FT imports that would otherwise have cascade-broken on these renames
.woodpecker/main.yaml — CI test-FTs step splits into parallel siblings test-FTs-non-room (22 files via `ls functional_tests/test_*.py | grep -v 'test_game_room_'`) + test-FTs-room (9 files via `ls functional_tests/test_game_room_*.py`); room cluster is the heaviest (~70% of the pre-split ~40-min wall-clock) and now runs concurrently w. the rest instead of in series; DAG explicit via depends_on on every step (Woodpecker mixes default-sequential w. depends_on awkwardly, so each step pins its prerequisite); collectstatic stays in test-two-browser-FTs only — the shared workspace propagates assets to both parallel FT steps, no race + no duplication; screendumps + build-and-push fan back in (depends_on both parallel steps); deploy-staging + deploy-prod depend on build-and-push smoke-import: 31/31 FT modules green after the rename pass Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
198
src/functional_tests/test_dash_wallet.py
Normal file
198
src/functional_tests/test_dash_wallet.py
Normal file
@@ -0,0 +1,198 @@
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
from .base import FunctionalTest
|
||||
from apps.applets.models import Applet
|
||||
|
||||
|
||||
class WalletDisplayTest(FunctionalTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
Applet.objects.get_or_create(slug="wallet", defaults={"name": "Wallet"})
|
||||
for slug, name, cols, rows in [
|
||||
("wallet-balances", "Wallet Balances", 3, 3),
|
||||
("wallet-tokens", "Wallet Tokens", 3, 3),
|
||||
("wallet-payment", "Payment Methods", 6, 3),
|
||||
]:
|
||||
Applet.objects.get_or_create(
|
||||
slug=slug,
|
||||
defaults={"name": name, "grid_cols": cols, "grid_rows": rows, "context": "wallet"},
|
||||
)
|
||||
|
||||
def test_new_user_wallet_shows_starting_balances(self):
|
||||
# 1. Log in as new user
|
||||
self.create_pre_authenticated_session("capman@test.io")
|
||||
# 2. Navigate to dashboard
|
||||
self.browser.get(self.live_server_url)
|
||||
# 3. Find wallet applet summary card
|
||||
self.wait_for(
|
||||
lambda: self.browser.find_element(By.ID, "id_applet_wallet")
|
||||
)
|
||||
# 4. Click thru to full wallet page via manage link
|
||||
self.browser.find_element(
|
||||
By.CSS_SELECTOR, "#id_applet_wallet a.wallet-manage-link"
|
||||
).click()
|
||||
# 5. Assert user landed on wallet page
|
||||
self.wait_for(
|
||||
lambda: self.assertRegex(self.browser.current_url, r"/dashboard/wallet/$")
|
||||
)
|
||||
# 6. Assert writs balance shows 144 (complimentary bundle on signup)
|
||||
self.wait_for(
|
||||
lambda: self.assertEqual(
|
||||
"144",
|
||||
self.browser.find_element(By.ID, "id_writs_balance").text,
|
||||
)
|
||||
)
|
||||
# 7. Assert esteem balance shows 0
|
||||
self.assertEqual(
|
||||
"0",
|
||||
self.browser.find_element(By.ID, "id_esteem_balance").text,
|
||||
)
|
||||
# 8. Assert Coin-on-a-String token element present
|
||||
coin = self.browser.find_element(By.ID, "id_coin_on_a_string")
|
||||
# 9. Hover over it; assert tooltip appears w. name, entry text, 'no expiry'
|
||||
ActionChains(self.browser).move_to_element(coin).perform()
|
||||
self.wait_for(
|
||||
lambda: self.assertTrue(
|
||||
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
|
||||
)
|
||||
)
|
||||
coin_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text
|
||||
self.assertIn("Coin-on-a-String", coin_tooltip)
|
||||
self.assertIn("Admit 1 Entry", coin_tooltip)
|
||||
self.assertIn("no expiry", coin_tooltip)
|
||||
# 10. Assert ×1 Free Token present (complimentary on signup)
|
||||
free_token = self.browser.find_element(By.ID, "id_free_token")
|
||||
# 11. Hover over it; assert tooltip shows name, entry text, expiry date
|
||||
ActionChains(self.browser).move_to_element(free_token).perform()
|
||||
self.wait_for(
|
||||
lambda: self.assertTrue(
|
||||
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
|
||||
)
|
||||
)
|
||||
free_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text
|
||||
self.assertIn("Free Token", free_tooltip)
|
||||
self.assertIn("Admit 1 Entry", free_tooltip)
|
||||
self.assertIn("Expires", free_tooltip)
|
||||
|
||||
def test_wallet_payment_section_renders(self):
|
||||
# 1. Log in, navigate directly to wallet page
|
||||
self.create_pre_authenticated_session("capman@test.io")
|
||||
self.browser.get(self.live_server_url + "/dashboard/wallet/")
|
||||
# 2. Assert saved payment methods section present
|
||||
self.wait_for(
|
||||
lambda: self.browser.find_element(By.ID, "id_payment_methods")
|
||||
)
|
||||
# 3. Assert the add-payment-method button is visible
|
||||
self.browser.find_element(By.ID, "id_add_payment_method")
|
||||
# 4. Assert Stripe Payment Element mount point exists
|
||||
self.browser.find_element(By.ID, "id_stripe_payment_element")
|
||||
|
||||
def test_user_can_save_a_payment_method(self):
|
||||
# 1. Log in, navigate to wallet page
|
||||
self.create_pre_authenticated_session("capman@test.io")
|
||||
self.browser.get(self.live_server_url + "/dashboard/wallet/")
|
||||
# 2. Click add-payment-method btn
|
||||
self.browser.find_element(By.ID, "id_add_payment_method").click()
|
||||
# 3. Wait for Stripe Payment Element iframe to appear inside mount point
|
||||
self.wait_for(
|
||||
lambda: self.browser.find_element(
|
||||
By.CSS_SELECTOR, "#id_stripe_payment_element iframe"
|
||||
)
|
||||
)
|
||||
# 4. Switch into Stripe iframe to interact w. card fields
|
||||
stripe_frame = self.browser.find_element(
|
||||
By.CSS_SELECTOR, "#id_stripe_payment_element iframe"
|
||||
)
|
||||
self.browser.switch_to.frame(stripe_frame)
|
||||
# 4a. Wait for card inputs to render inside iframe
|
||||
self.wait_for(
|
||||
lambda: self.browser.find_element(
|
||||
By.CSS_SELECTOR, 'input[placeholder="1234 1234 1234 1234"]'
|
||||
)
|
||||
)
|
||||
# 5. Fill in Stripe test card details
|
||||
self.browser.find_element(
|
||||
By.CSS_SELECTOR, 'input[placeholder="1234 1234 1234 1234"]'
|
||||
).send_keys("4242424242424242")
|
||||
self.browser.find_element(
|
||||
By.CSS_SELECTOR, 'input[placeholder="MM / YY"]'
|
||||
).send_keys("12 / 26")
|
||||
self.browser.find_element(
|
||||
By.CSS_SELECTOR, 'input[placeholder="CVC"]'
|
||||
).send_keys("424")
|
||||
self.browser.find_element(
|
||||
By.CSS_SELECTOR, 'input[placeholder="12345"]'
|
||||
).send_keys("42424")
|
||||
# 6. Return to main doc & submit form
|
||||
self.browser.switch_to.default_content()
|
||||
self.wait_for(
|
||||
lambda: self.assertFalse(
|
||||
self.browser.find_element(By.ID, "id_save_payment_method").get_attribute("hidden")
|
||||
)
|
||||
)
|
||||
self.browser.find_element(By.ID, "id_save_payment_method").click()
|
||||
# 7. Wait for saved card to appear in payment methods list
|
||||
# Assert last 4 digits shown (Stripe confirmSetup + server round-trip can be slow)
|
||||
self.wait_for_slow(
|
||||
lambda: self.assertIn(
|
||||
"4242",
|
||||
self.browser.find_element(By.ID, "id_payment_methods").text,
|
||||
)
|
||||
)
|
||||
|
||||
def test_user_can_cancel_adding_payment_method(self):
|
||||
# 1. Log in, navigate to wallet page
|
||||
self.create_pre_authenticated_session("capman@test.io")
|
||||
self.browser.get(self.live_server_url + "/dashboard/wallet/")
|
||||
# 2. Click Add Payment Method
|
||||
self.wait_for_slow(
|
||||
lambda: self.browser.find_element(By.ID, "id_add_payment_method")
|
||||
).click()
|
||||
# 3. Wait for Cancel button to appear (visible after setup-intent fetch returns)
|
||||
self.wait_for_slow(
|
||||
lambda: self.assertFalse(
|
||||
self.browser.find_element(By.ID, "id_cancel_payment_method").get_attribute("hidden")
|
||||
)
|
||||
)
|
||||
# 3a. Assert applet expanded to 15 rows
|
||||
rows = self.browser.execute_script(
|
||||
"return document.getElementById('id_payment_methods')"
|
||||
".style.getPropertyValue('--applet-rows').trim()"
|
||||
)
|
||||
self.assertEqual(rows, '15')
|
||||
# 4. Click Cancel
|
||||
self.browser.find_element(By.ID, "id_cancel_payment_method").click()
|
||||
# 5. Assert Cancel + Save buttons are hidden again
|
||||
self.wait_for_slow(
|
||||
lambda: self.assertTrue(
|
||||
self.browser.find_element(By.ID, "id_cancel_payment_method").get_attribute("hidden")
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
self.browser.find_element(By.ID, "id_save_payment_method").get_attribute("hidden")
|
||||
)
|
||||
# 6. Assert applet collapses back to 3 grid rows
|
||||
rows = self.browser.execute_script(
|
||||
"return document.getElementById('id_payment_methods')"
|
||||
".style.getPropertyValue('--applet-rows').trim()"
|
||||
)
|
||||
self.assertEqual(rows, '3')
|
||||
|
||||
def test_user_can_purchase_tithe_token_bundle(self):
|
||||
# 1. Log in, navigate to wallet page
|
||||
self.create_pre_authenticated_session("capman@test.io")
|
||||
self.browser.get(self.live_server_url + "/dashboard/wallet/")
|
||||
# 2. Assert Tithe Token purchase section present
|
||||
self.wait_for(
|
||||
lambda: self.browser.find_element(By.ID, "id_tithe_token_shop")
|
||||
)
|
||||
# 3. Assert min. +1 bundle option is visible
|
||||
bundle = self.browser.find_element(
|
||||
By.CSS_SELECTOR, "#id_tithe_token_shop .token-bundle"
|
||||
)
|
||||
# 4. Assert ea. bundle shows token count & writ bonus placeholder
|
||||
self.assertIn("Tithe Token", bundle.text)
|
||||
self.assertIn("Writ", bundle.text)
|
||||
# 5. (Placeholder) Purchase flow via Stripe not driven in this FT:
|
||||
# Full charge assertion deferred until Stripe webhook handling implemented
|
||||
Reference in New Issue
Block a user