# 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: src/manager/tests/e2e/booking/booking-price-change.spec.ts >> rejects a price change targeting items the booking does not have
- Location: src/manager/tests/e2e/booking/booking-price-change.spec.ts:184:5

# Error details

```
Error: expect(locator).toBeVisible() failed

Locator: locator('.v-snackbar').filter({ hasText: 'No price changes have been scheduled, as the selected bookings would not be affected by the price adjustment you have chosen.' })
Expected: visible
Timeout: 30000ms
Error: element(s) not found

Call log:
  - Expect "toBeVisible" with timeout 30000ms
  - waiting for locator('.v-snackbar').filter({ hasText: 'No price changes have been scheduled, as the selected bookings would not be affected by the price adjustment you have chosen.' })

```

```yaml
- navigation:
  - link:
    - /url: /
  - text: KINNOVIS Manager
  - button:
    - img
  - list:
    - text: Dashboard
    - link "Tasks":
      - /url: /tasks
      - img
      - text: Tasks
    - link "Facility maps":
      - /url: /facility-map
    - link "Analytics":
      - /url: /dashboard
    - text: Sales
    - link "Bookings":
      - /url: /bookings
    - link "Customers":
      - /url: /customers
    - link "Invoices":
      - /url: /invoices
    - link "Credit notes":
      - /url: /credit-notes
      - img
      - text: Credit notes
    - link "Units":
      - /url: /units
    - text: Site management
    - link "Locations":
      - /url: /locations
    - link "Unit types":
      - /url: /unit-types
    - link "Protection Plans":
      - /url: /insurances
    - link "Deposits":
      - /url: /deposits
    - link "Products":
      - /url: /products
    - link "Discounts":
      - /url: /discounts
    - option "Emails"
    - link "Document templates":
      - /url: /document-templates
    - text: Admin
    - link "Integrations":
      - /url: /connected-apps
    - link "User & Roles":
      - /url: /users
    - option "Booking Portal"
    - option "JaneAI"
    - text: Feedback
    - link "Voting Portal":
      - /url: /voting-portal
  - button "AS Andreas Stadler andreas.stadler@storeroom.at"
  - button
- main:
  - text: Bookings
  - link "Create booking":
    - /url: /bookings/create
  - button
  - button "Filters Reset filters":
    - heading "Filters" [level=3]
    - button "Reset filters"
  - textbox "Search": 9829-2584-6164-481
  - button "Clear Search": 󰅙
  - text: Search
  - combobox:
    - text: Location
    - combobox "Location"
  - combobox:
    - text: Status
    - combobox "Status"
  - combobox:
    - text: ID verification status
    - combobox "ID verification status"
  - combobox:
    - text: Access status
    - combobox "Access status"
  - combobox:
    - text: Unit type
    - combobox "Unit type"
  - text: Created at
  - textbox "Created at"
  - button "Select all"
  - button "Price adjustment" [disabled]:
    - img
    - text: Price adjustment
  - button "Remove price adjustment" [disabled]
  - button:
    - img
  - table:
    - rowgroup:
      - row "Booking number Location Status Customer Unit type Unit Booking plan Next invoice Created at Move-in Move-out":
        - columnheader:
          - checkbox
        - columnheader "Booking number":
          - button "Booking number"
        - columnheader "Location":
          - button "Location"
        - columnheader "Status":
          - button "Status"
        - columnheader "Customer":
          - button "Customer"
        - columnheader "Unit type"
        - columnheader "Unit":
          - button "Unit"
        - columnheader "Booking plan":
          - button "Booking plan"
        - columnheader "Next invoice":
          - button "Next invoice"
        - columnheader "Created at":
          - button "Created at"
        - columnheader "Move-in":
          - button "Move-in"
        - columnheader "Move-out":
          - button "Move-out"
      - row:
        - columnheader
    - rowgroup:
      - row "9829-2584-6164-481 Vienna South Active Kimberly Donnelly Generic Box 3HCywd Unit C6jSAX Basic (1 week) 5 Sep 2026 29 Aug 2026 29 Aug 2026":
        - cell:
          - checkbox
        - cell "9829-2584-6164-481":
          - link "9829-2584-6164-481":
            - /url: /bookings/982925846164481
        - cell "Vienna South":
          - link "Vienna South":
            - /url: /bookings/982925846164481
        - cell "Active":
          - link "Active":
            - /url: /bookings/982925846164481
        - cell "Kimberly Donnelly":
          - link "Kimberly Donnelly":
            - /url: /bookings/982925846164481
        - cell "Generic Box 3HCywd":
          - link "Generic Box 3HCywd":
            - /url: /bookings/982925846164481
        - cell "Unit C6jSAX":
          - link "Unit C6jSAX":
            - /url: /bookings/982925846164481
        - cell "Basic (1 week)":
          - link "Basic (1 week)":
            - /url: /bookings/982925846164481
        - cell "5 Sep 2026":
          - link "5 Sep 2026":
            - /url: /bookings/982925846164481
        - cell "29 Aug 2026":
          - link "29 Aug 2026":
            - /url: /bookings/982925846164481
        - cell "29 Aug 2026":
          - link "29 Aug 2026":
            - /url: /bookings/982925846164481
        - cell:
          - link:
            - /url: /bookings/982925846164481
  - text: "Items per page:"
  - combobox:
    - text: "10"
    - combobox "Items per page:": "10"
  - text: 1-1 of 1
  - button [disabled]
  - button [disabled]
  - button [disabled]
  - button [disabled]
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
- tooltip
```

