Skip to content
20 changes: 16 additions & 4 deletions zeppelin-web-angular/e2e/models/editor-search-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,22 @@ export class EditorSearchPage extends BasePage {
this.showHideCodeButton = page
.locator('zeppelin-notebook-paragraph-control a[nzTooltipTitle="Show/hide the code"]')
.first();
this.nextMatchButton = this.findWidget.locator('.button.next, [title^="Next Match"]').first();
this.previousMatchButton = this.findWidget.locator('.button.previous, [title^="Previous Match"]').first();
this.toggleReplaceButton = this.findWidget.locator('.button.toggle, [title^="Toggle Replace"]').first();
this.replaceAllButton = this.findWidget.locator('.button.replace-all, [title^="Replace All"]').first();
this.nextMatchButton = this.findWidget
.locator('.button.next, .button.codicon-find-next-match, [aria-label^="Next Match"], [title^="Next Match"]')
.first();
this.previousMatchButton = this.findWidget
.locator(
'.button.previous, .button.codicon-find-previous-match, [aria-label^="Previous Match"], [title^="Previous Match"]'
)
.first();
this.toggleReplaceButton = this.findWidget
.locator('.button.toggle, [aria-label^="Toggle Replace"], [title^="Toggle Replace"]')
.first();
this.replaceAllButton = this.findWidget
.locator(
'.button.replace-all, .button.codicon-find-replace-all, [aria-label^="Replace All"], [title^="Replace All"]'
)
.first();
}

async openNotebook(noteId: string): Promise<void> {
Expand Down
80 changes: 80 additions & 0 deletions zeppelin-web-angular/e2e/tests/notebook/inline-completion.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { expect, Page, test } from '@playwright/test';
import { NotebookKeyboardPage } from 'e2e/models/notebook-keyboard-page';
import { addPageAnnotationBeforeEach, performLoginIfRequired, waitForZeppelinReady, PAGES } from '../../utils';

const openInlineCompletionEditor = async (page: Page) => {
await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);

const notePath = `E2E_TEST_FOLDER/InlineCompletion_${Date.now()}`;
const createResponse = await page.request.post('/api/notebook', {
data: { notePath, defaultInterpreterGroup: 'python', addingEmptyParagraph: true }
});
expect(createResponse.ok()).toBeTruthy();
const noteId = (await createResponse.json()).body as string;
const noteResponse = await page.request.get(`/api/notebook/${noteId}`);
expect(noteResponse.ok()).toBeTruthy();

await page.goto(`/#/notebook/${noteId}?aiInlineComplete=true`);
await expect(page).toHaveURL(/#\/notebook\/[^?]+\?aiInlineComplete=true/);

const keyboardPage = new NotebookKeyboardPage(page);
await expect(keyboardPage.paragraphContainer.first()).toBeVisible({ timeout: 30000 });
await keyboardPage.setCodeEditorContent('%python\nprint("history")');
await keyboardPage.tryFocusCodeEditor();
await keyboardPage.pressSelectAll();
await page.keyboard.press('ArrowRight');
await page.keyboard.press('Enter');
await page.keyboard.type('prin');

const viewLines = page.locator('.monaco-editor .view-line');
await expect
.poll(async () => (await viewLines.last().textContent())?.replace(/\s+/g, ' ') ?? '', { timeout: 15000 })
.toContain('print("history")');

return { noteId, inputArea: page.locator('.monaco-editor textarea.inputarea').first() };
};

test.describe('Inline completion', () => {
addPageAnnotationBeforeEach(PAGES.WORKSPACE.NOTEBOOK_PARAGRAPH_CODE_EDITOR);

test('shows history completion and preserves focus when dismissed', async ({ page }) => {
const { noteId, inputArea } = await openInlineCompletionEditor(page);

try {
await expect(inputArea).toBeFocused();
await page.keyboard.press('Escape');
await expect(inputArea).toBeFocused();
} finally {
await page.request.delete(`/api/notebook/${noteId}`);
}
});

test('blurs the editor on the second Escape after dismissing completion', async ({ page, browserName }) => {
test.skip(browserName !== 'chromium', 'Monaco handles the second Escape differently in Firefox and WebKit');
const { noteId, inputArea } = await openInlineCompletionEditor(page);

try {
await expect(inputArea).toBeFocused();
await page.keyboard.press('Escape');
await expect(inputArea).toBeFocused();
await page.keyboard.press('Escape');
await expect(inputArea).not.toBeFocused();
} finally {
await page.request.delete(`/api/notebook/${noteId}`);
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -461,18 +461,22 @@ test.describe.serial('Comprehensive Keyboard Shortcuts (ShortcutsMap)', () => {
// ===== UI TOGGLE SHORTCUTS =====

test.describe('ParagraphActions.SwitchEditor: Control+Alt+E', () => {
test('should toggle editor visibility with Control+Alt+E', async () => {
// Given: A paragraph with visible editor
await keyboardPage.tryFocusCodeEditor();
await keyboardPage.setCodeEditorContent('%python\nprint("Test editor toggle")');
test('should toggle the focused editor with Control+Alt+E', async () => {
await keyboardPage.tryFocusCodeEditor(0);
await keyboardPage.setCodeEditorContent('%python\nprint("First paragraph")', 0);
await keyboardPage.pressInsertBelow();
await keyboardPage.waitForParagraphCountChange(2);
await keyboardPage.tryFocusCodeEditor(1);
await keyboardPage.setCodeEditorContent('%python\nprint("Second paragraph")', 1);
await keyboardPage.tryFocusCodeEditor(0);

const initialEditorVisibility = await keyboardPage.isEditorVisible(0);
const secondEditorVisibility = await keyboardPage.isEditorVisible(1);

// When: User presses Control+Alt+E
await keyboardPage.pressSwitchEditor();

// Then: editor visibility toggles
await expect.poll(() => keyboardPage.isEditorVisible(0), { timeout: 10000 }).toBe(!initialEditorVisibility);
expect(await keyboardPage.isEditorVisible(1)).toBe(secondEditorVisibility);
});
});

Expand Down
Loading
Loading