"""The home's lessons map and a lesson round in a real browser (game-home-lesson-rounds 2.1-2.3).""" import os from datetime import timedelta import pytest playwright = pytest.importorskip("playwright.sync_api") from mathly2 import clock from mathly2.bank import load_bank from mathly2.config import Settings from mathly2.main import create_app from mathly2.store import InMemoryStore from mathly2.tests.serve import offline_formulas, only, running from mathly2.tests.test_home_browser import KID, KID_ID, PHONE, TEACHER, no_sideways_scroll, sign_in, to_home SHOTS = os.environ.get("MATHLY_SHOTS") # a folder: save screenshots there to look at the screens STAMP = "2026-10-05T10:00:00+00:00" @pytest.fixture(scope="module") def bank(): return load_bank() @pytest.fixture def browser(): with playwright.sync_playwright() as p: b = p.chromium.launch() yield b b.close() def world(bank, n=4): lessons = [l["id"] for l in bank.lessons if any(s["unit"] == l["id"] and bank.question_keys(s["id"]) for s in bank.skills)][:n] store = InMemoryStore() klass = store.create_class("setup", "A", lessons=lessons, teacher_email=TEACHER) store.add_member(klass["id"], KID) small = only(bank, lessons) return create_app(Settings(), bank=small, store=store), store, small, lessons def shot(page, name): if SHOTS: page.screenshot(path=os.path.join(SHOTS, f"{name}.png"), full_page=True) def right_index(bank, key): skill = next(s for s in bank.skills if key in bank.question_keys(s["id"])) from mathly2.bank import question_key question = next(q for q in skill["questions"] if question_key(skill["id"], q) == key) return next(i for i, c in enumerate(question["choices"]) if c.get("correct")) def test_home_is_a_map_of_the_lessons_with_stars_and_a_play_button(browser, bank): app, store, small, lessons = world(bank) first = [s["id"] for s in small.skills if s["unit"] == lessons[0]] for s in first: store.save_progress(KID_ID, s, started_at=STAMP, mastered_at=STAMP) # lesson 1 perfect store.set_lesson_dates({lessons[2]: clock.school_day() - timedelta(days=3)}) # lesson 3 behind with running(app) as url: page = sign_in(browser, url) to_home(page) page.wait_for_selector("#lesson-map") nodes = page.locator(".lnode") assert nodes.count() == len(lessons) first, late = page.locator(f".lnode[data-lesson='{lessons[0]}']"), page.locator(f".lnode[data-lesson='{lessons[2]}']") assert first.get_attribute("data-stars") == "3" and "👑" in first.text_content() assert "⏰" in late.text_content() assert page.locator("#stars-count").inner_text() == f"⭐ 3/{3 * len(lessons)}" assert page.locator("#play").get_attribute("href") == f"#/round/{lessons[2]}" # the behind lesson comes first assert page.locator(".lnode.next").get_attribute("data-lesson") == lessons[2] assert page.locator(".tabs a").nth(1).inner_text() == "העמקה" and page.locator("#behind-card").count() == 0 no_sideways_scroll(page) shot(page, "home") assert page.errors == [] def test_a_round_from_the_map_gives_feedback_and_ends_with_stars(browser, bank): app, store, small, lessons = world(bank, 2) with running(app) as url: page = sign_in(browser, url) to_home(page) page.click("#play") page.wait_for_selector(".round-layer #ex-q") total = int(page.locator("#rd-count").inner_text().split()[-1]) shot(page, "round-question") for i in range(total): key = page.locator("#ex-q").get_attribute("data-key") pick = right_index(small, key) if i < total - 1 else None if pick is None: # the last one wrong page.locator(f".ex-choice:not([data-i='{right_index(small, key)}'])").first.click() page.wait_for_selector(".ex-bad") shot(page, "round-wrong") else: page.click(f".ex-choice[data-i='{pick}']") page.wait_for_selector(".ex-ok") page.click("#rd-next") if i < total - 1: page.wait_for_function(f"document.querySelector('#rd-count').textContent.includes('{i + 2} מתוך')") page.wait_for_selector("#rd-result") assert page.locator("#rd-score").inner_text().replace("\n", " ").startswith(f"{total - 1}") assert page.locator(".rd-bigstars span.on").count() == 2 # 9 of 10: two stars shot(page, "round-result") page.click("#rd-result [data-close]") page.wait_for_selector(f".lnode[data-lesson='{lessons[0]}'][data-stars='2']") # the map refreshes with the new stars assert page.evaluate("location.hash") == "#/subject/math" assert page.errors == [] def test_a_link_to_a_round_opens_the_home_under_it(browser, bank): app, _, _, lessons = world(bank, 2) with running(app) as url: page = sign_in(browser, url, f"#/round/{lessons[1]}") page.wait_for_selector(".round-layer #ex-q") assert page.locator("#lesson-map").count() == 1 page.click(".mx-x") page.wait_for_function("!document.querySelector('.round-layer')") assert page.errors == [] def test_home_is_one_screen_with_every_lesson_a_test_and_an_assignment(browser, bank): lessons = [l["id"] for l in bank.lessons] store = InMemoryStore() klass = store.create_class("setup", "A", lessons=lessons, teacher_email=TEACHER) store.add_member(klass["id"], KID) store.create_test(klass["id"], clock.school_day() + timedelta(days=9), lessons[:2], TEACHER, created_at=clock.now() - timedelta(days=20)) store.create_assignment(klass["id"], "דף", clock.school_day() + timedelta(days=3), TEACHER, "pages", pages=["1"]) with running(create_app(Settings(), bank=bank, store=store)) as url: page = sign_in(browser, url) to_home(page) page.wait_for_selector("#lesson-map") page.wait_for_selector("#assignments") assert page.locator(".lnode").count() == len(lessons) and page.locator("#nearest-test #home-sim").count() == 1 assert page.evaluate("document.documentElement.scrollHeight <= window.innerHeight") # nothing to scroll no_sideways_scroll(page) shot(page, "home-one-screen") assert page.errors == [] def test_home_shows_the_current_lesson_and_folds_the_past_and_the_coming_ones(browser, bank): app, store, small, lessons = world(bank, 5) for s in (s["id"] for s in small.skills if s["unit"] == lessons[0]): store.save_progress(KID_ID, s, started_at=STAMP, mastered_at=STAMP) # lesson 1 done today = clock.school_day() store.set_lesson_dates({lessons[0]: today - timedelta(days=14), lessons[1]: today - timedelta(days=7), # 2 is behind lessons[2]: today + timedelta(days=2), lessons[3]: today + timedelta(days=9)}) # 5 has no date with running(app) as url: page = sign_in(browser, url) to_home(page) page.wait_for_selector("#lesson-now") assert page.locator("#lesson-now .lnode").get_attribute("data-lesson") == lessons[2] assert "השיעור הנוכחי" in page.inner_text("#lesson-now") past = page.locator("#lessons-past") assert [n.get_attribute("data-lesson") for n in past.locator(".lnode").all()] == lessons[:2] assert "שיעורים שעברו (2)" in past.locator("summary").inner_text() and "⏰ 1 בפיגור" in past.locator("summary").inner_text() assert not past.locator(".lnode").first.is_visible() # folded until opened past.locator("summary").click() assert past.locator(".lnode").first.is_visible() later = page.locator("#lessons-later") assert [n.get_attribute("data-lesson") for n in later.locator(".lnode").all()] == lessons[3:5] assert page.locator("#play").get_attribute("href") == f"#/round/{lessons[1]}" # the behind lesson is still played first no_sideways_scroll(page) shot(page, "home-by-date") assert page.errors == []