From 5dd1cbeed89ba23909b3fc06f39b2dde87509d18 Mon Sep 17 00:00:00 2001 From: Barbara Valdez <34872381+barbaravaldez@users.noreply.github.com> Date: Thu, 28 Apr 2022 17:05:23 -0700 Subject: [PATCH] Keyboard navigation smoke test (#18796) --- test/automation/src/index.ts | 1 + test/automation/src/sql/constants.ts | 7 +++ test/automation/src/sql/notebook.ts | 57 +++++++++++++----- .../src/sql/areas/notebook/notebook.test.ts | 58 ++++++++++++++++--- 4 files changed, 101 insertions(+), 22 deletions(-) create mode 100644 test/automation/src/sql/constants.ts diff --git a/test/automation/src/index.ts b/test/automation/src/index.ts index 972a5279da..55aa1ae757 100644 --- a/test/automation/src/index.ts +++ b/test/automation/src/index.ts @@ -31,3 +31,4 @@ export * from './driver'; export * from './sql/connectionDialog'; export * from './sql/profiler'; export * from './sql/queryEditors'; +export * from './sql/constants'; diff --git a/test/automation/src/sql/constants.ts b/test/automation/src/sql/constants.ts new file mode 100644 index 0000000000..152adfcf21 --- /dev/null +++ b/test/automation/src/sql/constants.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const ctrlOrCmd = process.platform === 'darwin' ? 'cmd' : 'ctrl'; +export const winOrCtrl = process.platform === 'darwin' ? 'ctrl' : 'win'; diff --git a/test/automation/src/sql/notebook.ts b/test/automation/src/sql/notebook.ts index b6bb9a775c..e378194532 100644 --- a/test/automation/src/sql/notebook.ts +++ b/test/automation/src/sql/notebook.ts @@ -8,20 +8,22 @@ import { QuickAccess } from '../quickaccess'; import { QuickInput } from '../quickinput'; import { Editors } from '../editors'; import { IElement } from '..'; +import * as constants from '../sql/constants'; + +const activeCellSelector = '.notebook-cell.active'; export class Notebook { public readonly notebookToolbar: NotebookToolbar; public readonly textCellToolbar: TextCellToolbar; + public readonly notebookFind: NotebookFind; public readonly view: NotebookTreeView; - public readonly winOrCtrl = process.platform === 'darwin' ? 'ctrl' : 'win'; - public readonly ctrlOrCmd = process.platform === 'darwin' ? 'cmd' : 'ctrl'; - constructor(private code: Code, private quickAccess: QuickAccess, private quickInput: QuickInput, private editors: Editors) { this.notebookToolbar = new NotebookToolbar(code); this.textCellToolbar = new TextCellToolbar(code); this.view = new NotebookTreeView(code, quickAccess); + this.notebookFind = new NotebookFind(code); } async openFile(fileName: string): Promise { @@ -33,7 +35,7 @@ export class Notebook { } async newUntitledNotebook(): Promise { - await this.code.dispatchKeybinding(this.winOrCtrl + '+Alt+n'); + await this.code.dispatchKeybinding(`${constants.winOrCtrl}+Alt+n`); await this.editors.waitForActiveTab(`Notebook-0`); await this.code.waitForElement('.notebookEditor'); } @@ -47,21 +49,35 @@ export class Notebook { await this.code.dispatchKeybinding('ctrl+shift+c'); } - await this.code.waitForElement('.notebook-cell.active'); + await this.code.waitForElement(activeCellSelector); + } + + async waitForActiveCellGone(): Promise { + await this.code.waitForElementGone(activeCellSelector); } async runActiveCell(): Promise { await this.code.dispatchKeybinding('F5'); } + async exitActiveCell(): Promise { + await this.code.dispatchKeybinding('escape'); // first escape to exit edit mode + await this.code.dispatchKeybinding('escape'); // second escape to deselect cell + } + async runAllCells(): Promise { await this.code.dispatchKeybinding('ctrl+shift+F5'); } // Cell Actions - async waitForTypeInEditor(text: string) { - const editor = '.notebook-cell.active .monaco-editor'; + async getActiveCell(id?: string): Promise { + const activeCell = id ? `${activeCellSelector}[id="${id}"]` : activeCellSelector; + return this.code.waitForElement(activeCell); + } + + async waitForTypeInEditor(text: string, cellId?: string) { + const editor = cellId ? `${activeCellSelector}[id="${cellId}"] .monaco-editor` : `${activeCellSelector} .monaco-editor`; await this.code.waitAndClick(editor); const textarea = `${editor} textarea`; @@ -72,7 +88,7 @@ export class Notebook { } async waitForActiveCellEditorContents(accept: (contents: string) => boolean): Promise { - const selector = '.notebook-cell.active .monaco-editor .view-lines'; + const selector = `${activeCellSelector} .monaco-editor .view-lines`; return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' '))); } @@ -82,24 +98,24 @@ export class Notebook { } public async selectAllTextInRichTextEditor(): Promise { - const editor = '.notebook-cell.active .notebook-preview[contenteditable="true"]'; + const editor = `${activeCellSelector} .notebook-preview[contenteditable="true"]`; await this.selectAllText(editor); } public async selectAllTextInEditor(): Promise { - const editor = '.notebook-cell.active .monaco-editor'; + const editor = `${activeCellSelector} .monaco-editor`; await this.selectAllText(editor); } private async selectAllText(selector: string): Promise { await this.code.waitAndClick(selector); - await this.code.dispatchKeybinding(this.ctrlOrCmd + '+a'); + await this.code.dispatchKeybinding(`${constants.ctrlOrCmd}+a`); } private static readonly placeholderSelector = 'div.placeholder-cell-component'; async addCellFromPlaceholder(cellType: 'Markdown' | 'Code'): Promise { await this.code.waitAndClick(`${Notebook.placeholderSelector} p a[id="add${cellType}"]`); - await this.code.waitForElement('.notebook-cell.active'); + await this.code.waitForElement(activeCellSelector); } async waitForPlaceholderGone(): Promise { @@ -172,12 +188,12 @@ export class Notebook { // Cell Output Actions async waitForJupyterErrorOutput(): Promise { - const jupyterErrorOutput = `.notebook-cell.active .notebook-output mime-output[data-mime-type="application/vnd.jupyter.stderr"]`; + const jupyterErrorOutput = `${activeCellSelector} .notebook-output mime-output[data-mime-type="application/vnd.jupyter.stderr"]`; await this.code.waitForElement(jupyterErrorOutput); } async waitForActiveCellResults(): Promise { - const outputComponent = '.notebook-cell.active .notebook-output'; + const outputComponent = `${activeCellSelector} .notebook-output`; await this.code.waitForElement(outputComponent); } @@ -192,7 +208,7 @@ export class Notebook { } async waitForActiveCellResultsGone(): Promise { - const outputComponent = '.notebook-cell.active .notebook-output'; + const outputComponent = `${activeCellSelector} .notebook-output`; await this.code.waitForElementGone(outputComponent); } @@ -438,3 +454,14 @@ export class NotebookTreeView { await this.code.waitForElementGone(NotebookTreeView.pinnedNotebooksSelector); } } + +export class NotebookFind { + + constructor(private code: Code) { } + + async openFindWidget(): Promise { + const findWidgetCmd = `${constants.ctrlOrCmd}+f`; + await this.code.dispatchKeybinding(findWidgetCmd); + await this.code.waitForElement('.editor-widget.find-widget.visible'); + } +} diff --git a/test/smoke/src/sql/areas/notebook/notebook.test.ts b/test/smoke/src/sql/areas/notebook/notebook.test.ts index 5b4bf3c3e7..69f3289943 100644 --- a/test/smoke/src/sql/areas/notebook/notebook.test.ts +++ b/test/smoke/src/sql/areas/notebook/notebook.test.ts @@ -3,11 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Application } from '../../../../../automation'; +import { Application, ctrlOrCmd } from '../../../../../automation'; import * as minimist from 'minimist'; import { afterSuite, beforeSuite } from '../../../utils'; import * as assert from 'assert'; - export function setup(opts: minimist.ParsedArgs) { describe('Notebook', () => { beforeSuite(opts); @@ -59,6 +58,7 @@ export function setup(opts: minimist.ParsedArgs) { await app.workbench.sqlNotebook.waitForColorization('6', 'mtk1'); // employees }); + // Python Notebooks it('can open new notebook, configure Python, and execute one cell', async function () { @@ -132,6 +132,50 @@ export function setup(opts: minimist.ParsedArgs) { await app.code.dispatchKeybinding('escape'); }); + describe('Notebook keyboard navigation', async () => { + it('can enter and exit edit mode and navigate using keyboard nav', async function () { + const app = this.app as Application; + await app.workbench.sqlNotebook.newUntitledNotebook(); + await app.workbench.sqlNotebook.addCellFromPlaceholder('Code'); // add new code cell + await app.workbench.sqlNotebook.waitForPlaceholderGone(); + const activeCodeCellId = (await app.workbench.sqlNotebook.getActiveCell()).attributes['id']; + await app.workbench.sqlNotebook.waitForTypeInEditor('code cell', activeCodeCellId); // the new cell should be in edit mode + await app.workbench.sqlNotebook.exitActiveCell(); + await app.workbench.sqlNotebook.waitForActiveCellGone(); + + await app.workbench.sqlNotebook.addCell('markdown'); // add markdown cell + await app.workbench.sqlNotebook.textCellToolbar.changeTextCellView('Split View'); + const activeTextCellId = (await app.workbench.sqlNotebook.getActiveCell()).attributes['id']; + await app.workbench.sqlNotebook.waitForTypeInEditor('text cell', activeTextCellId); // Text cell should be in edit mode + + await app.code.dispatchKeybinding('escape'); // exit edit mode and stay in browse mode + await app.code.dispatchKeybinding('up'); // select code cell + await app.workbench.sqlNotebook.getActiveCell(activeCodeCellId); // check that the code cell is now active + await app.code.dispatchKeybinding('enter'); + await app.workbench.sqlNotebook.waitForTypeInEditor('test', activeCodeCellId); // code cell should be in edit mode after hitting enter + await app.code.dispatchKeybinding('escape'); // exit edit mode and stay in browse mode + await app.code.dispatchKeybinding('down'); // select text cell + await app.code.dispatchKeybinding('enter'); + await app.workbench.sqlNotebook.textCellToolbar.changeTextCellView('Split View'); + await app.workbench.sqlNotebook.waitForTypeInEditor('test', activeTextCellId); // text cell should be in edit mode after hitting enter + }); + + it('cannot move through cells when find widget is invoked', async function () { + const app = this.app as Application; + await app.workbench.sqlNotebook.newUntitledNotebook(); + await app.workbench.sqlNotebook.addCell('markdown'); + await app.workbench.sqlNotebook.exitActiveCell(); + await app.workbench.sqlNotebook.addCell('markdown'); + await app.workbench.sqlNotebook.exitActiveCell(); + await app.workbench.sqlNotebook.addCell('markdown'); + await app.code.dispatchKeybinding('escape'); + const activeCellId = (await app.workbench.sqlNotebook.getActiveCell()).attributes['id']; + await app.workbench.sqlNotebook.notebookFind.openFindWidget(); + await app.code.dispatchKeybinding('down'); + await app.workbench.sqlNotebook.getActiveCell(activeCellId); // verify that the active cell is the same + }); + }); + describe('Notebook Toolbar Actions', async () => { it('Collapse and Expand Cell', async function () { @@ -333,27 +377,27 @@ export function setup(opts: minimist.ParsedArgs) { it('can bold text with keyboard shortcut', async function () { const app = this.app as Application; - await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+b', 'p strong'); + await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+b`, 'p strong'); }); it('can italicize text with keyboard shortcut', async function () { const app = this.app as Application; - await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+i', 'p em'); + await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+i`, 'p em'); }); it('can underline text with keyboard shortcut', async function () { const app = this.app as Application; - await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+u', 'p u'); + await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+u`, 'p u'); }); it('can highlight text with keyboard shortcut', async function () { const app = this.app as Application; - await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+shift+h', 'p mark'); + await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+shift+h`, 'p mark'); }); it('can codify text with keyboard shortcut', async function () { const app = this.app as Application; - await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+shift+k', 'pre code'); + await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+shift+k`, 'pre code'); }); });