# 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/customer/customer-edit-general.spec.ts >> edits the phone number - Location: src/manager/tests/e2e/customer/customer-edit-general.spec.ts:125:5 # Error details ``` Error: expect(locator).toHaveCount(expected) failed Locator: getByTestId('app-page').getByTestId('app-page-content').locator('.ui-data-grid').locator('[data-test-id=ui-data-grid-table]:not(.disabled):not(.loading):not(.fetching)').getByTestId('ui-data-grid-table-with-data').locator('tr') Expected: 1 Received: 0 Timeout: 20000ms Call log: - Expect "toHaveCount" with timeout 20000ms - waiting for getByTestId('app-page').getByTestId('app-page-content').locator('.ui-data-grid').locator('[data-test-id=ui-data-grid-table]:not(.disabled):not(.loading):not(.fetching)').getByTestId('ui-data-grid-table-with-data').locator('tr') 24 × locator resolved to 0 elements - unexpected value "0" ``` # Page snapshot ```yaml - generic [ref=e1]: - generic [ref=e6]: - banner [ref=e7]: - generic [ref=e8]: - button [ref=e9] [cursor=pointer]: - generic [ref=e11]: 󰍜 - link [ref=e12] [cursor=pointer]: - /url: / - button "AS" [ref=e15] [cursor=pointer]: - generic [ref=e19]: AS - generic: - text: 󰗊 󰅀 - text: 󰷖 󰍃 - navigation [ref=e20]: - list [ref=e22]: - generic [ref=e24]: Dashboard - link "Tasks" [ref=e25] [cursor=pointer]: - /url: /tasks - img [ref=e28] - generic [ref=e31]: Tasks - link "Facility maps" [ref=e32] [cursor=pointer]: - /url: /facility-map - generic [ref=e34]: 󰧾 - generic [ref=e36]: Facility maps - link "Analytics" [ref=e37] [cursor=pointer]: - /url: /dashboard - generic [ref=e39]: 󱖶 - generic [ref=e41]: Analytics - generic [ref=e43]: Sales - link "Bookings" [ref=e44] [cursor=pointer]: - /url: /bookings - generic [ref=e46]: 󰇡 - generic [ref=e48]: Bookings - link "Customers" [ref=e49] [cursor=pointer]: - /url: /customers - generic [ref=e51]: 󰀏 - generic [ref=e53]: Customers - link "Invoices" [ref=e54] [cursor=pointer]: - /url: /invoices - generic [ref=e56]: 󰷉 - generic [ref=e58]: Invoices - link "Credit notes" [ref=e59] [cursor=pointer]: - /url: /credit-notes - img [ref=e62] - generic [ref=e65]: Credit notes - link "Units" [ref=e66] [cursor=pointer]: - /url: /units - generic [ref=e68]: 󰍀 - generic [ref=e70]: Units - generic [ref=e72]: Site management - link "Locations" [ref=e73] [cursor=pointer]: - /url: /locations - generic [ref=e75]: 󰟙 - generic [ref=e77]: Locations - link "Unit types" [ref=e78] [cursor=pointer]: - /url: /unit-types - generic [ref=e80]: 󰆧 - generic [ref=e82]: Unit types - link "Protection Plans" [ref=e83] [cursor=pointer]: - /url: /insurances - generic [ref=e85]: 󰳌 - generic [ref=e87]: Protection Plans - link "Deposits" [ref=e88] [cursor=pointer]: - /url: /deposits - generic [ref=e90]: 󱙆 - generic [ref=e92]: Deposits - link "Products" [ref=e93] [cursor=pointer]: - /url: /products - generic [ref=e95]: 󰄑 - generic [ref=e97]: Products - link "Discounts" [ref=e98] [cursor=pointer]: - /url: /discounts - generic [ref=e100]: 󰓼 - generic [ref=e102]: Discounts - generic [ref=e103]: - option "Emails" [ref=e104] [cursor=pointer]: - generic [ref=e106]: 󰻨 - generic [ref=e108]: Emails - generic [ref=e112]: 󰅀 - text: 󱡰 󰁥 - generic [ref=e114]: Admin - link "Integrations" [ref=e115] [cursor=pointer]: - /url: /connected-apps - generic [ref=e117]: 󱘖 - generic [ref=e119]: Integrations - link "User & Roles" [ref=e120] [cursor=pointer]: - /url: /users - generic [ref=e122]: 󰭘 - generic [ref=e124]: User & Roles - generic [ref=e125]: - option "Booking Portal" [ref=e126] [cursor=pointer]: - generic [ref=e128]: 󱃁 - generic [ref=e130]: Booking Portal - generic [ref=e134]: 󰅀 - text: 󰖟 󰟙 - generic [ref=e135]: - option "JaneAI" [ref=e136] [cursor=pointer]: - generic [ref=e138]: 󱙺 - generic [ref=e140]: JaneAI - generic [ref=e144]: 󰅀 - text: 󱜹 - generic [ref=e146]: Feedback - link "Voting Portal" [ref=e147] [cursor=pointer]: - /url: /voting-portal - generic [ref=e149]: 󰔔 - generic [ref=e151]: Voting Portal - generic [ref=e153]: - generic [ref=e154]: Kinnovis GmbH © 2026 - generic [ref=e155]: v2026.05.04 - main [ref=e156]: - generic [ref=e158]: - generic [ref=e160]: - generic [ref=e163]: - generic [ref=e164]: Sales - heading "Customers" [level=1] [ref=e166]: - generic [ref=e167]: Customers - generic [ref=e169]: - link "Create customer" [ref=e170] [cursor=pointer]: - /url: /customers/create - generic [ref=e171]: - generic [ref=e172]: 󰐕 - text: Create customer - button [ref=e173] [cursor=pointer]: - generic [ref=e177]: 󰍝 - generic [ref=e179]: - generic [ref=e181]: - button "Filters Reset filters" [ref=e182] [cursor=pointer]: - generic [ref=e183]: - heading "Filters" [level=3] [ref=e186] - generic [ref=e188]: - button "Reset filters" [ref=e190]: - generic [ref=e191]: - generic [ref=e192]: 󰑐 - text: Reset filters - generic [ref=e196]: 󰅀 - generic [ref=e202]: - generic [ref=e207]: - generic [ref=e209]: 󰍉 - textbox "Search" [active] [ref=e211]: +55 93 4522 6249 - button "Clear Search" [ref=e213] [cursor=pointer]: 󰅙 - generic: - generic: Search - combobox [ref=e217]: - generic [ref=e218]: - generic: Location - combobox "Location" [ref=e220] - button [ref=e222] [cursor=pointer]: 󰍝 - combobox [ref=e226]: - generic [ref=e227]: - generic: Has past due invoice(s) - combobox "Has past due invoice(s)" [ref=e229] - button [ref=e231] [cursor=pointer]: 󰍝 - combobox [ref=e235]: - generic [ref=e236]: - generic: Status - combobox "Status" [ref=e238] - button [ref=e240] [cursor=pointer]: 󰍝 - generic [ref=e242]: - table [ref=e246]: - rowgroup [ref=e247]: - row "Customer no. Name Location(s) Email address Phone number Status Past due invoice(s) Created at" [ref=e248]: - columnheader "Customer no." [ref=e249]: - button "Customer no." [ref=e250] [cursor=pointer]: - generic [ref=e252]: Customer no. - generic [ref=e255]: 󰁝 - columnheader "Name" [ref=e256]: - button "Name" [ref=e257] [cursor=pointer]: - generic [ref=e259]: Name - generic [ref=e262]: 󰁝 - columnheader "Location(s)" [ref=e263]: - button "Location(s)" [ref=e264] [cursor=pointer]: - generic [ref=e266]: Location(s) - generic [ref=e269]: 󰁝 - columnheader "Email address" [ref=e270]: - button "Email address" [ref=e271] [cursor=pointer]: - generic [ref=e273]: Email address - generic [ref=e276]: 󰁝 - columnheader "Phone number" [ref=e277]: - generic [ref=e280]: Phone number - columnheader "Status" [ref=e281]: - button "Status" [ref=e282] [cursor=pointer]: - generic [ref=e284]: Status - generic [ref=e287]: 󰁝 - columnheader "Past due invoice(s)" [ref=e288]: - button "Past due invoice(s)" [ref=e289] [cursor=pointer]: - generic [ref=e291]: Past due invoice(s) - generic [ref=e294]: 󰁝 - columnheader "Created at" [ref=e295]: - button "Created at" [ref=e296] [cursor=pointer]: - generic [ref=e298]: Created at - generic [ref=e301]: 󰁝 - row [ref=e302]: - columnheader [ref=e303] - rowgroup [ref=e304]: - row "No data yet" [ref=e305]: - cell "No data yet" [ref=e306]: - generic [ref=e307]: No data yet - generic [ref=e310]: - generic [ref=e311]: - generic [ref=e312]: "Items per page:" - combobox [ref=e315]: - generic [ref=e317] [cursor=pointer]: - generic [ref=e319]: "10" - combobox "Items per page:": "10" - generic [ref=e321]: 󰍝 - generic [ref=e322]: 0-0 of 0 - generic [ref=e323]: - button [disabled]: - generic: - generic: 󰘀 - button [disabled]: - generic: - generic: 󰅁 - button [disabled]: - generic: - generic: 󰅂 - button [disabled]: - generic: - generic: 󰘁 - generic: - tooltip - tooltip - tooltip - tooltip ``` # Test source ```ts 45 | await expectMultiLocatorToHaveText(newDetailsPage.generalCard.locations, customerLocationNames); 46 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.type, newCustomer.type); 47 | 48 | if (newCustomer.type === 'business') { 49 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.companyName, newCustomer.companyName); 50 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.vatType, newCustomer.vatType?.name); 51 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.vatNumber, newCustomer.vatNumber); 52 | } else { 53 | await expect(newDetailsPage.generalCard.companyName).toBeHidden(); 54 | await expect(newDetailsPage.generalCard.vatType).toBeHidden(); 55 | await expect(newDetailsPage.generalCard.vatNumber).toBeHidden(); 56 | } 57 | 58 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.name, customerFullName); 59 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.email, newCustomer.email); 60 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.phoneNumber, customerPhone); 61 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.language, newCustomer.language.id); 62 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.address, customerAddress); 63 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.note, newCustomer.note); 64 | }); 65 | 66 | await test.step('verify new customer on list page', async () => { 67 | const listPage = await newDetailsPage.returnToCustomerListPage(); 68 | 69 | await listPage.searchTextField.fill(newCustomer.email); 70 | 71 | await expect(listPage.dataTable.getRows()).toHaveCount(1); 72 | await expectDataTableTextColumnToHaveText(listPage.dataTable, customerTableColumnTestIds.id, customerId); 73 | await expectDataTableTextColumnToHaveText(listPage.dataTable, customerTableColumnTestIds.name, customerTitle); 74 | await expectDataTableChipsColumnToHaveText( 75 | listPage.dataTable, 76 | customerTableColumnTestIds.locations, 77 | customerLocationNames 78 | ); 79 | await expectDataTableTextColumnToHaveText( 80 | listPage.dataTable, 81 | customerTableColumnTestIds.email, 82 | newCustomer.email 83 | ); 84 | await expectDataTableTextColumnToHaveText( 85 | listPage.dataTable, 86 | customerTableColumnTestIds.phoneNumber, 87 | customerPhone 88 | ); 89 | await expectDataTableChipsColumnToHaveText( 90 | listPage.dataTable, 91 | customerTableColumnTestIds.status, 92 | newCustomer.status 93 | ); 94 | await expectDataTableTextColumnToHaveText( 95 | listPage.dataTable, 96 | customerTableColumnTestIds.createdAt, 97 | customerCreatedAt 98 | ); 99 | }); 100 | }); 101 | } 102 | 103 | test('edits the email', async ({ createCustomer, customerDetailsPage }) => { 104 | const customer = await createCustomer(getCustomerCreateData()); 105 | const newEmail = faker.internet.email(); 106 | 107 | await customerDetailsPage.goto(customer.id); 108 | 109 | const newDetailsPage = await test.step('edit customer email', async () => { 110 | const dialog = await customerDetailsPage.openCustomerEditGeneralDialog(); 111 | return dialog.edit({ email: newEmail }); 112 | }); 113 | 114 | await test.step('verify new email on details page', async () => { 115 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.email, newEmail); 116 | }); 117 | 118 | await test.step('verify new email on list page', async () => { 119 | const listPage = await newDetailsPage.returnToCustomerListPage(); 120 | await listPage.searchTextField.fill(newEmail); 121 | await expect(listPage.dataTable.getRows()).toHaveCount(1); 122 | }); 123 | }); 124 | 125 | test('edits the phone number', async ({ createCustomer, customerDetailsPage }) => { 126 | const customer = await createCustomer(getCustomerCreateData()); 127 | const newCountry = countries.BR; 128 | const newPhone = getCustomerPhone(newCountry); 129 | const formattedNewPhone = formatPhone(newPhone); 130 | 131 | await customerDetailsPage.goto(customer.id); 132 | 133 | const newDetailsPage = await test.step('edit customer phone number', async () => { 134 | const dialog = await customerDetailsPage.openCustomerEditGeneralDialog(); 135 | return dialog.edit({ phone: newPhone, country: newCountry }); 136 | }); 137 | 138 | await test.step('verify new phone number on details page', async () => { 139 | await expectSingleLocatorToHaveText(newDetailsPage.generalCard.phoneNumber, formattedNewPhone); 140 | }); 141 | 142 | await test.step('verify new phone number on list page', async () => { 143 | const listPage = await newDetailsPage.returnToCustomerListPage(); 144 | await listPage.searchTextField.fill(formattedNewPhone); > 145 | await expect(listPage.dataTable.getRows()).toHaveCount(1); | ^ Error: expect(locator).toHaveCount(expected) failed 146 | }); 147 | }); 148 | 149 | test('rejects an edit with a duplicate email', async ({ createCustomer, customerDetailsPage }) => { 150 | const existing = await createCustomer(getCustomerCreateData()); 151 | const editing = await createCustomer(getCustomerCreateData()); 152 | 153 | await customerDetailsPage.goto(editing.id); 154 | 155 | const dialog = await test.step('submit edit with duplicate email', async () => { 156 | const editDialog = await customerDetailsPage.openCustomerEditGeneralDialog(); 157 | await editDialog.fill({ email: existing.email }); 158 | await editDialog.submit(); 159 | return editDialog; 160 | }); 161 | 162 | await test.step('verify errors on form', async () => { 163 | await expect(dialog.errors).toHaveCountGreaterThan(0); 164 | }); 165 | }); 166 | 167 | test('rejects an edit with a duplicate phone number', async ({ createCustomer, customerDetailsPage }) => { 168 | const existing = await createCustomer(getCustomerCreateData()); 169 | const editing = await createCustomer(getCustomerCreateData()); 170 | 171 | await customerDetailsPage.goto(editing.id); 172 | 173 | const dialog = await test.step('submit edit with duplicate phone number', async () => { 174 | const editDialog = await customerDetailsPage.openCustomerEditGeneralDialog(); 175 | await editDialog.fill({ phone: existing.phone, country: existing.country }); 176 | await editDialog.submit(); 177 | return editDialog; 178 | }); 179 | 180 | await test.step('verify errors on form', async () => { 181 | await expect(dialog.errors).toHaveCountGreaterThan(0); 182 | }); 183 | }); 184 | 185 | test('rejects an edit with a vat number that does not match the vat type', async ({ 186 | createCustomer, 187 | customerDetailsPage, 188 | }) => { 189 | const customer = await createCustomer(getCustomerCreateData({ type: 'business' })); 190 | 191 | await customerDetailsPage.goto(customer.id); 192 | 193 | const dialog = await test.step('submit edit with mismatched vat type and number', async () => { 194 | const editDialog = await customerDetailsPage.openCustomerEditGeneralDialog(); 195 | await editDialog.fill({ vatType: vatTypes.eu_vat, vatNumber: 'INVALID-VAT-1234' }); 196 | await editDialog.submit(); 197 | return editDialog; 198 | }); 199 | 200 | await test.step('verify errors on form', async () => { 201 | await expect(dialog.errors).toHaveCountGreaterThan(0); 202 | }); 203 | }); 204 | 205 | test('rejects an edit without all required fields', async ({ createCustomer, customerDetailsPage }) => { 206 | const customer = await createCustomer(getCustomerCreateData({ type: 'private' })); 207 | 208 | await customerDetailsPage.goto(customer.id); 209 | 210 | const dialog = await test.step('submit edit clearing required fields', async () => { 211 | const editDialog = await customerDetailsPage.openCustomerEditGeneralDialog(); 212 | await editDialog.fill({ firstName: '', lastName: '' }); 213 | await editDialog.submit(); 214 | return editDialog; 215 | }); 216 | 217 | await test.step('verify errors on form', async () => { 218 | await expect(dialog.errors).toHaveCountGreaterThan(0); 219 | }); 220 | }); 221 | ```