# 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-2585-6861-579 - 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-2585-6861-579 Vienna South Active Amos Hayes Electronic Box m4QgB5 Unit ZeTj0e Basic (1 week) 4 Sep 2026 28 Aug 2026 28 Aug 2026": - cell: - checkbox - cell "9829-2585-6861-579": - link "9829-2585-6861-579": - /url: /bookings/982925856861579 - cell "Vienna South": - link "Vienna South": - /url: /bookings/982925856861579 - cell "Active": - link "Active": - /url: /bookings/982925856861579 - cell "Amos Hayes": - link "Amos Hayes": - /url: /bookings/982925856861579 - cell "Electronic Box m4QgB5": - link "Electronic Box m4QgB5": - /url: /bookings/982925856861579 - cell "Unit ZeTj0e": - link "Unit ZeTj0e": - /url: /bookings/982925856861579 - cell "Basic (1 week)": - link "Basic (1 week)": - /url: /bookings/982925856861579 - cell "4 Sep 2026": - link "4 Sep 2026": - /url: /bookings/982925856861579 - cell "28 Aug 2026": - link "28 Aug 2026": - /url: /bookings/982925856861579 - cell "28 Aug 2026": - link "28 Aug 2026": - /url: /bookings/982925856861579 - cell: - link: - /url: /bookings/982925856861579 - 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 | ```