← Back
Editing: category-actions.spec.ts
/** * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ import { expect, type Locator, type Page, type TestInfo, test } from '@playwright/test' import { login } from '../support/login' function appNavigation(page: Page): Locator { return page.getByRole('navigation').filter({ has: page.getByRole('button', { name: 'New category', exact: true }), }).first() } function newCategoryButton(page: Page): Locator { return page.getByRole('button', { name: 'New category', exact: true }) } function contentNewNoteButton(page: Page): Locator { return page.getByRole('button', { name: 'New note', exact: true }) } function notesSearchField(page: Page): Locator { return page.getByRole('textbox', { name: 'Search for notes', exact: true }) } function notesViewNewNoteButton(page: Page): Locator { return notesSearchField(page) .locator('xpath=ancestor::div[contains(@class, "content-list__search")][1]') .getByRole('button', { name: 'New note', exact: true }) } function navigationRow(page: Page, name: string): Locator { return page.getByRole('link', { name, exact: true }) .locator('xpath=ancestor::li[1]') .first() } function navigationLink(page: Page, name: string): Locator { return page.getByRole('link', { name, exact: true }) } async function expectNavigationItemActive(page: Page, name: string): Promise<void> { await expect(navigationLink(page, name)).toHaveAttribute('aria-current', 'page') } function uniqueCategoryName(prefix: string, testInfo: TestInfo): string { return `Playwright ${prefix} ${testInfo.parallelIndex}-${Date.now()}` } function currentNoteId(page: Page): number | null { const match = page.url().match(/\/note\/(\d+)(?:\?.*)?$/) return match ? Number(match[1]) : null } async function openNotesApp(page: Page): Promise<void> { await page.goto('/index.php/apps/notes/') await expect(newCategoryButton(page)).toBeVisible() await expect(contentNewNoteButton(page)).toHaveCount(1) await expect(contentNewNoteButton(page)).toBeVisible() } async function createCategory(page: Page, name: string): Promise<void> { const navigation = appNavigation(page) await newCategoryButton(page).click() const input = navigation.getByPlaceholder('New category', { exact: true }) await expect(input).toBeVisible() await input.fill(name) await input.press('Enter') await expect(navigationRow(page, name)).toBeVisible() await expectNavigationItemActive(page, name) } async function waitForNewNoteRoute(page: Page, previousNoteId: number | null): Promise<number> { await expect.poll(() => currentNoteId(page)).not.toBe(previousNoteId) const noteId = currentNoteId(page) if (noteId === null || noteId === previousNoteId) { throw new Error('Expected a new note route after creating a note') } return noteId } async function ensureNotesView(page: Page): Promise<void> { if (await notesSearchField(page).isVisible()) { return } const previousNoteId = currentNoteId(page) await contentNewNoteButton(page).click() await waitForNewNoteRoute(page, previousNoteId) await expect(notesSearchField(page)).toBeVisible() } async function createNoteInSelectedCategory(page: Page, category: string): Promise<number> { await ensureNotesView(page) const previousNoteId = currentNoteId(page) await notesViewNewNoteButton(page).click() const noteId = await waitForNewNoteRoute(page, previousNoteId) await expect(navigationRow(page, category).locator('.app-navigation-entry__counter-wrapper')).toContainText('1') return noteId } async function openCategoryActions(page: Page, category: string): Promise<void> { const row = navigationRow(page, category) await expect(row).toBeVisible() await row.hover() const actionsButton = row.getByRole('button', { name: 'Actions', exact: true }) await expect(actionsButton).toBeVisible() await actionsButton.click() } test.describe('Category actions', () => { test.beforeEach(async ({ page }) => { await login(page) await openNotesApp(page) }) test('renames a category from the actions menu', async ({ page }, testInfo: TestInfo) => { const category = uniqueCategoryName('rename', testInfo) const renamedCategory = `${category} renamed` const noteId = await (async () => { await createCategory(page, category) return createNoteInSelectedCategory(page, category) })() await openCategoryActions(page, category) await page.getByRole('menuitem', { name: 'Rename category', exact: true }).click() const renameInput = appNavigation(page).getByPlaceholder(category, { exact: true }) await expect(renameInput).toBeVisible() await renameInput.fill(renamedCategory) await renameInput.press('Enter') await expect(navigationRow(page, renamedCategory)).toBeVisible() await expectNavigationItemActive(page, renamedCategory) await expect(navigationRow(page, renamedCategory).locator('.app-navigation-entry__counter-wrapper')).toContainText('1') await expect(navigationRow(page, category)).toHaveCount(0) await expect(page).toHaveURL(new RegExp(`/note/${noteId}(\\?.*)?$`)) }) test('deletes a category from the actions menu', async ({ page }, testInfo: TestInfo) => { const category = uniqueCategoryName('delete', testInfo) await createCategory(page, category) const noteId = await createNoteInSelectedCategory(page, category) const deletedNoteUrl = new RegExp(`/note/${noteId}(\\?.*)?$`) await openCategoryActions(page, category) await page.getByRole('menuitem', { name: 'Delete category', exact: true }).click() const confirmationText = `Delete category "${category}" and its 1 note?` await expect(page.getByText(confirmationText, { exact: true })).toBeVisible() await page.getByRole('button', { name: 'Delete', exact: true }).click() await expect(navigationRow(page, category)).toHaveCount(0) await expectNavigationItemActive(page, 'All notes') await expect(page).not.toHaveURL(deletedNoteUrl) }) })
Save File
Cancel