# Test source

```ts
  117 | });
  118 | 
  119 | test('rejects a price change with an out-of-range value', async ({
  120 |   setupBookingCreateData,
  121 |   createBooking,
  122 |   bookingListPage,
  123 | }) => {
  124 |   // The magnitude caps differ per direction and value type: increases allow at most 1000 %, decreases at
  125 |   // most 100 % (a full waiver), and fixed amounts at most 5000.
  126 |   const invalidPriceChanges: { priceChange: BookingPriceChange; error: string }[] = [
  127 |     {
  128 |       priceChange: {
  129 |         option: 'increase',
  130 |         value: 1001,
  131 |         valueType: 'percentage',
  132 |         changeAt: getFutureDate(0, 'day'),
  133 |         adjustUnit: true,
  134 |         adjustRecurringProducts: false,
  135 |       },
  136 |       error: 'must have a maximum value of 1000',
  137 |     },
  138 |     {
  139 |       priceChange: {
  140 |         option: 'decrease',
  141 |         value: 101,
  142 |         valueType: 'percentage',
  143 |         changeAt: getFutureDate(0, 'day'),
  144 |         adjustUnit: true,
  145 |         adjustRecurringProducts: false,
  146 |       },
  147 |       error: 'must have a maximum value of 100',
  148 |     },
  149 |     {
  150 |       priceChange: {
  151 |         option: 'increase',
  152 |         value: 5001,
  153 |         valueType: 'amount',
  154 |         changeAt: getFutureDate(0, 'day'),
  155 |         adjustUnit: true,
  156 |         adjustRecurringProducts: false,
  157 |       },
  158 |       error: 'must have a maximum value of 5000',
  159 |     },
  160 |   ];
  161 | 
  162 |   const booking = await createBooking(await setupBookingCreateData());
  163 |   const currency = booking.location.currency;
  164 | 
  165 |   await bookingListPage.goto();
  166 | 
  167 |   await test.step('select the booking in the list', async () => {
  168 |     await bookingListPage.searchTextField.fill(formatId(booking.id));
  169 |     await bookingListPage.selectBookings(0);
  170 |   });
  171 | 
  172 |   const priceChangeDialog = await test.step('open the price change dialog', () =>
  173 |     bookingListPage.openBookingPriceChangeDialog());
  174 | 
  175 |   for (const { priceChange, error } of invalidPriceChanges) {
  176 |     await test.step(`verify ${priceChange.value} ${priceChange.valueType} ${priceChange.option} is rejected`, async () => {
  177 |       await priceChangeDialog.fill(priceChange, currency);
  178 |       await priceChangeDialog.submit();
  179 |       await expect(priceChangeDialog.errors).toContainText([error]);
  180 |     });
  181 |   }
  182 | });
  183 | 
  184 | test('rejects a price change targeting items the booking does not have', async ({
  185 |   setupBookingCreateData,
  186 |   createBooking,
  187 |   bookingListPage,
  188 | }) => {
  189 |   // Recurring products only, on a booking that has none: the server schedules nothing and reports it.
  190 |   const priceChange: BookingPriceChange = {
  191 |     option: 'increase',
  192 |     value: 10,
  193 |     valueType: 'percentage',
  194 |     changeAt: getFutureDate(0, 'day'),
  195 |     adjustUnit: false,
  196 |     adjustRecurringProducts: true,
  197 |   };
  198 | 
  199 |   const booking = await createBooking(await setupBookingCreateData());
  200 | 
  201 |   await bookingListPage.goto();
  202 | 
  203 |   await test.step('select the booking in the list', async () => {
  204 |     await bookingListPage.searchTextField.fill(formatId(booking.id));
  205 |     await bookingListPage.selectBookings(0);
  206 |   });
  207 | 
  208 |   const priceChangeDialog = await test.step('apply the price change', async () => {
  209 |     const dialog = await bookingListPage.openBookingPriceChangeDialog();
  210 |     await dialog.fill(priceChange, booking.location.currency);
  211 |     await dialog.submit();
  212 |     await dialog.confirmDialog.submit();
  213 |     return dialog;
  214 |   });
  215 | 
  216 |   await test.step('verify the error snackbar', async () => {
> 217 |     await expect(priceChangeDialog.noBookingsAffectedSnackbar).toBeVisible();
      |                                                                ^ Error: expect(locator).toBeVisible() failed
  218 |   });
  219 | });
  220 | 
  221 | test('disables the price adjustment action for a scheduled booking', async ({
  222 |   setupBookingCreateData,
  223 |   createBooking,
  224 |   bookingListPage,
  225 | }) => {
  226 |   const booking = await createBooking(await setupBookingCreateData({}, { moveInDaysInTheFuture: 5 }));
  227 | 
  228 |   await bookingListPage.goto();
  229 | 
  230 |   await test.step('select the scheduled booking in the list', async () => {
  231 |     await bookingListPage.searchTextField.fill(formatId(booking.id));
  232 |     await bookingListPage.selectBookings(0);
  233 |   });
  234 | 
  235 |   await test.step('verify the price adjustment action is disabled', async () => {
  236 |     await expect(bookingListPage.priceChangeActionButton).toBeDisabled();
  237 |   });
  238 | });
  239 | 
  240 | test('disables the price adjustment action for a booking with a scheduled price change', async ({
  241 |   setupBookingCreateData,
  242 |   createBooking,
  243 |   createBookingPriceChange,
  244 |   bookingListPage,
  245 | }) => {
  246 |   const priceChange: BookingPriceChange = {
  247 |     option: 'increase',
  248 |     value: 10,
  249 |     valueType: 'percentage',
  250 |     changeAt: getFutureDate(0, 'day'),
  251 |     adjustUnit: true,
  252 |     adjustRecurringProducts: false,
  253 |   };
  254 | 
  255 |   const booking = await createBooking(await setupBookingCreateData());
  256 |   await createBookingPriceChange([booking.id], priceChange);
  257 | 
  258 |   await bookingListPage.goto();
  259 | 
  260 |   await test.step('select the booking in the list', async () => {
  261 |     await bookingListPage.searchTextField.fill(formatId(booking.id));
  262 |     await bookingListPage.selectBookings(0);
  263 |   });
  264 | 
  265 |   await test.step('verify the price adjustment action is disabled', async () => {
  266 |     await expect(bookingListPage.priceChangeActionButton).toBeDisabled();
  267 |   });
  268 | });
  269 | 
```