# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: shopping.spec.ts >> user sees quantity suggestions in shopping list
- Location: tests/shopping.spec.ts:175:5

# Error details

```
Error: expect(received).toBe(expected) // Object.is equality

Expected: "400 г"
Received: undefined
```

# Page snapshot

```yaml
- generic [ref=e4]:
  - main "Список покупок" [ref=e5]:
    - generic [ref=e6]:
      - button "0" [ref=e7]:
        - generic [ref=e8]:
          - img [ref=e9]
          - generic [ref=e11]: "0"
      - button "add" [ref=e12]:
        - img [ref=e14]
    - dialog "В корзине":
      - generic [ref=e19]:
        - generic [ref=e21]:
          - generic [ref=e22]:
            - img [ref=e23]
            - img [ref=e26]
          - generic [ref=e28]: 7 из 7 куплено
        - list [ref=e30]:
          - listitem "Морковь" [ref=e31]:
            - button "Морковь" [ref=e32] [cursor=pointer]
            - generic:
              - img
              - generic:
                - generic: Морковь
              - button "400 г" [ref=e33] [cursor=pointer]:
                - generic [ref=e34]: 400 г
                - img [ref=e35]
          - listitem "Перец чёрный" [ref=e37]:
            - button "Перец чёрный" [ref=e38] [cursor=pointer]
            - generic:
              - img
              - generic:
                - generic: Перец чёрный
                - generic: 1 рецепт
              - button "Указать кол-во" [ref=e39] [cursor=pointer]:
                - img [ref=e40]
                - generic [ref=e42]: Указать кол-во
          - listitem "Соль" [ref=e43]:
            - button "Соль" [ref=e44] [cursor=pointer]
            - generic:
              - img
              - generic:
                - generic: Соль
                - generic: 1 рецепт
              - button "500 г" [ref=e45] [cursor=pointer]:
                - generic [ref=e46]: 500 г
                - img [ref=e47]
          - listitem "Помидоры" [ref=e49]:
            - button "Помидоры" [ref=e50] [cursor=pointer]
            - generic:
              - img
              - generic:
                - generic: Помидоры
                - generic: 1 рецепт • 200 г
              - button "200 г" [ref=e51] [cursor=pointer]:
                - generic [ref=e52]: 200 г
                - img [ref=e53]
          - listitem "Молоко" [ref=e55]:
            - button "Молоко" [ref=e56] [cursor=pointer]
            - generic:
              - img
              - generic:
                - generic: Молоко
                - generic: 1 рецепт • 500 мл
              - button "500 г" [ref=e57] [cursor=pointer]:
                - generic [ref=e58]: 500 г
                - img [ref=e59]
          - listitem "Яйцо куриное" [ref=e61]:
            - button "Яйцо куриное" [ref=e62] [cursor=pointer]
            - generic:
              - img
              - generic:
                - generic: Яйцо куриное
                - generic: 1 рецепт • 2 шт
              - button "10 шт" [ref=e63] [cursor=pointer]:
                - generic [ref=e64]: 10 шт
                - img [ref=e65]
          - listitem "Говядина" [ref=e67]:
            - button "Говядина" [ref=e68] [cursor=pointer]
            - generic:
              - img
              - generic:
                - generic: Говядина
                - generic: 1 рецепт • 300 г
              - button "300 г" [ref=e69] [cursor=pointer]:
                - generic [ref=e70]: 300 г
                - img [ref=e71]
        - button "Завершить покупки" [ref=e74]:
          - generic [ref=e76]: Завершить покупки
  - navigation [ref=e77]:
    - button "Рецепты" [ref=e78] [cursor=pointer]:
      - img [ref=e79]
    - button "Журнал" [ref=e81] [cursor=pointer]:
      - img [ref=e82]
    - button "Есть дома" [ref=e84] [cursor=pointer]:
      - img [ref=e85]
    - button "Покупки" [ref=e87] [cursor=pointer]:
      - img [ref=e88]
    - button "Профиль" [ref=e90] [cursor=pointer]:
      - img [ref=e91]
```

# Test source

