From ddf150f428b3d3bb98befd89a6b710e1bd12917c Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Sat, 26 Sep 2026 16:31:48 -0400 Subject: [PATCH] fix(cart): account for a closed window and an empty menu A closed page during add marks the remaining meals as not attempted, and an empty menu catalog raises the same error as the menu parser. --- scripts/fill_redefine_cart.py | 51 +++++++++++++++++++++-------------- src/scraper/parse_menu.py | 2 ++ tests/test_fill_cart.py | 23 ++++++++++++++++ tests/test_parse_menu.py | 8 +++++- 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/scripts/fill_redefine_cart.py b/scripts/fill_redefine_cart.py index 4983fbb..3ff81f3 100644 --- a/scripts/fill_redefine_cart.py +++ b/scripts/fill_redefine_cart.py @@ -145,7 +145,8 @@ def _fill_page(page, plan: FillPlan, menu_url: str) -> int: return 1 posted = [] - for item in plan.adds: + page_closed = False + for index, item in enumerate(plan.adds): try: _cart_request( page, @@ -161,27 +162,37 @@ def _fill_page(page, plan: FillPlan, menu_url: str) -> int: failed.append(f"{item.name}: {exc}") print(f"Failed: {item.name}: {exc}") continue - posted.append(item) - - try: - current = cart_lines(_cart_request(page, "GET", "/api/cart")) - except RuntimeError as exc: - failed.append(f"Could not read the cart: {exc}") - print(f"Failed: Could not read the cart: {exc}") - else: - for item in posted: - found = quantity_for(current, item.uuid) - if found != item.quantity: - message = ( - f"{item.name}: requested {item.quantity}, cart has {found}" - ) + except PlaywrightError as exc: + failed.append(f"{item.name}: {exc}") + print(f"Failed: {item.name}: {exc}") + for rest in plan.adds[index + 1 :]: + message = f"{rest.name}: not attempted" failed.append(message) print(f"Failed: {message}") - else: - added += 1 - origin = _origin(page.url or menu_url) - page.goto(f"{origin}/cart", wait_until="domcontentloaded", timeout=60000) - ready = True + page_closed = True + break + posted.append(item) + + if not page_closed: + try: + current = cart_lines(_cart_request(page, "GET", "/api/cart")) + except RuntimeError as exc: + failed.append(f"Could not read the cart: {exc}") + print(f"Failed: Could not read the cart: {exc}") + else: + for item in posted: + found = quantity_for(current, item.uuid) + if found != item.quantity: + message = ( + f"{item.name}: requested {item.quantity}, cart has {found}" + ) + failed.append(message) + print(f"Failed: {message}") + else: + added += 1 + origin = _origin(page.url or menu_url) + page.goto(f"{origin}/cart", wait_until="domcontentloaded", timeout=60000) + ready = True except (RuntimeError, PlaywrightError) as exc: failed.append(str(exc)) print(f"Failed: {exc}") diff --git a/src/scraper/parse_menu.py b/src/scraper/parse_menu.py index 6b905cb..5ee1f6e 100644 --- a/src/scraper/parse_menu.py +++ b/src/scraper/parse_menu.py @@ -49,6 +49,8 @@ def extract_catalog_products(page: str) -> list: products = _extract_json_attr(page, PRODUCTS_MARKER) if not isinstance(products, list): raise MenuParseError(":products must be a JSON array") + if not products: + raise MenuParseError("Menu catalog is empty") return products diff --git a/tests/test_fill_cart.py b/tests/test_fill_cart.py index ac8011a..c19e0ec 100644 --- a/tests/test_fill_cart.py +++ b/tests/test_fill_cart.py @@ -205,6 +205,29 @@ def test_fill_page_prints_a_summary_when_the_window_closes_mid_add(capsys): assert "Cart is ready" not in output +def test_fill_page_counts_items_left_when_the_window_closes(capsys): + plan = build_plan( + "Item,Quantity\nKorean Steak Bowl,1\nSale Bowl,2\n", + PRODUCTS, + ) + page = _CartPage( + [ + {"ok": True, "status": 200, "data": {"items": []}}, + PlaywrightError("Target page, context or browser has been closed"), + ] + ) + + code = fill_redefine_cart._fill_page( + page, plan, "https://www.redefinemeals.com/menu" + ) + output = capsys.readouterr().out + + assert code == 1 + assert "Korean Steak Bowl:" in output + assert "Sale Bowl: not attempted" in output + assert "Added 0. Failed 2. Skipped 0." in output + + def test_fill_page_prints_a_summary_when_the_first_cart_read_fails(capsys): plan = build_plan("Item,Quantity\nKorean Steak Bowl,1\n", PRODUCTS) page = _CartPage( diff --git a/tests/test_parse_menu.py b/tests/test_parse_menu.py index a06ad61..2256f33 100644 --- a/tests/test_parse_menu.py +++ b/tests/test_parse_menu.py @@ -4,7 +4,7 @@ from pathlib import Path import pytest -from scraper.parse_menu import MenuParseError, parse_menu_html +from scraper.parse_menu import MenuParseError, extract_catalog_products, parse_menu_html FIXTURE = Path(__file__).resolve().parent / "fixtures" / "menu_page.html" @@ -43,6 +43,12 @@ def test_parse_menu_html_rejects_a_missing_catalog(): parse_menu_html("", menu_url="https://example.com/menu") +def test_extract_catalog_products_rejects_an_empty_catalog(): + page = "" + with pytest.raises(MenuParseError, match="empty"): + extract_catalog_products(page) + + def test_parse_menu_html_rejects_an_empty_catalog(): page = "" with pytest.raises(MenuParseError, match="empty"):