diff --git a/tests/playwright/pageObjects/browser-page.ts b/tests/playwright/pageObjects/browser-page.ts index 073cb8b004..cdd6b86abf 100755 --- a/tests/playwright/pageObjects/browser-page.ts +++ b/tests/playwright/pageObjects/browser-page.ts @@ -484,7 +484,9 @@ export class BrowserPage extends BasePage { this.hashFieldNameInput = page.getByTestId('field-name') this.hashFieldValueEditor = page.getByTestId('hash_value-editor') this.hashTtlFieldInput = page.getByTestId('hash-ttl') - this.listKeyElementEditorInput = page.getByTestId('list_value-editor-') + this.listKeyElementEditorInput = page.locator( + '[data-testid^="list_value-editor-"]', + ) this.stringKeyValueInput = page.getByTestId('string-value') this.jsonKeyValueInput = page.locator('div[data-mode-id=json] textarea') this.jsonUploadInput = page.getByTestId('upload-input-file') @@ -1019,7 +1021,11 @@ export class BrowserPage extends BasePage { async editListKeyValue(value: string): Promise { await this.listElementsList.hover() await this.editListButton.click() - await this.listKeyElementEditorInput.fill(value, { + + // Wait for any list editor to appear - this is a legacy method + const editorInput = this.listKeyElementEditorInput.first() + await expect(editorInput).toBeVisible() + await editorInput.fill(value, { timeout: 0, noWaitAfter: false, }) @@ -1718,4 +1724,110 @@ export class BrowserPage extends BasePage { }) .toContain(expectedValue) } + async editListElementValue(newValue: string): Promise { + await this.listElementsList.first().hover() + await this.editListButton.first().click() + + // Wait for any list editor to appear - don't assume specific index + const editorInput = this.listKeyElementEditorInput.first() + await expect(editorInput).toBeVisible() + await editorInput.fill(newValue, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() + + // Wait for the editor to close and changes to be applied + await expect(editorInput).not.toBeVisible() + + // Wait for the new value to appear in the first list element + await expect(this.listElementsList.first()).toContainText(newValue) + } + + async cancelListElementEdit(newValue: string): Promise { + await this.listElementsList.first().hover() + await this.editListButton.first().click() + + // Wait for any list editor to appear - don't assume specific index + const editorInput = this.listKeyElementEditorInput.first() + await expect(editorInput).toBeVisible() + await editorInput.fill(newValue, { + timeout: 0, + noWaitAfter: false, + }) + + // Cancel using Escape key + await this.page.keyboard.press('Escape') + + // Wait for the editor to close + await expect(editorInput).not.toBeVisible() + } + + async addElementsToList( + elements: string[], + position: AddElementInList = AddElementInList.Tail, + ): Promise { + if (await this.toast.isCloseButtonVisible()) { + await this.toast.closeToast() + } + await this.addKeyValueItemsButton.click() + + if (position === AddElementInList.Head) { + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await expect(this.removeFromHeadSelection).not.toBeVisible() + } + + for (let i = 0; i < elements.length; i += 1) { + await this.getListElementInput(i).click() + await this.getListElementInput(i).fill(elements[i]) + if (elements.length > 1 && i < elements.length - 1) { + await this.addAdditionalElement.click() + } + } + await this.saveElementButton.click() + } + + async removeListElementsFromTail(count: number): Promise { + await this.removeElementFromListIconButton.click() + await this.countInput.fill(count.toString()) + await this.removeElementFromListButton.click() + await this.confirmRemoveListElementButton.click() + } + + async removeListElementsFromHead(count: number): Promise { + await this.removeElementFromListIconButton.click() + await this.countInput.fill(count.toString()) + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await this.removeElementFromListButton.click() + await this.confirmRemoveListElementButton.click() + } + + async verifyListContainsElements( + expectedElements: string[], + ): Promise { + const displayedElements = await this.getAllListElements() + expectedElements.forEach((expectedElement) => { + expect(displayedElements).toContain(expectedElement) + }) + } + + async verifyListDoesNotContainElements( + unwantedElements: string[], + ): Promise { + const displayedElements = await this.getAllListElements() + unwantedElements.forEach((unwantedElement) => { + expect(displayedElements).not.toContain(unwantedElement) + }) + } + + async waitForListLengthToUpdate(expectedLength: number): Promise { + await expect + .poll(async () => { + const keyLength = await this.getKeyLength() + return parseInt(keyLength, 10) + }) + .toBe(expectedLength) + } } diff --git a/tests/playwright/tests/browser/keys-edit/edit-list-key.spec.ts b/tests/playwright/tests/browser/keys-edit/edit-list-key.spec.ts new file mode 100644 index 0000000000..a467931560 --- /dev/null +++ b/tests/playwright/tests/browser/keys-edit/edit-list-key.spec.ts @@ -0,0 +1,248 @@ +import { faker } from '@faker-js/faker' + +import { BrowserPage } from '../../../pageObjects/browser-page' +import { test, expect } from '../../../fixtures/test' +import { ossStandaloneConfig } from '../../../helpers/conf' +import { AddElementInList } from '../../../helpers/constants' +import { + addStandaloneInstanceAndNavigateToIt, + navigateToStandaloneInstance, +} from '../../../helpers/utils' + +test.describe('Browser - Edit Key Operations - List Key Editing', () => { + let browserPage: BrowserPage + let keyName: string + let cleanupInstance: () => Promise + + test.beforeEach(async ({ page, api: { databaseService } }) => { + browserPage = new BrowserPage(page) + keyName = faker.string.alphanumeric(10) + cleanupInstance = await addStandaloneInstanceAndNavigateToIt( + page, + databaseService, + ) + + await navigateToStandaloneInstance(page) + }) + + test.afterEach(async ({ api: { keyService } }) => { + // Clean up: delete the key if it exists + try { + await keyService.deleteKeyByNameApi( + keyName, + ossStandaloneConfig.databaseName, + ) + } catch (error) { + // Key might already be deleted in test, ignore error + } + + await cleanupInstance() + }) + + test('should edit list element value successfully', async ({ + api: { keyService }, + }) => { + // Arrange: Create a list key with multiple elements + const listElements = [ + faker.lorem.word(), + faker.lorem.word(), + faker.lorem.word(), + ] + const newElementValue = faker.lorem.word() + + await keyService.addListKeyApi( + { keyName, elements: listElements }, + ossStandaloneConfig, + ) + + // Open key details and verify initial content + await browserPage.openKeyDetailsAndVerify(keyName) + await browserPage.verifyListContainsElements(listElements) + await browserPage.verifyKeyLength(listElements.length.toString()) + + // Edit the first element value + await browserPage.editListElementValue(newElementValue) + + // Verify the element was updated + await browserPage.verifyListContainsElements([newElementValue]) + await browserPage.verifyListDoesNotContainElements([listElements[0]]) + await browserPage.verifyKeyLength(listElements.length.toString()) // Length should remain the same + }) + + test('should cancel list element edit successfully', async ({ + api: { keyService }, + }) => { + // Arrange: Create a list key + const listElements = [faker.lorem.word(), faker.lorem.word()] + const attemptedValue = faker.lorem.word() + + await keyService.addListKeyApi( + { keyName, elements: listElements }, + ossStandaloneConfig, + ) + + // Open key details and start edit but cancel + await browserPage.openKeyDetailsAndVerify(keyName) + await browserPage.verifyListContainsElements(listElements) + + // Start edit but cancel + await browserPage.cancelListElementEdit(attemptedValue) + + // Verify original content is preserved + await browserPage.verifyListContainsElements(listElements) + await browserPage.verifyListDoesNotContainElements([attemptedValue]) + }) + + test('should add elements to list tail successfully', async ({ + api: { keyService }, + }) => { + // Arrange: Create a list key with initial elements + const initialElements = [faker.lorem.word(), faker.lorem.word()] + const newElements = [faker.lorem.word(), faker.lorem.word()] + + await keyService.addListKeyApi( + { keyName, elements: initialElements }, + ossStandaloneConfig, + ) + + // Open key details and add elements to tail + await browserPage.openKeyDetailsAndVerify(keyName) + await browserPage.verifyListContainsElements(initialElements) + + await browserPage.addElementsToList(newElements, AddElementInList.Tail) + + // Verify all elements are present and length is updated + const expectedLength = initialElements.length + newElements.length + await browserPage.waitForListLengthToUpdate(expectedLength) + await browserPage.verifyListContainsElements([ + ...initialElements, + ...newElements, + ]) + }) + + test('should add elements to list head successfully', async ({ + api: { keyService }, + }) => { + // Arrange: Create a list key with initial elements + const initialElements = [faker.lorem.word(), faker.lorem.word()] + const newElements = [faker.lorem.word()] + + await keyService.addListKeyApi( + { keyName, elements: initialElements }, + ossStandaloneConfig, + ) + + // Open key details and add elements to head + await browserPage.openKeyDetailsAndVerify(keyName) + await browserPage.verifyListContainsElements(initialElements) + + await browserPage.addElementsToList(newElements, AddElementInList.Head) + + // Verify all elements are present and length is updated + const expectedLength = initialElements.length + newElements.length + await browserPage.waitForListLengthToUpdate(expectedLength) + await browserPage.verifyListContainsElements([ + ...newElements, + ...initialElements, + ]) + }) + + test('should remove elements from list tail successfully', async ({ + api: { keyService }, + }) => { + // Arrange: Create a list key with multiple elements + const listElements = [ + faker.lorem.word(), + faker.lorem.word(), + faker.lorem.word(), + faker.lorem.word(), + ] + const removeCount = 2 + + await keyService.addListKeyApi( + { keyName, elements: listElements }, + ossStandaloneConfig, + ) + + // Open key details and remove elements from tail + await browserPage.openKeyDetailsAndVerify(keyName) + await browserPage.verifyListContainsElements(listElements) + + await browserPage.removeListElementsFromTail(removeCount) + + // Verify length is updated (Redis lists remove from the right/tail) + const expectedLength = listElements.length - removeCount + await browserPage.waitForListLengthToUpdate(expectedLength) + await browserPage.verifyKeyLength(expectedLength.toString()) + + // Verify the correct elements were removed (last 2 elements should be gone) + const remainingElements = listElements.slice(0, -removeCount) // Keep all but last 2 + const removedElements = listElements.slice(-removeCount) // Last 2 elements + await browserPage.verifyListContainsElements(remainingElements) + await browserPage.verifyListDoesNotContainElements(removedElements) + }) + + test('should remove elements from list head successfully', async ({ + api: { keyService }, + }) => { + // Arrange: Create a list key with multiple elements + const listElements = [ + faker.lorem.word(), + faker.lorem.word(), + faker.lorem.word(), + faker.lorem.word(), + ] + const removeCount = 1 + + await keyService.addListKeyApi( + { keyName, elements: listElements }, + ossStandaloneConfig, + ) + + // Open key details and remove elements from head + await browserPage.openKeyDetailsAndVerify(keyName) + await browserPage.verifyListContainsElements(listElements) + + await browserPage.removeListElementsFromHead(removeCount) + + // Verify length is updated (Redis lists remove from the left/head) + const expectedLength = listElements.length - removeCount + await browserPage.waitForListLengthToUpdate(expectedLength) + await browserPage.verifyKeyLength(expectedLength.toString()) + + // Verify the correct elements were removed (first element should be gone) + const remainingElements = listElements.slice(removeCount) // Skip first element + const removedElements = listElements.slice(0, removeCount) // First element + await browserPage.verifyListContainsElements(remainingElements) + await browserPage.verifyListDoesNotContainElements(removedElements) + }) + + test('should handle removing all elements from list', async ({ + api: { keyService }, + }) => { + // Arrange: Create a list key with a few elements + const listElements = [faker.lorem.word(), faker.lorem.word()] + + await keyService.addListKeyApi( + { keyName, elements: listElements }, + ossStandaloneConfig, + ) + + // Open key details and remove all elements + await browserPage.openKeyDetailsAndVerify(keyName) + await browserPage.verifyListContainsElements(listElements) + + await browserPage.removeListElementsFromTail(listElements.length) + + // Verify list is empty (key should be removed when list becomes empty) + await expect + .poll(async () => { + try { + return await browserPage.isKeyDetailsOpen(keyName) + } catch { + return false + } + }) + .toBe(false) + }) +})