```ts
  150 |   // Set quantity for "Молоко" (1000g)
  151 |   await openIngredientBottomSheet(page, "Молоко");
  152 |   await markIngredientAsInStock(page, "Молоко", "Граммы", 1000);
  153 | 
  154 |   // Set quantity for "Соль" (500g)
  155 |   await openIngredientBottomSheet(page, "Соль");
  156 |   await markIngredientAsInStock(page, "Соль", "Граммы", 500);
  157 | 
  158 |   // Set quantity for "Перец чёрный" (left some)
  159 |   await openIngredientBottomSheet(page, "Перец чёрный");
  160 |   await markIngredientAsInStock(page, "Перец чёрный", "Примерно");
  161 | 
  162 |   // Navigate to shopping list
  163 |   await goToShopping(page);
  164 | 
  165 |   // Verify that only missing ingredients appear in the shopping list
  166 |   await expect(getItem(page, "Яйцо куриное")).toContainText(recipeTitle);
  167 |   await expect(getItem(page, "Яйцо куриное")).toContainText("2 шт");
  168 |   await expect(getItem(page, "Говядина")).not.toBeVisible();
  169 |   await expect(getItem(page, "Помидоры")).not.toBeVisible();
  170 |   await expect(getItem(page, "Молоко")).not.toBeVisible();
  171 |   await expect(getItem(page, "Соль")).not.toBeVisible();
  172 |   await expect(getItem(page, "Перец чёрный")).not.toBeVisible();
  173 | });
  174 | 
  175 | test("user sees quantity suggestions in shopping list", async ({
  176 |   page,
  177 |   user,
  178 | }) => {
  179 |   test.setTimeout(120_000);
  180 |   // Clean meal plans
  181 |   await user.kitchen.cleanMealPlans();
  182 | 
  183 |   // Create a test recipe with known ingredients
  184 |   const recipeTitle = "Тестовое блюдо для проверки подсказок";
  185 |   await user.createRecipe({
  186 |     title: recipeTitle,
  187 |     servings: 2,
  188 |     ingredients: [
  189 |       ["Говядина", "WEIGHT", 300],
  190 |       ["Яйцо куриное", "QUANTITY", 2],
  191 |       ["Молоко", "VOLUME", 0.5],
  192 |       ["Помидоры", "WEIGHT", 200],
  193 |       "Соль",
  194 |       "Перец чёрный",
  195 |     ],
  196 |     dishTypes: ["Main course"],
  197 |   });
  198 | 
  199 |   // Login to the app
  200 |   await loginByEmailAndPassword(page, user.email, user.password);
  201 | 
  202 |   // ------------------------------------------------------------------
  203 |   // Round 1: seed shopping history with the recipe's ingredients plus a
  204 |   // manually-added ingredient (Морковь) that isn't part of any recipe.
  205 |   // ------------------------------------------------------------------
  206 |   await addRecipeToMealPlan(page, recipeTitle, "Ужин");
  207 |   await goToShopping(page);
  208 | 
  209 |   // Морковь is not part of the recipe - add it manually
  210 |   await addToShoppingList(page, "Морковь");
  211 | 
  212 |   // Говядина - specify exact recipe quantity (300г)
  213 |   await markShoppingItemCompleted(page, "Говядина");
  214 |   expect(await getCompletedQuantity(page, "Говядина")).toBeUndefined();
  215 |   await specifyCompletedQuantity(page, "Говядина", "Граммы", 300);
  216 |   expect(await getCompletedQuantity(page, "Говядина")).toBe("300 г");
  217 | 
  218 |   // Яйцо куриное - specify excess (10шт vs recipe's 2)
  219 |   await markShoppingItemCompleted(page, "Яйцо куриное");
  220 |   expect(await getCompletedQuantity(page, "Яйцо куриное")).toBeUndefined();
  221 |   await specifyCompletedQuantity(page, "Яйцо куриное", "Штуки", 10);
  222 |   expect(await getCompletedQuantity(page, "Яйцо куриное")).toBe("10 шт");
  223 | 
  224 |   // Молоко - specify 500г (different unit than recipe's 500мл)
  225 |   await markShoppingItemCompleted(page, "Молоко");
  226 |   expect(await getCompletedQuantity(page, "Молоко")).toBeUndefined();
  227 |   await specifyCompletedQuantity(page, "Молоко", "Граммы", 500);
  228 |   expect(await getCompletedQuantity(page, "Молоко")).toBe("500 г");
  229 | 
  230 |   // Помидоры - specify 200г (exact recipe quantity)
  231 |   await markShoppingItemCompleted(page, "Помидоры");
  232 |   expect(await getCompletedQuantity(page, "Помидоры")).toBeUndefined();
  233 |   await specifyCompletedQuantity(page, "Помидоры", "Граммы", 200);
  234 |   expect(await getCompletedQuantity(page, "Помидоры")).toBe("200 г");
  235 | 
  236 |   // Соль - not in recipe, specify 500г
  237 |   await markShoppingItemCompleted(page, "Соль");
  238 |   expect(await getCompletedQuantity(page, "Соль")).toBeUndefined();
  239 |   await specifyCompletedQuantity(page, "Соль", "Граммы", 500);
  240 |   expect(await getCompletedQuantity(page, "Соль")).toBe("500 г");
  241 | 
  242 |   // Перец чёрный - leave unspecified (will be filtered out of future suggestions)
  243 |   await markShoppingItemCompleted(page, "Перец чёрный");
  244 |   expect(await getCompletedQuantity(page, "Перец чёрный")).toBeUndefined();
  245 | 
  246 |   // Морковь (manual) - specify 400г
  247 |   await markShoppingItemCompleted(page, "Морковь");
  248 |   expect(await getCompletedQuantity(page, "Морковь")).toBeUndefined();
  249 |   await specifyCompletedQuantity(page, "Морковь", "Граммы", 400);
> 250 |   expect(await getCompletedQuantity(page, "Морковь")).toBe("400 г");
      |                                                       ^ Error: expect(received).toBe(expected) // Object.is equality
  251 | 
  252 |   // Complete shopping to record history, then reset stock
  253 |   await completeShopping(page);
  254 |   await goToLeftovers(page);
  255 |   await markLeftoverAsLost(page, "Говядина");
  256 |   await markLeftoverAsLost(page, "Яйцо куриное");
  257 |   await markLeftoverAsLost(page, "Молоко");
  258 |   await markLeftoverAsLost(page, "Помидоры");
  259 |   await markLeftoverAsLost(page, "Соль");
  260 |   await markLeftoverAsLost(page, "Перец чёрный");
  261 |   await markLeftoverAsLost(page, "Морковь");
  262 | 
  263 |   // ------------------------------------------------------------------
  264 |   // Round 2: plan the recipe a second time (required quantities = 2x)
  265 |   // so we can verify auto-apply respects sufficiency against the history
  266 |   // from round 1, and extend history with new purchases.
  267 |   // ------------------------------------------------------------------
  268 |   await addRecipeToMealPlan(page, recipeTitle, "Ужин");
  269 |   await goToShopping(page);
  270 | 
  271 |   await addToShoppingList(page, "Морковь");
  272 | 
  273 |   // Говядина - recipe needs 600г (2x300g), previously bought 300г
  274 |   // 300г is below required amount so not auto-applied
  275 |   await markShoppingItemCompleted(page, "Говядина");
  276 |   expect(await getCompletedQuantity(page, "Говядина")).toBeUndefined();
  277 |   // Buy a larger, distinct amount so round 3 has two unique history entries
  278 |   await specifyCompletedQuantity(page, "Говядина", "Граммы", 500);
  279 |   expect(await getCompletedQuantity(page, "Говядина")).toBe("500 г");
  280 | 
  281 |   // Яйцо куриное - recipe needs 4шт (2x2), previously bought 10шт
  282 |   // 10шт >= 4шт so suggestion is auto-applied
  283 |   await markShoppingItemCompleted(page, "Яйцо куриное");
  284 |   expect(await getCompletedQuantity(page, "Яйцо куриное")).toBe("10 шт");
  285 |   // Override the auto-applied value to add a distinct history entry
  286 |   await specifyCompletedQuantity(page, "Яйцо куриное", "Штуки", 8);
  287 |   expect(await getCompletedQuantity(page, "Яйцо куриное")).toBe("8 шт");
  288 | 
  289 |   // Молоко - recipe needs 1000мл (2x500ml), previously bought 500г (different unit!)
  290 |   // Cross-unit comparison can't prove sufficiency, so not auto-applied
  291 |   await markShoppingItemCompleted(page, "Молоко");
  292 |   expect(await getCompletedQuantity(page, "Молоко")).toBeUndefined();
  293 |   // This time buy in the recipe's unit so the suggestion survives round 3
  294 |   await specifyCompletedQuantity(page, "Молоко", "Миллилитры", 500);
  295 |   expect(await getCompletedQuantity(page, "Молоко")).toBe("500 мл");
  296 | 
  297 |   // Помидоры - recipe needs 400г (2x200g), previously bought 200г
  298 |   // 200г is below required amount so not auto-applied
  299 |   await markShoppingItemCompleted(page, "Помидоры");
  300 |   expect(await getCompletedQuantity(page, "Помидоры")).toBeUndefined();
  301 |   // Buy 200г again (same as round 1) - exercises dedup in round 3 (#94)
  302 |   await specifyCompletedQuantity(page, "Помидоры", "Граммы", 200);
  303 |   expect(await getCompletedQuantity(page, "Помидоры")).toBe("200 г");
  304 | 
  305 |   // Соль - no recipe requirement, previously bought 500г → auto-applied
  306 |   await markShoppingItemCompleted(page, "Соль");
  307 |   expect(await getCompletedQuantity(page, "Соль")).toBe("500 г");
  308 |   // Keep 500г (same as round 1) - exercises dedup in round 3 (#94)
  309 |   await specifyCompletedQuantity(page, "Соль", "Граммы", 500);
  310 |   expect(await getCompletedQuantity(page, "Соль")).toBe("500 г");
  311 | 
  312 |   // Перец чёрный - no recipe requirement, round 1 was unspecified so no suggestion
  313 |   await markShoppingItemCompleted(page, "Перец чёрный");
  314 |   expect(await getCompletedQuantity(page, "Перец чёрный")).toBeUndefined();
  315 |   // Record a first concrete quantity so round 3 has something to suggest
  316 |   await specifyCompletedQuantity(page, "Перец чёрный", "Граммы", 10);
  317 |   expect(await getCompletedQuantity(page, "Перец чёрный")).toBe("10 г");
  318 | 
  319 |   // Морковь (manual) - not in any recipe, no preselected quantity expected
  320 |   await markShoppingItemCompleted(page, "Морковь");
  321 |   expect(await getCompletedQuantity(page, "Морковь")).toBeUndefined();
  322 |   // Buy a different amount than round 1 (250г vs 400г)
  323 |   await specifyCompletedQuantity(page, "Морковь", "Граммы", 250);
  324 |   expect(await getCompletedQuantity(page, "Морковь")).toBe("250 г");
  325 | 
  326 |   // Complete shopping to record history, then reset stock
  327 |   await completeShopping(page);
  328 |   await goToLeftovers(page);
  329 |   await markLeftoverAsLost(page, "Говядина");
  330 |   await markLeftoverAsLost(page, "Яйцо куриное");
  331 |   await markLeftoverAsLost(page, "Молоко");
  332 |   await markLeftoverAsLost(page, "Помидоры");
  333 |   await markLeftoverAsLost(page, "Соль");
  334 |   await markLeftoverAsLost(page, "Перец чёрный");
  335 |   await markLeftoverAsLost(page, "Морковь");
  336 | 
  337 |   // ------------------------------------------------------------------
  338 |   // Round 3: clear meal plans and re-plan once (1x requirements) so the
  339 |   // widest possible set of past purchases qualifies as suggestions.
  340 |   // Then verify suggestions via the modal — including dedup behavior
  341 |   // from issue #94 and full-history behavior for the manual ingredient.
  342 |   // ------------------------------------------------------------------
  343 |   await user.kitchen.cleanMealPlans();
  344 |   await addRecipeToMealPlan(page, recipeTitle, "Ужин");
  345 |   await goToShopping(page);
  346 | 
  347 |   await addToShoppingList(page, "Морковь");
  348 | 
  349 |   // Mark all items as completed so we can open the edit-quantity modal
  350 |   await markShoppingItemCompleted(page, "Говядина");
```