# 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": 7217-1828-2939-933 - 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" - text: 1 selected - button "Deselect all" - button "Select all" - button "Price adjustment": - 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 [checked] - 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 "7217-1828-2939-933 Vienna South Active Finn Yundt Fresh Box qJFWco Unit eCYsz2 Basic (1 week) 29 Aug 2026 22 Aug 2026 22 Aug 2026": - cell: - checkbox [checked] - cell "7217-1828-2939-933": - link "7217-1828-2939-933": - /url: /bookings/721718282939933 - cell "Vienna South": - link "Vienna South": - /url: /bookings/721718282939933 - cell "Active": - link "Active": - /url: /bookings/721718282939933 - cell "Finn Yundt": - link "Finn Yundt": - /url: /bookings/721718282939933 - cell "Fresh Box qJFWco": - link "Fresh Box qJFWco": - /url: /bookings/721718282939933 - cell "Unit eCYsz2": - link "Unit eCYsz2": - /url: /bookings/721718282939933 - cell "Basic (1 week)": - link "Basic (1 week)": - /url: /bookings/721718282939933 - cell "29 Aug 2026": - link "29 Aug 2026": - /url: /bookings/721718282939933 - cell "22 Aug 2026": - link "22 Aug 2026": - /url: /bookings/721718282939933 - cell "22 Aug 2026": - link "22 Aug 2026": - /url: /bookings/721718282939933 - cell: - link: - /url: /bookings/721718282939933 - 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 - tooltip - dialog: - heading "Price adjustment" [level=3] - radio "Increase" [checked] - text: Increase - spinbutton "Increase (excl. VAT) by *": "10" - text: Increase (excl. VAT) by * - alert - combobox: - text: "%" - combobox: "%" - radio "Decrease" - text: Decrease - alert - group "Adjust base price of current": - heading "Adjust base price of current" [level=4] - checkbox "Unit" - text: Unit - checkbox "Protection plan & recurring products (increase/decrease per item)" [checked] - text: Protection plan & recurring products (increase/decrease per item) - alert - group "Price adjustment effective from": - heading "Price adjustment effective from" [level=4] - textbox "Date *": - /placeholder: dd/mm/yyyy - text: 22/08/2026 - text: Date * - alert - text: The price adjustment will be applied to the customer's next renewal from the selected date. Did you know that you can set up an automatic email that will be sent out to all affected customers? For this, click on 'Create Email' and select the trigger "x days before price adjustment". - contentinfo: - button "Cancel" - button "Save" ``` # 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 | ```