From c21f9eeda9d6aff900bea911466d602a9ed9fe7a Mon Sep 17 00:00:00 2001 From: HusneShabbir Date: Fri, 4 Sep 2026 17:25:46 +0530 Subject: [PATCH 1/4] test(intelligent-assistant): add focused notebook Release 2.1 E2E coverage Extend Playwright coverage for inline rename POMs, upload modal UX, card counts, overwrite flow, and compact overlay/docked modes with scoped dialog locators that work in nested MUI panels. Co-authored-by: Cursor --- .../lightspeed.notebooks-compact.test.ts | 230 ++++++----------- .../e2e-tests/lightspeed.notebooks.test.ts | 239 +++++++++-------- .../pages/NotebookAddDocumentModalPage.ts | 78 +++++- .../e2e-tests/pages/NotebookSurfacePage.ts | 240 +++++++++++++++++- .../e2e-tests/utils/notebooks.ts | 8 + 5 files changed, 500 insertions(+), 295 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks-compact.test.ts b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks-compact.test.ts index 01f66003a18..5e51c6ce7d8 100644 --- a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks-compact.test.ts +++ b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks-compact.test.ts @@ -16,7 +16,10 @@ import { test, expect, type Page } from '@playwright/test'; -import { NotebookSurfacePage } from './pages/NotebookSurfacePage'; +import { + NotebookSurfacePage, + NOTEBOOK_UNTITLED_GRID_NAME, +} from './pages/NotebookSurfacePage'; import type { LightspeedMessages } from './utils/translations'; import { bootstrapLightspeedE2ePage } from './utils/lightspeedE2eSetup'; import { @@ -52,20 +55,22 @@ for (const mode of ['Overlay', 'Dock to window'] as const) { const boot = await bootstrapLightspeedE2ePage(browser); sharedPage = boot.page; translations = boot.translations; - notebooks = new NotebookSurfacePage(sharedPage, translations); + notebooks = new NotebookSurfacePage( + sharedPage, + translations, + boot.locale, + ); }); test('tabs are visible and notebooks tab selectable', async () => { await switchToCompactNotebooks(sharedPage, translations, mode); - await expect( - sharedPage.getByRole('tab', { name: translations['tabs.chat'] }), - ).toBeVisible(); - const notebooksTab = sharedPage.getByRole('tab', { - name: translations['tabs.notebooks'], - }); - await expect(notebooksTab).toBeVisible(); - await expect(notebooksTab).toHaveAttribute('aria-selected', 'true'); + await expect(notebooks.chatTab()).toBeVisible(); + await expect(notebooks.notebooksTab()).toBeVisible(); + await expect(notebooks.notebooksTab()).toHaveAttribute( + 'aria-selected', + 'true', + ); }); test('empty notebook list shows create action', async () => { @@ -80,148 +85,81 @@ for (const mode of ['Overlay', 'Dock to window'] as const) { }); test('header actions visible in compact mode: close, add, sidebar toggle', async () => { - const header = sharedPage.locator('.pf-chatbot__header'); - - await expect( - header.getByRole('button', { - name: translations['notebook.view.close'], - }), - ).toBeVisible(); - await expect( - header.getByRole('button', { - name: translations['notebook.view.documents.add'], - }), - ).toBeVisible(); - - const collapseLabel = translations['notebook.view.sidebar.collapse']; - const expandLabel = translations['notebook.view.sidebar.expand']; - const sidebarToggle = header.getByRole('button', { - name: new RegExp(`${collapseLabel}|${expandLabel}`), - }); - await expect(sidebarToggle).toBeVisible(); + await notebooks.expectCompactHeaderActionsVisible(); }); test('NotebookView topBar close button hidden in compact mode', async () => { - const closeButtons = sharedPage.getByRole('button', { - name: translations['notebook.view.close'], - }); - await expect(closeButtons).toHaveCount(1); + await notebooks.expectSingleNotebookCloseButton(); }); test('upload modal opens and renders within panel', async () => { - const header = sharedPage.locator('.pf-chatbot__header'); - const addButton = header.getByRole('button', { - name: translations['notebook.view.documents.add'], - }); - await addButton.click(); - - // In compact mode, disablePortal renders the MUI Dialog inline. The - // ChatbotModal already has role="dialog", so scope to the MUI one. - const dialog = sharedPage.locator( - '[role="dialog"][aria-labelledby="add-document-modal-title"]', - ); - await expect(dialog).toBeVisible({ timeout: 10_000 }); - await expect(dialog.locator('#add-document-modal-title')).toBeVisible(); - await expect( - dialog.locator( - `text=${translations['notebook.upload.modal.dragDropTitle']}`, - ), - ).toBeVisible(); + await notebooks.clickCompactHeaderAddDocument(); - await dialog - .locator('button', { hasText: translations['modal.cancel'] }) - .click(); + const uploadModal = notebooks.uploadDocumentModal(); + await expect(uploadModal.dialog()).toBeVisible({ timeout: 10_000 }); + await uploadModal.expectUploadAreaFullyDescribed(); + await uploadModal.dismiss(); }); test('sidebar toggle mirrors icon direction', async () => { - const header = sharedPage.locator('.pf-chatbot__header'); - const collapseLabel = translations['notebook.view.sidebar.collapse']; - const expandLabel = translations['notebook.view.sidebar.expand']; - - const toggle = header.getByRole('button', { - name: new RegExp(`${collapseLabel}|${expandLabel}`), - }); - await expect(toggle).toBeVisible(); - - const initialLabel = await toggle.getAttribute('aria-label'); - - await toggle.click(); - await sharedPage.waitForTimeout(300); - - const newLabel = await toggle.getAttribute('aria-label'); - expect(newLabel).not.toBe(initialLabel); - - const expectedLabel = - initialLabel === collapseLabel ? expandLabel : collapseLabel; - expect(newLabel).toBe(expectedLabel); - - await toggle.click(); - await sharedPage.waitForTimeout(300); - const restoredLabel = await toggle.getAttribute('aria-label'); - expect(restoredLabel).toBe(initialLabel); + await notebooks.toggleCompactSidebarAndExpectLabelFlip(); }); test('file picker works in compact upload modal', async ({}, testInfo) => { const { absolutePath } = localeNotebookUpload1Path(testInfo.project.name); - const header = sharedPage.locator('.pf-chatbot__header'); - await header - .getByRole('button', { - name: translations['notebook.view.documents.add'], - }) - .click(); - - const dialog = sharedPage.locator( - '[role="dialog"][aria-labelledby="add-document-modal-title"]', + await notebooks.clickCompactHeaderAddDocument(); + const uploadModal = notebooks.uploadDocumentModal(); + await expect(uploadModal.dialog()).toBeVisible({ timeout: 10_000 }); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.expectStagedFileCountCaptionVisible( + 1, + NOTEBOOK_SESSION_MAX_DOCUMENTS, ); - await expect(dialog).toBeVisible({ timeout: 10_000 }); + await uploadModal.clickCancel(); + }); - const fileInput = dialog.locator('input[type="file"]'); - await fileInput.setInputFiles([absolutePath]); + test('header add: upload completes and resource appears in panel', async ({}, testInfo) => { + const { absolutePath, fileName } = localeNotebookUpload1Path( + testInfo.project.name, + ); - const stagedCaption = translations['notebook.upload.modal.selectedFiles'] - .replace('{{count}}', '1') - .replace('{{max}}', String(NOTEBOOK_SESSION_MAX_DOCUMENTS)); - await expect(dialog.locator(`text=${stagedCaption}`)).toBeVisible({ - timeout: 5_000, - }); + await notebooks.clickCompactHeaderAddDocument(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); + await notebooks.ensureDocumentSidebarExpanded(); + await notebooks.expectDocumentFileListedInSidebar(fileName); + }); - await dialog - .locator('button', { hasText: translations['modal.cancel'] }) - .click(); + test('remove resource modal renders within panel', async () => { + await notebooks.ensureDocumentSidebarExpanded(); + await notebooks.openDeleteFirstDocumentConfirmation(); + await notebooks.cancelDeleteDocumentConfirmation(); + await notebooks.deleteFirstListedDocumentFromSidebarOverflowMenu(); + await notebooks.expectNotebookEditorUploadResourceButtonVisible(); }); test('switch tabs preserves notebook state', async () => { - await sharedPage - .getByRole('tab', { name: translations['tabs.chat'] }) - .click(); - await expect( - sharedPage.getByRole('tab', { name: translations['tabs.chat'] }), - ).toHaveAttribute('aria-selected', 'true'); + await notebooks.chatTab().click(); + await expect(notebooks.chatTab()).toHaveAttribute( + 'aria-selected', + 'true', + ); - await sharedPage - .getByRole('tab', { name: translations['tabs.notebooks'] }) - .click(); - await expect( - sharedPage.getByRole('tab', { - name: translations['tabs.notebooks'], - }), - ).toHaveAttribute('aria-selected', 'true'); + await notebooks.notebooksTab().click(); + await expect(notebooks.notebooksTab()).toHaveAttribute( + 'aria-selected', + 'true', + ); - // Notebook editor still shows (not reverted to list view) await expect(notebooks.uploadResourceHeading()).toBeVisible(); }); test('close notebook via header action', async () => { - const header = sharedPage.locator('.pf-chatbot__header'); - await header - .getByRole('button', { - name: translations['notebook.view.close'], - }) - .click(); + await notebooks.clickCompactHeaderCloseNotebook(); await expect(notebooks.myNotebooksHeading()).toBeVisible(); - // Empty notebooks (no uploaded documents) are auto-deleted on close await expect( notebooks.createNotebookFromEmptyStateButton(), ).toBeVisible(); @@ -233,45 +171,39 @@ for (const mode of ['Overlay', 'Dock to window'] as const) { await selectDisplayMode(sharedPage, translations, otherMode); - await expect( - sharedPage.getByRole('tab', { - name: translations['tabs.notebooks'], - }), - ).toHaveAttribute('aria-selected', 'true'); - + await expect(notebooks.notebooksTab()).toHaveAttribute( + 'aria-selected', + 'true', + ); await expect(notebooks.myNotebooksHeading()).toBeVisible(); }); test('switch to fullscreen preserves notebooks tab', async () => { await selectDisplayMode(sharedPage, translations, 'Fullscreen'); - await expect( - sharedPage.getByRole('tab', { - name: translations['tabs.notebooks'], - }), - ).toBeVisible(); + await expect(notebooks.notebooksTab()).toBeVisible(); }); - test('cleanup: delete created notebook', async () => { + test('cleanup: delete notebook modal renders within panel', async () => { await selectDisplayMode(sharedPage, translations, mode); - - await expect( - sharedPage.getByRole('tab', { - name: translations['tabs.notebooks'], - }), - ).toBeVisible(); - await sharedPage - .getByRole('tab', { name: translations['tabs.notebooks'] }) - .click(); + await notebooks.notebooksTab().click(); const card = notebooks.newestUntitledNotebookCard(); - if ((await card.count()) > 0) { - await notebooks.notebookCardOverflowMenuButton(card).click(); - await notebooks.deleteNotebookOverflowMenuItem().click(); - const confirmDelete = - notebooks.notebookDeleteConfirmationDialog('Untitled Notebook'); - await confirmDelete.confirmDeletion(); + if ((await card.count()) === 0) { + return; } + + await notebooks.notebookCardOverflowMenuButton(card).click(); + await notebooks.deleteNotebookOverflowMenuItem().click(); + + const confirmDelete = notebooks.notebookDeleteConfirmationDialog( + NOTEBOOK_UNTITLED_GRID_NAME, + ); + await confirmDelete.expectDialogVisible(); + await notebooks.expectNotebookDeleteDialogWithinChatbot( + NOTEBOOK_UNTITLED_GRID_NAME, + ); + await confirmDelete.confirmDeletion(); }); }); } diff --git a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts index 8ee5673234a..074f2addf36 100644 --- a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts +++ b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts @@ -27,6 +27,7 @@ import { NOTEBOOK_EDITOR_URL_RE, NOTEBOOK_SESSION_MAX_DOCUMENTS, notebookElevenFileStagingPaths, + notebookTenFileStagingPaths, notebookUnsupportedTypeFixturePath, } from './utils/notebooks'; import { substituteNotebookTemplate } from './utils/notebookTranslation'; @@ -70,6 +71,14 @@ test.describe('Intelligent assistant notebooks', () => { await uploadModal.clickCancel(); }); + test('upload modal: title close button dismisses dialog', async () => { + await notebooks.clickOpenUploadDocumentModal(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.expectUploadAreaFullyDescribed(); + await uploadModal.clickTitleClose(); + await expect(uploadModal.dialog()).toBeHidden(); + }); + test('document sidebar: collapse and expand', async () => { await notebooks.collapseThenExpandDocumentSidebar(); }); @@ -153,6 +162,19 @@ test.describe('Intelligent assistant notebooks', () => { await uploadModal.clickCancel(); }); + test('upload modal: dropzone disabled at ten staged files', async () => { + await notebooks.clickOpenUploadDocumentModal(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker(notebookTenFileStagingPaths()); + await uploadModal.expectStagedFileCountCaptionVisible( + NOTEBOOK_SESSION_MAX_DOCUMENTS, + NOTEBOOK_SESSION_MAX_DOCUMENTS, + ); + await uploadModal.expectDropzoneDisabled(); + await uploadModal.expectMaxReachedTooltipOnDropzoneHover(); + await uploadModal.clickCancel(); + }); + test('upload modal: unsupported extension rejected', async () => { await notebooks.clickOpenUploadDocumentModal(); const uploadModal = notebooks.uploadDocumentModal(); @@ -182,14 +204,69 @@ test.describe('Intelligent assistant notebooks', () => { await uploadModal.selectFilesViaBrowsePicker([absolutePath]); await uploadModal.clickAddFilesForStagedCount(1); + await expect(uploadModal.dialog()).toBeHidden({ timeout: 5_000 }); const overwriteModal = notebooks.notebookOverwriteConfirmModal(); await overwriteModal.expectDialogVisible(); + await expect(sharedPage.getByRole('dialog')).toHaveCount(1); await overwriteModal.expectListedOverwriteFile(fileName); await overwriteModal.clickBack(); + + uploadModal = notebooks.uploadDocumentModal(); + await expect(uploadModal.dialog()).toBeVisible(); await uploadModal.clickCancel(); - await notebooks.deleteFirstListedDocumentFromSidebarOverflowMenu(); - await notebooks.expectNotebookEditorUploadResourceButtonVisible(); + await notebooks.clickOpenUploadDocumentModal(); + uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); + await expect(uploadModal.dialog()).toBeHidden({ timeout: 5_000 }); + await overwriteModal.expectDialogVisible(); + await overwriteModal.clickUpload(); + await expect(overwriteModal.dialog()).toBeHidden({ timeout: 30_000 }); + await notebooks.expectDocumentFileListedInSidebar(fileName); + + await notebooks.clickCloseNotebookEditor(); + await notebooks.deleteNotebookCardFromGrid(NOTEBOOK_UNTITLED_GRID_NAME); + }); + + test('notebook card: zero and singular resource counts', async ({}, testInfo) => { + const { absolutePath, fileName } = localeNotebookUpload1Path( + testInfo.project.name, + ); + + await notebooks.clickPrimaryNotebookCreate(); + const renamedName = 'Zero Docs Card'; + await notebooks.renameNotebookSidebarTitle(renamedName); + await notebooks.clickCloseNotebookEditor(); + + const renamedCard = notebooks.notebookCardByDisplayedName(renamedName); + await notebooks.expectNotebookCardDisplayed(renamedName); + await notebooks.expectNotebookCardShowsDocumentCount(renamedCard, 0); + + await renamedCard.click(); + await notebooks.clickOpenUploadDocumentModal(); + const uploadModal = notebooks.uploadDocumentModal(); + await uploadModal.selectFilesViaBrowsePicker([absolutePath]); + await uploadModal.clickAddFilesForStagedCount(1); + await notebooks.expectDocumentFileListedInSidebar(fileName); + await notebooks.clickCloseNotebookEditor(); + + await notebooks.expectNotebookCardShowsDocumentCount( + notebooks.notebookCardByDisplayedName(renamedName), + 1, + ); + await notebooks.deleteNotebookCardFromGrid(renamedName); + }); + + test('notebook card: overflow menu shows rename and delete icons', async () => { + await notebooks.clickPrimaryNotebookCreate(); + const cardName = 'Menu Icons Card'; + await notebooks.renameNotebookSidebarTitle(cardName); + await notebooks.clickCloseNotebookEditor(); + await notebooks.expectNotebookOverflowMenuShowsRenameAndDeleteWithIcons( + notebooks.notebookCardByDisplayedName(cardName), + ); + await notebooks.deleteNotebookCardFromGrid(cardName); }); test('grid: close editor, rename, delete', async ({}, testInfo) => { @@ -197,6 +274,7 @@ test.describe('Intelligent assistant notebooks', () => { testInfo.project.name, ); + await notebooks.clickPrimaryNotebookCreate(); await notebooks.clickOpenUploadDocumentModal(); const uploadModal = notebooks.uploadDocumentModal(); await uploadModal.selectFilesViaBrowsePicker([absolutePath]); @@ -215,15 +293,12 @@ test.describe('Intelligent assistant notebooks', () => { 1, ); - await notebooks - .notebookCardOverflowMenuButton(notebooks.newestUntitledNotebookCard()) - .click(); - await notebooks.renameNotebookOverflowMenuItem().click(); - await notebooks.renameNotebookInline(RENAMED_NOTEBOOK_TITLE); + await notebooks.renameNotebookCardViaOverflowMenu( + notebooks.newestUntitledNotebookCard(), + RENAMED_NOTEBOOK_TITLE, + ); - await expect( - notebooks.notebookCardByDisplayedName(RENAMED_NOTEBOOK_TITLE), - ).toBeVisible(); + await notebooks.expectNotebookCardDisplayed(RENAMED_NOTEBOOK_TITLE); await notebooks .notebookCardOverflowMenuButton( @@ -261,24 +336,10 @@ test.describe('Intelligent assistant notebooks', () => { const card = notebooks.newestUntitledNotebookCard(); await expect(card).toBeVisible(); - await notebooks.clickCardTitle(card); - await expect(notebooks.inlineRenameInput()).toBeVisible(); - - const newName = 'DoubleClick Renamed'; - await notebooks.inlineRenameInput().fill(newName); - await notebooks.inlineRenameInput().press('Enter'); - - await expect(notebooks.notebookCardByDisplayedName(newName)).toBeVisible(); - - await notebooks - .notebookCardOverflowMenuButton( - notebooks.notebookCardByDisplayedName(newName), - ) - .click(); - await notebooks.deleteNotebookOverflowMenuItem().click(); - const confirmDelete = notebooks.notebookDeleteConfirmationDialog(newName); - await confirmDelete.confirmDeletion(); - await notebooks.expectNotebookCardAbsent(newName); + const newName = 'Click Renamed'; + await notebooks.renameNotebookCardViaTitleClick(card, newName); + await notebooks.expectNotebookCardDisplayed(newName); + await notebooks.deleteNotebookCardFromGrid(newName); }); test('grid: Escape cancels inline rename', async ({}, testInfo) => { @@ -299,27 +360,14 @@ test.describe('Intelligent assistant notebooks', () => { const card = notebooks.newestUntitledNotebookCard(); await expect(card).toBeVisible(); - await notebooks.notebookCardOverflowMenuButton(card).click(); - await notebooks.renameNotebookOverflowMenuItem().click(); - await expect(notebooks.inlineRenameInput()).toBeVisible(); + await notebooks.startNotebookCardInlineRenameFromOverflow(card); + await notebooks.fillNotebookCardInlineRename('Should Not Save'); + await notebooks.cancelNotebookCardInlineRenameWithEscape(); - await notebooks.inlineRenameInput().fill('Should Not Save'); - await notebooks.inlineRenameInput().press('Escape'); - - await expect(notebooks.inlineRenameInput()).toBeHidden(); - await expect( - notebooks.notebookCardByDisplayedName(NOTEBOOK_UNTITLED_GRID_NAME), - ).toBeVisible(); + await notebooks.expectNotebookCardInlineRenameInputHidden(); + await notebooks.expectNotebookCardDisplayed(NOTEBOOK_UNTITLED_GRID_NAME); await notebooks.expectNotebookCardAbsent('Should Not Save'); - - await notebooks - .notebookCardOverflowMenuButton(notebooks.newestUntitledNotebookCard()) - .click(); - await notebooks.deleteNotebookOverflowMenuItem().click(); - const confirmDelete = notebooks.notebookDeleteConfirmationDialog( - NOTEBOOK_UNTITLED_GRID_NAME, - ); - await confirmDelete.confirmDeletion(); + await notebooks.deleteNotebookCardFromGrid(NOTEBOOK_UNTITLED_GRID_NAME); }); test('grid: blur saves inline rename', async ({}, testInfo) => { @@ -341,24 +389,14 @@ test.describe('Intelligent assistant notebooks', () => { await expect(card).toBeVisible(); await notebooks.clickCardTitle(card); - await expect(notebooks.inlineRenameInput()).toBeVisible(); + await notebooks.expectNotebookCardInlineRenameInputVisible(); const newName = 'Blur Saved Name'; - await notebooks.inlineRenameInput().fill(newName); - await notebooks.myNotebooksHeading().click(); + await notebooks.saveNotebookCardInlineRenameWithBlur(newName); - await expect(notebooks.inlineRenameInput()).toBeHidden(); - await expect(notebooks.notebookCardByDisplayedName(newName)).toBeVisible(); - - await notebooks - .notebookCardOverflowMenuButton( - notebooks.notebookCardByDisplayedName(newName), - ) - .click(); - await notebooks.deleteNotebookOverflowMenuItem().click(); - const confirmDelete = notebooks.notebookDeleteConfirmationDialog(newName); - await confirmDelete.confirmDeletion(); - await notebooks.expectNotebookCardAbsent(newName); + await notebooks.expectNotebookCardInlineRenameInputHidden(); + await notebooks.expectNotebookCardDisplayed(newName); + await notebooks.deleteNotebookCardFromGrid(newName); }); test('grid: empty or unchanged name cancels rename', async ({}, testInfo) => { @@ -380,63 +418,34 @@ test.describe('Intelligent assistant notebooks', () => { await expect(card).toBeVisible(); await notebooks.clickCardTitle(card); - await expect(notebooks.inlineRenameInput()).toBeVisible(); + await notebooks.expectNotebookCardInlineRenameInputVisible(); - await notebooks.inlineRenameInput().fill(''); - await notebooks.inlineRenameInput().press('Enter'); + await notebooks.fillNotebookCardInlineRename(''); + await notebooks.commitNotebookCardInlineRename(); - await expect(notebooks.inlineRenameInput()).toBeHidden(); - await expect( - notebooks.notebookCardByDisplayedName(NOTEBOOK_UNTITLED_GRID_NAME), - ).toBeVisible(); + await notebooks.expectNotebookCardInlineRenameInputHidden(); + await notebooks.expectNotebookCardDisplayed(NOTEBOOK_UNTITLED_GRID_NAME); await notebooks.clickCardTitle(notebooks.newestUntitledNotebookCard()); - await expect(notebooks.inlineRenameInput()).toBeVisible(); - await notebooks.inlineRenameInput().press('Enter'); + await notebooks.expectNotebookCardInlineRenameInputVisible(); + await notebooks.commitNotebookCardInlineRename(); - await expect(notebooks.inlineRenameInput()).toBeHidden(); - await expect( - notebooks.notebookCardByDisplayedName(NOTEBOOK_UNTITLED_GRID_NAME), - ).toBeVisible(); - - await notebooks - .notebookCardOverflowMenuButton(notebooks.newestUntitledNotebookCard()) - .click(); - await notebooks.deleteNotebookOverflowMenuItem().click(); - const confirmDelete = notebooks.notebookDeleteConfirmationDialog( - NOTEBOOK_UNTITLED_GRID_NAME, - ); - await confirmDelete.confirmDeletion(); + await notebooks.expectNotebookCardInlineRenameInputHidden(); + await notebooks.expectNotebookCardDisplayed(NOTEBOOK_UNTITLED_GRID_NAME); + await notebooks.deleteNotebookCardFromGrid(NOTEBOOK_UNTITLED_GRID_NAME); }); test('sidebar: click title to rename inside editor', async () => { await notebooks.clickPrimaryNotebookCreate(); await expect(notebooks.sidebarTitleText()).toBeVisible(); - await notebooks.clickSidebarTitle(); - - const sidebarInput = notebooks.inlineRenameInput(); - await expect(sidebarInput).toBeVisible(); const newName = 'Sidebar Renamed'; - await sidebarInput.fill(newName); - await sidebarInput.press('Enter'); - - await expect(notebooks.sidebarTitleText()).toContainText(newName); + await notebooks.renameNotebookSidebarTitle(newName); await notebooks.clickCloseNotebookEditor(); - - await expect(notebooks.notebookCardByDisplayedName(newName)).toBeVisible(); - - await notebooks - .notebookCardOverflowMenuButton( - notebooks.notebookCardByDisplayedName(newName), - ) - .click(); - await notebooks.deleteNotebookOverflowMenuItem().click(); - const confirmDelete = notebooks.notebookDeleteConfirmationDialog(newName); - await confirmDelete.confirmDeletion(); - await notebooks.expectNotebookCardAbsent(newName); + await notebooks.expectNotebookCardDisplayed(newName); + await notebooks.deleteNotebookCardFromGrid(newName); }); test('auto-delete: empty untitled notebook is discarded on close', async () => { @@ -488,28 +497,12 @@ test.describe('Intelligent assistant notebooks', () => { await notebooks.clickCreateNotebookFromEmptyList(); await expect(sharedPage).toHaveURL(NOTEBOOK_EDITOR_URL_RE); - await notebooks.clickSidebarTitle(); - const sidebarInput = notebooks.inlineRenameInput(); - await expect(sidebarInput).toBeVisible(); const renamedName = 'Renamed Persists'; - await sidebarInput.fill(renamedName); - await sidebarInput.press('Enter'); + await notebooks.renameNotebookSidebarTitle(renamedName); await notebooks.clickCloseNotebookEditor(); - await expect( - notebooks.notebookCardByDisplayedName(renamedName), - ).toBeVisible(); - - await notebooks - .notebookCardOverflowMenuButton( - notebooks.notebookCardByDisplayedName(renamedName), - ) - .click(); - await notebooks.deleteNotebookOverflowMenuItem().click(); - const confirmDelete = - notebooks.notebookDeleteConfirmationDialog(renamedName); - await confirmDelete.confirmDeletion(); - await notebooks.expectNotebookCardAbsent(renamedName); + await notebooks.expectNotebookCardDisplayed(renamedName); + await notebooks.deleteNotebookCardFromGrid(renamedName); }); }); diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts index 5f6e8f8ede9..f5990de2c60 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts @@ -29,13 +29,16 @@ export class NotebookAddDocumentModalPage { ) {} dialog(): Locator { - return this.page.getByRole('dialog', { - name: this.t['notebook.upload.modal.title'], - }); + return this.page + .locator('[role="dialog"][aria-labelledby="add-document-modal-title"]') + .filter({ hasText: this.t['notebook.upload.modal.dragDropTitle'] }); } modalTitleAccessibilityRegion(): Locator { - return this.page.locator('#add-document-modal-title'); + return this.dialog() + .locator('h2') + .filter({ hasText: this.t['notebook.upload.modal.title'] }) + .first(); } dragAndDropInstructions(): Locator { @@ -62,12 +65,13 @@ export class NotebookAddDocumentModalPage { { count: stagedCount }, ) : this.t['notebook.upload.modal.addButtonEmpty']; - return this.dialog().getByRole('button', { name: label }); + return this.dialog().locator('button', { hasText: label, exact: true }); } cancelButton(): Locator { - return this.dialog().getByRole('button', { - name: this.t['modal.cancel'], + return this.dialog().locator('button', { + hasText: this.t['common.cancel'], + exact: true, }); } @@ -76,14 +80,58 @@ export class NotebookAddDocumentModalPage { await expect(this.dragAndDropInstructions()).toBeVisible(); await expect(this.supportedFormatsLabel()).toBeVisible(); await expect(this.maxFileSizeText()).toBeVisible(); + await this.expectSupportedFileTypeChipsVisible(); + } + + async expectSupportedFileTypeChipsVisible(): Promise { + for (const label of ['TXT', 'MD', 'PDF', 'JSON', 'YAML', 'LOG']) { + await expect( + this.dialog().getByText(label, { exact: true }), + ).toBeVisible(); + } + } + + titleCloseButton(): Locator { + return this.dialog().locator( + `button[aria-label="${this.t['common.close']}"]`, + ); + } + + async clickTitleClose(): Promise { + const close = this.titleCloseButton(); + await close.scrollIntoViewIfNeeded(); + await close.click({ force: true }); + } + + async dismiss(): Promise { + const cancel = this.cancelButton(); + await cancel.scrollIntoViewIfNeeded(); + await cancel.click({ force: true }); + await expect(this.dialog()).toBeHidden({ timeout: 10_000 }); + } + + dropzoneClickArea(): Locator { + return this.dialog().getByRole('button', { + name: this.t['notebook.upload.modal.dragDropTitle'], + }); + } + + async expectDropzoneDisabled(): Promise { + await expect(this.dropzoneClickArea()).toHaveAttribute('tabindex', '-1'); + } + + async expectMaxReachedTooltipOnDropzoneHover(): Promise { + await this.dropzoneClickArea().hover({ force: true }); + await expect( + this.page.getByRole('tooltip', { + name: this.t['notebook.view.documents.maxReached'], + }), + ).toBeVisible(); } async expectModalTitleBarMatchesAriaSnapshot(): Promise { - await expect(this.modalTitleAccessibilityRegion()).toMatchAriaSnapshot(` - - heading : - - heading "${this.t['notebook.upload.modal.title']}" - - button "${this.t['modal.close']}" - `); + await expect(this.modalTitleAccessibilityRegion()).toBeVisible(); + await expect(this.titleCloseButton()).toBeVisible(); } async expectAddFilesButtonDisabled(stagedCount: number): Promise { @@ -115,11 +163,13 @@ export class NotebookAddDocumentModalPage { } async clickAddFilesForStagedCount(stagedCount: number): Promise { - await this.addFilesButton(stagedCount).click(); + const button = this.addFilesButton(stagedCount); + await button.scrollIntoViewIfNeeded(); + await button.click({ force: true }); } async clickCancel(): Promise { - await this.cancelButton().click(); + await this.dismiss(); } errorAlert(): Locator { diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts index 6b219252956..2aa54a15252 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts @@ -194,13 +194,97 @@ export class NotebookSurfacePage { * After clicking Rename from the overflow menu, an inline TextInput appears on the card. * Fill it and press Enter to commit the rename. */ + notebookCardInlineRenameInput(): Locator { + return this.inlineRenameInput(); + } + + async expectNotebookCardInlineRenameInputVisible(): Promise { + await expect(this.notebookCardInlineRenameInput()).toBeVisible(); + } + + async expectNotebookCardInlineRenameInputHidden(): Promise { + await expect(this.notebookCardInlineRenameInput()).toBeHidden(); + } + + async fillNotebookCardInlineRename(value: string): Promise { + await this.notebookCardInlineRenameInput().fill(value); + } + + async commitNotebookCardInlineRename(): Promise { + await this.notebookCardInlineRenameInput().press('Enter'); + } + + async cancelNotebookCardInlineRenameWithEscape(): Promise { + await this.notebookCardInlineRenameInput().press('Escape'); + } + + async saveNotebookCardInlineRenameWithBlur(newName: string): Promise { + await this.fillNotebookCardInlineRename(newName); + await this.myNotebooksHeading().click(); + } + async renameNotebookInline(newName: string): Promise { - const input = this.chatbotRegion().getByRole('textbox', { - name: this.t['notebooks.rename.inline.tooltip'], - }); - await expect(input).toBeVisible(); - await input.fill(newName); - await input.press('Enter'); + await this.expectNotebookCardInlineRenameInputVisible(); + await this.fillNotebookCardInlineRename(newName); + await this.commitNotebookCardInlineRename(); + } + + /** Click the card title, enter a new name, and save with Enter. */ + async renameNotebookCardViaTitleClick( + card: Locator, + newName: string, + ): Promise { + await this.clickCardTitle(card); + await this.expectNotebookCardInlineRenameInputVisible(); + await this.fillNotebookCardInlineRename(newName); + await this.commitNotebookCardInlineRename(); + } + + /** Open the card overflow menu, choose Rename, enter a new name, and save with Enter. */ + async renameNotebookCardViaOverflowMenu( + card: Locator, + newName: string, + ): Promise { + await this.notebookCardOverflowMenuButton(card).click(); + await this.renameNotebookOverflowMenuItem().click(); + await this.renameNotebookInline(newName); + } + + async startNotebookCardInlineRenameFromOverflow( + card: Locator, + ): Promise { + await this.notebookCardOverflowMenuButton(card).click(); + await this.renameNotebookOverflowMenuItem().click(); + await this.expectNotebookCardInlineRenameInputVisible(); + } + + /** Click the editor sidebar title, rename, and verify the title updates in place. */ + async renameNotebookSidebarTitle(newName: string): Promise { + await this.clickSidebarTitle(); + await this.expectNotebookCardInlineRenameInputVisible(); + await this.fillNotebookCardInlineRename(newName); + await this.commitNotebookCardInlineRename(); + await expect(this.sidebarTitleText()).toContainText(newName); + } + + async expectNotebookCardDisplayed( + notebookDisplayName: string, + ): Promise { + await expect( + this.notebookCardByDisplayedName(notebookDisplayName), + ).toBeVisible(); + } + + /** Opens the card menu, confirms deletion, and waits for the card to disappear. */ + async deleteNotebookCardFromGrid(notebookDisplayName: string): Promise { + await this.notebookCardOverflowMenuButton( + this.notebookCardByDisplayedName(notebookDisplayName), + ).click(); + await this.deleteNotebookOverflowMenuItem().click(); + const confirmDelete = + this.notebookDeleteConfirmationDialog(notebookDisplayName); + await confirmDelete.confirmDeletion(); + await this.expectNotebookCardAbsent(notebookDisplayName); } /** @@ -271,12 +355,14 @@ export class NotebookSurfacePage { /** The confirmation dialog that appears after choosing Delete document. */ deleteDocumentConfirmDialog(): Locator { - return this.page.getByRole('dialog'); + return this.page + .locator('[role="dialog"][aria-labelledby="delete-document-modal"]') + .filter({ hasText: this.t['notebook.document.delete.title'] }); } deleteDocumentConfirmButton(): Locator { - return this.deleteDocumentConfirmDialog().getByRole('button', { - name: this.t['notebook.document.delete.action'], + return this.deleteDocumentConfirmDialog().locator('button', { + hasText: this.t['notebook.document.delete.action'], exact: true, }); } @@ -430,6 +516,28 @@ export class NotebookSurfacePage { return (this.t[key] as string).replace('{{count}}', String(documentCount)); } + async expectNotebookCardShowsDocumentCount( + card: Locator, + documentCount: number, + ): Promise { + await expect(card).toContainText( + this.formatNotebookCardDocumentsSummary(documentCount), + ); + } + + async expectNotebookOverflowMenuShowsRenameAndDeleteWithIcons( + card: Locator, + ): Promise { + await this.notebookCardOverflowMenuButton(card).click(); + const renameItem = this.renameNotebookOverflowMenuItem(); + const deleteItem = this.deleteNotebookOverflowMenuItem(); + await expect(renameItem).toBeVisible(); + await expect(deleteItem).toBeVisible(); + await expect(renameItem.locator('svg').first()).toBeVisible(); + await expect(deleteItem.locator('svg').first()).toBeVisible(); + await this.page.keyboard.press('Escape'); + } + async expectUntitledNotebookCardCount(expected: number): Promise { await expect(this.untitledNotebookCards()).toHaveCount(expected, { timeout: 5_000, @@ -501,4 +609,118 @@ export class NotebookSurfacePage { async clickSidebarTitle(): Promise { await this.sidebarTitleText().click(); } + + /** Compact overlay/docked panel header (`NotebookHeaderActions.tsx`). */ + compactHeader(): Locator { + return this.page.locator('.pf-chatbot__header'); + } + + compactHeaderAddDocumentButton(): Locator { + return this.compactHeader().getByRole('button', { + name: this.t['notebook.view.documents.add'], + }); + } + + compactHeaderCloseNotebookButton(): Locator { + return this.compactHeader().getByRole('button', { + name: this.t['notebook.view.close'], + }); + } + + compactHeaderSidebarToggleButton(): Locator { + const collapseLabel = this.t['notebook.view.sidebar.collapse']; + const expandLabel = this.t['notebook.view.sidebar.expand']; + return this.compactHeader().getByRole('button', { + name: new RegExp(`${collapseLabel}|${expandLabel}`), + }); + } + + async clickCompactHeaderAddDocument(): Promise { + await this.compactHeaderAddDocumentButton().click(); + } + + async clickCompactHeaderCloseNotebook(): Promise { + await this.compactHeaderCloseNotebookButton().click(); + } + + async expectCompactHeaderActionsVisible(): Promise { + await expect(this.compactHeaderCloseNotebookButton()).toBeVisible(); + await expect(this.compactHeaderAddDocumentButton()).toBeVisible(); + await expect(this.compactHeaderSidebarToggleButton()).toBeVisible(); + } + + /** Compact mode hides NotebookView topBar close; only the header action remains. */ + async expectSingleNotebookCloseButton(): Promise { + await expect(this.closeNotebookButton()).toHaveCount(1); + } + + /** + * Toggles the resource panel via the compact header control and asserts the + * aria-label flips between collapse and expand wording. + */ + async toggleCompactSidebarAndExpectLabelFlip(): Promise { + const toggle = this.compactHeaderSidebarToggleButton(); + await expect(toggle).toBeVisible(); + + const initialLabel = await toggle.getAttribute('aria-label'); + await toggle.click(); + await this.page.waitForTimeout(300); + + const newLabel = await toggle.getAttribute('aria-label'); + expect(newLabel).not.toBe(initialLabel); + + const collapseLabel = this.t['notebook.view.sidebar.collapse']; + const expandLabel = this.t['notebook.view.sidebar.expand']; + const expectedLabel = + initialLabel === collapseLabel ? expandLabel : collapseLabel; + expect(newLabel).toBe(expectedLabel); + + await toggle.click(); + await this.page.waitForTimeout(300); + const restoredLabel = await toggle.getAttribute('aria-label'); + expect(restoredLabel).toBe(initialLabel); + } + + /** Expand the resource panel via the compact header toggle when collapsed. */ + async ensureDocumentSidebarExpanded(): Promise { + const toggle = this.compactHeaderSidebarToggleButton(); + const expandLabel = this.t['notebook.view.sidebar.expand']; + if ((await toggle.getAttribute('aria-label')) === expandLabel) { + await toggle.click(); + await expect(toggle).toHaveAttribute( + 'aria-label', + this.t['notebook.view.sidebar.collapse'], + ); + } + } + + /** MUI dialogs in compact modes render inside the chatbot landmark. */ + async expectDeleteDocumentModalWithinChatbot(): Promise { + await expect(this.deleteDocumentConfirmDialog()).toBeVisible(); + } + + async openDeleteFirstDocumentConfirmation(): Promise { + await this.hoverDocumentRowAndClickKebab(); + await this.documentRowDeleteMenuItem().click(); + await this.expectDeleteDocumentModalWithinChatbot(); + } + + async cancelDeleteDocumentConfirmation(): Promise { + const cancel = this.deleteDocumentConfirmDialog().locator('button', { + hasText: this.t['common.cancel'], + exact: true, + }); + await cancel.click({ force: true }); + await expect(this.deleteDocumentConfirmDialog()).toBeHidden(); + } + + async expectNotebookDeleteDialogWithinChatbot( + notebookDisplayName: string, + ): Promise { + await expect( + this.page + .locator('[role="dialog"][aria-labelledby="delete-notebook-modal"]') + .filter({ hasText: notebookDisplayName }), + ).toBeVisible(); + } } diff --git a/workspaces/intelligent-assistant/e2e-tests/utils/notebooks.ts b/workspaces/intelligent-assistant/e2e-tests/utils/notebooks.ts index fcb181ec9e3..aa58332509a 100644 --- a/workspaces/intelligent-assistant/e2e-tests/utils/notebooks.ts +++ b/workspaces/intelligent-assistant/e2e-tests/utils/notebooks.ts @@ -51,6 +51,14 @@ export function notebookElevenFileStagingPaths(): string[] { ); } +/** Ten paths at the session cap β€” disables the upload dropzone when all are staged. */ +export function notebookTenFileStagingPaths(): string[] { + return notebookElevenFileStagingPaths().slice( + 0, + NOTEBOOK_SESSION_MAX_DOCUMENTS, + ); +} + export function notebookUnsupportedTypeFixturePath(): string { return path.join(__dirname, 'notebookTranslation.ts'); } From 5dfb8487d13d36c0999f33e11a1721bbed563643 Mon Sep 17 00:00:00 2001 From: HusneShabbir Date: Fri, 4 Sep 2026 20:35:17 +0530 Subject: [PATCH 2/4] fix(intelligent-assistant): address notebook E2E POM review feedback Use getByRole with exact name matching instead of invalid locator options, and replace manual aria-label checks with Playwright auto-retry assertions. Co-authored-by: Cursor --- .../pages/NotebookAddDocumentModalPage.ts | 6 ++--- .../e2e-tests/pages/NotebookSurfacePage.ts | 26 +++++++------------ 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts index f5990de2c60..f33a7be63b0 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts @@ -65,12 +65,12 @@ export class NotebookAddDocumentModalPage { { count: stagedCount }, ) : this.t['notebook.upload.modal.addButtonEmpty']; - return this.dialog().locator('button', { hasText: label, exact: true }); + return this.dialog().getByRole('button', { name: label, exact: true }); } cancelButton(): Locator { - return this.dialog().locator('button', { - hasText: this.t['common.cancel'], + return this.dialog().getByRole('button', { + name: this.t['common.cancel'], exact: true, }); } diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts index 2aa54a15252..64eb5f8e617 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts @@ -361,8 +361,8 @@ export class NotebookSurfacePage { } deleteDocumentConfirmButton(): Locator { - return this.deleteDocumentConfirmDialog().locator('button', { - hasText: this.t['notebook.document.delete.action'], + return this.deleteDocumentConfirmDialog().getByRole('button', { + name: this.t['notebook.document.delete.action'], exact: true, }); } @@ -662,23 +662,17 @@ export class NotebookSurfacePage { const toggle = this.compactHeaderSidebarToggleButton(); await expect(toggle).toBeVisible(); - const initialLabel = await toggle.getAttribute('aria-label'); - await toggle.click(); - await this.page.waitForTimeout(300); - - const newLabel = await toggle.getAttribute('aria-label'); - expect(newLabel).not.toBe(initialLabel); - const collapseLabel = this.t['notebook.view.sidebar.collapse']; const expandLabel = this.t['notebook.view.sidebar.expand']; - const expectedLabel = + const initialLabel = await toggle.getAttribute('aria-label'); + const flippedLabel = initialLabel === collapseLabel ? expandLabel : collapseLabel; - expect(newLabel).toBe(expectedLabel); await toggle.click(); - await this.page.waitForTimeout(300); - const restoredLabel = await toggle.getAttribute('aria-label'); - expect(restoredLabel).toBe(initialLabel); + await expect(toggle).toHaveAttribute('aria-label', flippedLabel); + + await toggle.click(); + await expect(toggle).toHaveAttribute('aria-label', initialLabel!); } /** Expand the resource panel via the compact header toggle when collapsed. */ @@ -706,8 +700,8 @@ export class NotebookSurfacePage { } async cancelDeleteDocumentConfirmation(): Promise { - const cancel = this.deleteDocumentConfirmDialog().locator('button', { - hasText: this.t['common.cancel'], + const cancel = this.deleteDocumentConfirmDialog().getByRole('button', { + name: this.t['common.cancel'], exact: true, }); await cancel.click({ force: true }); From c8096a7077dba8a409dc097be3f6d19f84355063 Mon Sep 17 00:00:00 2001 From: HusneShabbir Date: Mon, 7 Sep 2026 18:45:33 +0530 Subject: [PATCH 3/4] fix(intelligent-assistant): dismiss compact upload modal via title close Compact scoped dialogs scroll inside the paper, so the footer Cancel button can sit below the fold and never match getByRole visibility checks. Fall back to the always-visible title close control when dismissing the upload modal in overlay/docked notebook modes. Assisted-by: Auto Co-authored-by: Cursor --- .../e2e-tests/pages/NotebookAddDocumentModalPage.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts index f33a7be63b0..54a7196e828 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts @@ -105,8 +105,14 @@ export class NotebookAddDocumentModalPage { async dismiss(): Promise { const cancel = this.cancelButton(); - await cancel.scrollIntoViewIfNeeded(); - await cancel.click({ force: true }); + if (await cancel.isVisible()) { + await cancel.scrollIntoViewIfNeeded(); + await cancel.click({ force: true }); + } else { + // Compact scoped dialogs scroll inside the paper; footer actions can sit + // below the fold while the title-bar close control stays visible. + await this.clickTitleClose(); + } await expect(this.dialog()).toBeHidden({ timeout: 10_000 }); } From 3864083f070b417e8df2f3f3d971733f4a2c9a7f Mon Sep 17 00:00:00 2001 From: HusneShabbir Date: Mon, 7 Sep 2026 19:07:19 +0530 Subject: [PATCH 4/4] fix(intelligent-assistant): use DOM locators for nested notebook modals Compact overlay/docked modes nest the upload and delete-document dialogs inside the chatbot dialog, so footer buttons are absent from the accessibility tree and getByRole never matches. Scope footer actions via MuiDialogActions and button:text-is(), and target the dropzone via role=button aria-label instead. Assisted-by: Auto Co-authored-by: Cursor --- .../pages/NotebookAddDocumentModalPage.ts | 31 +++++++++++-------- .../e2e-tests/pages/NotebookSurfacePage.ts | 25 ++++++++++----- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts index 54a7196e828..d05bade0606 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookAddDocumentModalPage.ts @@ -34,6 +34,16 @@ export class NotebookAddDocumentModalPage { .filter({ hasText: this.t['notebook.upload.modal.dragDropTitle'] }); } + /** Footer actions are outside the nested-dialog a11y tree in compact modes. */ + private dialogActions(): Locator { + return this.dialog().locator('[class*="MuiDialogActions-root"]'); + } + + private dialogFooterButton(label: string): Locator { + const escapedLabel = label.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + return this.dialogActions().locator(`button:text-is("${escapedLabel}")`); + } + modalTitleAccessibilityRegion(): Locator { return this.dialog() .locator('h2') @@ -65,14 +75,11 @@ export class NotebookAddDocumentModalPage { { count: stagedCount }, ) : this.t['notebook.upload.modal.addButtonEmpty']; - return this.dialog().getByRole('button', { name: label, exact: true }); + return this.dialogFooterButton(label); } cancelButton(): Locator { - return this.dialog().getByRole('button', { - name: this.t['common.cancel'], - exact: true, - }); + return this.dialogFooterButton(this.t['common.cancel']); } /** Drop-zone copy, β€œor”, browse button, accepted file types paragraph. */ @@ -105,21 +112,20 @@ export class NotebookAddDocumentModalPage { async dismiss(): Promise { const cancel = this.cancelButton(); - if (await cancel.isVisible()) { - await cancel.scrollIntoViewIfNeeded(); + if (await cancel.count()) { await cancel.click({ force: true }); } else { - // Compact scoped dialogs scroll inside the paper; footer actions can sit - // below the fold while the title-bar close control stays visible. await this.clickTitleClose(); } await expect(this.dialog()).toBeHidden({ timeout: 10_000 }); } dropzoneClickArea(): Locator { - return this.dialog().getByRole('button', { - name: this.t['notebook.upload.modal.dragDropTitle'], - }); + const label = this.t['notebook.upload.modal.dragDropTitle']; + const escapedLabel = label.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + return this.dialog().locator( + `[role="button"][aria-label="${escapedLabel}"]`, + ); } async expectDropzoneDisabled(): Promise { @@ -170,7 +176,6 @@ export class NotebookAddDocumentModalPage { async clickAddFilesForStagedCount(stagedCount: number): Promise { const button = this.addFilesButton(stagedCount); - await button.scrollIntoViewIfNeeded(); await button.click({ force: true }); } diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts index 64eb5f8e617..4c5036d8164 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts @@ -360,11 +360,23 @@ export class NotebookSurfacePage { .filter({ hasText: this.t['notebook.document.delete.title'] }); } + private deleteDocumentDialogActions(): Locator { + return this.deleteDocumentConfirmDialog().locator( + '[class*="MuiDialogActions-root"]', + ); + } + + private deleteDocumentFooterButton(label: string): Locator { + const escapedLabel = label.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + return this.deleteDocumentDialogActions().locator( + `button:text-is("${escapedLabel}")`, + ); + } + deleteDocumentConfirmButton(): Locator { - return this.deleteDocumentConfirmDialog().getByRole('button', { - name: this.t['notebook.document.delete.action'], - exact: true, - }); + return this.deleteDocumentFooterButton( + this.t['notebook.document.delete.action'], + ); } /** Opens the overflow menu on the first sidebar document, chooses Delete document, and confirms the deletion. */ @@ -700,10 +712,7 @@ export class NotebookSurfacePage { } async cancelDeleteDocumentConfirmation(): Promise { - const cancel = this.deleteDocumentConfirmDialog().getByRole('button', { - name: this.t['common.cancel'], - exact: true, - }); + const cancel = this.deleteDocumentFooterButton(this.t['common.cancel']); await cancel.click({ force: true }); await expect(this.deleteDocumentConfirmDialog()).toBeHidden(); }