diff --git a/.github/workflows/publish-wiki.yml b/.github/workflows/publish-wiki.yml new file mode 100644 index 000000000..e3642ab92 --- /dev/null +++ b/.github/workflows/publish-wiki.yml @@ -0,0 +1,21 @@ +name: Publish wiki +on: + push: + branches: [ main ] + paths: + - wiki/** + - .github/workflows/publish-wiki.yml + +concurrency: + group: publish-wiki + cancel-in-progress: true + +permissions: + contents: write + +jobs: + publish-wiki: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Andrew-Chen-Wang/github-wiki-action@v4 diff --git a/wiki/ActionsControl.md b/wiki/ActionsControl.md new file mode 100644 index 000000000..8057d12d8 --- /dev/null +++ b/wiki/ActionsControl.md @@ -0,0 +1,28 @@ +![actionControl](https://user-images.githubusercontent.com/4181232/56589258-27bb1900-65e5-11e9-9aef-7643f44697f4.png) + +#### Look up the ActionsControl by title +Import and find the control through activity bar +```typescript +import { ActivityBar, ActionsControl } from 'vscode-extension-tester'; +... +// get actions control for 'Manage' +const control: ActionsControl = new ActivityBar().getGlobalAction('Manage'); +``` + +#### Open action menu +Click the action control to open its context menu +```typescript +const menu = await control.openActionMenu(); +``` + +#### Get title +Get the control's title +```typescript +const title = control.getTitle(); +``` + +#### Open context menu +Left click on the control to open the context menu (in this case has the same effect as openActionMenu) +```typescript +const menu = await control.openContextMenu(); +``` \ No newline at end of file diff --git a/wiki/ActivityBar.md b/wiki/ActivityBar.md new file mode 100644 index 000000000..17574a3cc --- /dev/null +++ b/wiki/ActivityBar.md @@ -0,0 +1,40 @@ +![activityBar](https://user-images.githubusercontent.com/4181232/56586855-c133fc00-65e0-11e9-9317-158d7fcedd43.png) + +#### Look up the Activity Bar +Import and find the activity bar +```typescript +import { ActivityBar } from 'vscode-extension-tester'; +... +const activityBar = new ActivityBar(); +``` + +#### Get view controls +Get handles for all view controls/buttons that operate the view containers +```typescript +const controls = await activityBar.getViewControls(); +``` + +#### Get view control by title +Find a view control/button in the activity bar by its title +```typescript +// get Explorer view control +const controls = await activityBar.getViewControl('Explorer'); +``` + +#### Get global actions +Get handles for all global actions buttons on the bottom of the action bar +```typescript +const actions = await activityBar.getGlobalActions(); +``` + +#### Get global action by title +Find global actions button by title +```typescript +const actions = await activityBar.getGlobalAction('Manage'); +``` + +#### Open context menu +Left click on the activity bar to open the context menu +```typescript +const menu = await activityBar.openContextMenu(); +``` \ No newline at end of file diff --git a/wiki/BottomBarPanel.md b/wiki/BottomBarPanel.md new file mode 100644 index 000000000..9b0ef8183 --- /dev/null +++ b/wiki/BottomBarPanel.md @@ -0,0 +1,30 @@ +![bottomBar](https://user-images.githubusercontent.com/4181232/56640335-099bfa00-6673-11e9-957f-37c47db20ff4.png) + +#### Lookup +```typescript +import { BottomBarPanel } from 'vscode-extension-tester'; +... +const bottomBar = new BottomBarPanel(); +``` + +#### Open/Close the panel +```typescript +// open +await bottomBar.toggle(true); +// close +await bottomBar.toggle(false); +``` + +#### Maximize/Restore the panel +```typescript +await bottomBar.maximize(); +await bottomBar.restore(); +``` + +#### Open specific view in the bottom panel +```typescript +const problemsView = await bottomBar.openProblemsView(); +const outputView = await bottomBar.openOutputView(); +const debugConsoleView = await bottomBar.openDebugConsoleView(); +const terminalView = await bottomBar.openTerminalView(); +``` \ No newline at end of file diff --git a/wiki/ContentAssist.md b/wiki/ContentAssist.md new file mode 100644 index 000000000..0cb899adf --- /dev/null +++ b/wiki/ContentAssist.md @@ -0,0 +1,23 @@ +![codeAssist](https://user-images.githubusercontent.com/4181232/56645020-20474e80-667d-11e9-9ebb-4f84c45d9080.png) + +#### Open/Lookup +```typescript +import { TextEditor, ContentAssist } from 'vscode-extension-tester'; +... +const contentAssist = await new TextEditor().toggleContentAssist(true); +``` + +#### Get Items +```typescript +// find if an item with given label is present +const hasItem = await contentAssist.hasItem('Get'); +// get an item by label +const item = await contentAssist.getItem('Get'); +// get all visible items +const items = await contentAssist.getItems(); +``` + +#### Select an Item +```typescript +await contentAssist.getItem('Get').click(); +``` \ No newline at end of file diff --git a/wiki/ContextMenu.md b/wiki/ContextMenu.md new file mode 100644 index 000000000..99fc1a30e --- /dev/null +++ b/wiki/ContextMenu.md @@ -0,0 +1,28 @@ +![contextMenu](https://user-images.githubusercontent.com/4181232/56651979-88e8f800-668a-11e9-97f6-0a3a7b582a8d.png) +Page object for any context menu opened by left-clicking an element that has a context menu. Title bar items also produce context menus when clicked. + +#### Open/Lookup +Typically, a context menu is opened by calling ```openContextMenu``` on elements that support it. For example: +```typescript +import { ActivityBar, ContextMenu } from 'vscode-extension-tester'; +... +const menu = await new ActivityBar().openContextMenu(); +``` + +#### Retrieve Items +```typescript +// find if an item with title exists +const exists = await menu.hasItem('Copy'); +// get a handle for an item +const item = await menu.getItem('Copy'); +// get all displayed items +const items = await menu.getItems(); +``` + +#### Select Item +```typescript +// recursively select an item in nested submenus +await menu.select('File', 'Preferences', 'Settings'); +// select an item that has a child submenu +const submenu = await menu.select('File', 'Preferences'); +``` \ No newline at end of file diff --git a/wiki/ContextMenuItem.md b/wiki/ContextMenuItem.md new file mode 100644 index 000000000..658c5b254 --- /dev/null +++ b/wiki/ContextMenuItem.md @@ -0,0 +1,28 @@ +![contextMenuItem](https://user-images.githubusercontent.com/4181232/56653068-26ddc200-668d-11e9-820e-dffb39000fea.png) + +#### Lookup +One can retrieve an item from an open context menu, much like follows: +```typescript +import { ActivityBar } from 'vscode-extension-tester'; +... +const menu = await new ActivityBar().openContextMenu(); +const item = await menu.getItem('References'); +``` + +#### Select/Click +```typescript +// if item has no children +await item.select(); +// if there is a submenu under the item +const submenu = await item.select(); +``` + +#### Get Parent Menu +```typescript +const parentMenu = item.getParent(); +``` + +#### Get Label +```typescript +const label = await item.getLabel(); +``` \ No newline at end of file diff --git a/wiki/CustomEditor.md b/wiki/CustomEditor.md new file mode 100644 index 000000000..551d164a1 --- /dev/null +++ b/wiki/CustomEditor.md @@ -0,0 +1,30 @@ +In case your extension contributes a `CustomEditor`/`CustomTextEditor`. Both are based on a webview, and the page object is a combination of a `Webview` and common editor functionality. + +#### Lookup +```typescript +import { CustomEditor } from 'vscode-extension-tester' +... +// make sure the editor is opened by now +const editor = new CustomEditor(); +``` + +#### Webview +The whole editor is serviced by a [[WebView]], we just need to get a reference to it. +```typescript +const webview = editor.getWebView(); +``` + +#### Common Functionality +Most editors share this: +```typescript +// check if there are unsaved changes +const dirty = await editor.isDirty(); +// save +await editor.save(); +// open 'save as' prompt +const prompt = await editor.saveAs(); +// get title +const title = await editor.getTitle(); +// get the editor tab +const tab = await editor.getTab(); +``` \ No newline at end of file diff --git a/wiki/CustomTreeSection.md b/wiki/CustomTreeSection.md new file mode 100644 index 000000000..0b6bc2eff --- /dev/null +++ b/wiki/CustomTreeSection.md @@ -0,0 +1,27 @@ +![customTree](https://user-images.githubusercontent.com/4181232/65507524-6e7fa880-dece-11e9-93a5-e6ead75afc4e.png) + +The 'custom' tree section, usually contributed by extensions as TreeView. All The behaviour is defined by the general [[ViewSection]] class. + +#### Lookup +```typescript +import { SideBarView, CustomTreeSection } from 'vscode-extension-tester'; +... +// Type is inferred automatically, the type cast here is used to be more explicit +const section = await new SideBarView().getContent().getSection('servers') as CustomTreeSection; +``` + +#### Get Welcome Content +Some sections may provide a welcome content when their tree is empty. +```typescript +// find welcome content, return undefined if not present +const welcome: WelcomeContentSection = await section.findWelcomeContent(); + +// get all the possible buttons and paragraphs in a list +const contents = await welcome.getContents(); + +// get all buttons +const btns = await welcome.getButtons(); + +// get paragraphs as strings in a list +const text = await welcome.getTextSections(); +``` \ No newline at end of file diff --git a/wiki/DebugConsoleView.md b/wiki/DebugConsoleView.md new file mode 100644 index 000000000..46919d11b --- /dev/null +++ b/wiki/DebugConsoleView.md @@ -0,0 +1,28 @@ +Page object needs extending, currently minimal support. + +#### Lookup +```typescript +import { BottomBarPanel, DebugConsoleView } from 'vscode-extension-tester'; +... +const debugView = await new BottomBarPanel().openDebugConsoleView(); +``` + +#### Text Handling +```typescript +// get all text as string +const text = await debugView.getText(); +// clear the text +await debugView.clearText(); +``` + +#### Expressions +```typescript +// type an expression +await debugView.setExpression('expression'); +// evaluate an existing expression +await debugView.evaluateExpression(); +// type and evaluate an expression +await debugView.evaluateExpression('expression'); +// get a handle for content assist +const assist = await debugView.getContentAssist(); +``` \ No newline at end of file diff --git a/wiki/DebugToolbar.md b/wiki/DebugToolbar.md new file mode 100644 index 000000000..890bb811b --- /dev/null +++ b/wiki/DebugToolbar.md @@ -0,0 +1,30 @@ +![toolbar](https://user-images.githubusercontent.com/4181232/122540755-3bc5fe00-d029-11eb-8b74-77ee740acdad.png) + +#### Lookup +```typescript +// get a handle for existing toolbar (i.e. debug session needs to be in progress) +const bar = await DebugToolbar.create(); +``` + +#### Buttons +```typescript +// continue +await bar.continue(); +// pause +await bar.pause(); +// step over +await bar.stepOver(); +// step into +await bar.stepInto(); +// step out +await bar.stepOut(); +// restart +await bar.restart(); +// stop +await bar.stop(); +``` + +#### Wait for code to pause again +```typescript +await bar.waitForBreakPoint(); +``` \ No newline at end of file diff --git a/wiki/DebugView.md b/wiki/DebugView.md new file mode 100644 index 000000000..ed5814fdc --- /dev/null +++ b/wiki/DebugView.md @@ -0,0 +1,24 @@ +![debugview](https://user-images.githubusercontent.com/4181232/122539414-e0dfd700-d027-11eb-9c72-f1745d7bb3c7.png) + +#### Lookup +```typescript +// open the view using the icon in the view container +const btn = await new ActivityBar().getViewControl('Run'); +const debugView = (await btn.openView()) as DebugView; +``` + +#### Launch Configurations +```typescript +// get title of current launch configuration +const config = await debugView.getLaunchConfiguration(); +// get titles of all available laynch configurations +const configs = await debugView.getLaunchConfigurations(); +// select launch configuration by title +await debugConfiguration.selectLaunchConfiguration('Test Launch'); +``` + +#### Launch +```typescript +// start selected launch configuration +await debugView.start(); +``` \ No newline at end of file diff --git a/wiki/Debugging-Tests.md b/wiki/Debugging-Tests.md new file mode 100644 index 000000000..7a3ff6242 --- /dev/null +++ b/wiki/Debugging-Tests.md @@ -0,0 +1,43 @@ +Attaching a debugger from VS Code can be achieved with a launch configuration such as this one: +``` +{ + "name": "Debug UI Tests", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/.bin/extest", + "args": [ + "setup-and-run", + "${workspaceFolder}/out/ui-test/*.js", + "--mocha_config", + "${workspaceFolder}/src/ui-test/.mocharc-debug.js" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" +} +``` +Sometimes Windows terminal has trouble interpreting the extecutable. If that happens, you can run the cli directly: +``` +{ + "name": "Debug UI Tests", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/vscode-extension-tester/out/cli.js", + "args": [ + "setup-and-run", + "${workspaceFolder}/out/ui-test/*.js", + "--mocha_config", + "${workspaceFolder}/src/ui-test/.mocharc-debug.js" + ], + "console": "integratedTerminal", + "runtimeExecutable": "node", + "internalConsoleOptions": "neverOpen" +} +``` + +Change the `args` to fit your needs. Note the usage of `--mocha_config`, this is the supported way of globally changing test options. In this case, the important option is timeout (the default 2 second timeout is hardly enough to debug anything). + +We recommend using separate mocha configuration files for running, and debugging the tests, so you can globally control timeouts for each. Be aware that using per-test case timeout will override the global settings. + +To learn more about mocha configuration, check [[Mocha-Configuration]]. + +An example debugging setup can be found in [helloworld-sample](../tree/master/sample-projects/helloworld-sample). \ No newline at end of file diff --git a/wiki/DefaultTreeSection.md b/wiki/DefaultTreeSection.md new file mode 100644 index 000000000..840420ac5 --- /dev/null +++ b/wiki/DefaultTreeSection.md @@ -0,0 +1,11 @@ +![section](https://user-images.githubusercontent.com/4181232/56656305-76c08700-6695-11e9-8630-e878ff478201.png) + +The 'default' tree section, as used in the explorer view. All The behaviour is defined by the general [[ViewSection]] class. + +#### Lookup +```typescript +import { SideBarView, DefaultTreeSection } from 'vscode-extension-tester'; +... +// Type is inferred automatically, the type cast here is used to be more explicit +const section = await new SideBarView().getContent().getSection('workspace') as DefaultTreeSection; +``` \ No newline at end of file diff --git a/wiki/DiffEditor.md b/wiki/DiffEditor.md new file mode 100644 index 000000000..0ca1ca61d --- /dev/null +++ b/wiki/DiffEditor.md @@ -0,0 +1,21 @@ +![diff](https://user-images.githubusercontent.com/4181232/77437498-04947d00-6de5-11ea-9d8e-d41dd5440a78.png) + +#### Lookup +```typescript +// through editors view +const diffEditor1 = await new EditorView().openEditor('editorTitle'); + +// directly +const diffEditor2 = new DiffEditor(); +``` + +#### Working with the Contents +Since diff editor is basicaly two text editors in one, the `DiffEditor` object gives you the ability to work with two editors: + +```typescript +// get the original editor +const original = await diffEditor1.getOriginalEditor(); + +// get the modified editor +const changed = await diffEditor1.getModifiedEditor(); +``` \ No newline at end of file diff --git a/wiki/EditorView.md b/wiki/EditorView.md new file mode 100644 index 000000000..6e0d26ce9 --- /dev/null +++ b/wiki/EditorView.md @@ -0,0 +1,98 @@ +![editorView](https://user-images.githubusercontent.com/4181232/56643169-5f73a080-6679-11e9-8b16-13f4b5f11a35.png) + +#### Lookup +```typescript +import { EditorView } from 'vscode-extension-tester'; +... +const editorView = new EditorView(); +``` + +#### Select an Editor Tab +```typescript +// open editor tab by title +const editor = await editorView.openEditor('package.json'); +``` + +#### Closing Editors +```typescript +// close an editor tab by title +await editorView.closeEditor('package.json'); +// close all open tabs +await editorView.closeAllEditors(); +``` + +#### Retrieve Open Tab Titles +```typescript +const titles = await editorView.getOpenEditorTitles(); +``` + +#### Editor Actions +```typescript +// find an editor action button by title +const button = await editorView.getAction('Open Changes'); +// also works for multiple editor groups, select the group by index, starting with 0 from the left +const buttonFromSecondGroup = await editorView.getAction('More Actions...', 1); +// get all visible action buttons, again you may specify a group index, default is 0 +const buttons = await editorView.getActions(); +``` + +### Editor Groups +By default, all EditorView methods work with the first (left-most) editor group, except `closeAllEditors` and `getOpenEditorTitles` which by default work across all groups. You can use indices to target specific editor groups, or you can get handles to directly work with `EditorGroup` objects. + +#### EditorView Actions on Editor Groups +```typescript +// open editor in the second group (from the left, using a zero-based index) +const editor = await editorView.openEditor('package.json', 1); + + +// close editor in the second group +await editorView.closeEditor('package.json', 1); + +// close all editors in the second group (and the whole group) +await editorView.closeAllEditors(1); + +// get open editor titles for the second group +const titles = await editorView.getOpenEditorTitles(1); +``` + +#### Retrieve Handles for the Editor Groups +Instead of working in context of the whole editor view, you can get a handle for a particular editor group. The `EditorGroup` object then exposes the same set of methods as `EditorView` (without the group retrieval though). + +```typescript +// get the handle for the second group +const group = await editorView.getEditorGroup(1); + +// get handles for all editor groups in an array +const groups = await editorView.getEditorGroups(); +``` + +### Editor Tabs +Another way to handle open editors is using the editor tabs. For that we have the `EditorTab` page object. + +#### Lookup +There are two basic ways to get `EditorTab` objects, through `EditorView`/`EditorGroup`: +```typescript +// using EditorView, the same principle applies to EditorGroup +// get tab by title from the first group +const tab = await editorView.getTabByTitle('Index.d.ts'); +// get all open tabs in a list +const tabs = await editorView.getOpenTabs(); +// get the active tab (or undefined if none is active) +const active = await editorView.getActiveTab(); +``` + +From an `Editor` instance: +```typescript +const editor = await editorView.openEditor('Index.d.ts'); +const etab = await editorView.getTab(); +``` + +#### Actions +```typescript +// get the tab title +const title = await tab.getTitle(); +// select the tab +await tab.select(); +// open the tab context menu +const menu = await tab.openContextMenu(); +``` \ No newline at end of file diff --git a/wiki/ExtensionsViewSection.md b/wiki/ExtensionsViewSection.md new file mode 100644 index 000000000..b067a9701 --- /dev/null +++ b/wiki/ExtensionsViewSection.md @@ -0,0 +1,64 @@ +![extSection](https://user-images.githubusercontent.com/4181232/65507937-53f9ff00-decf-11e9-8fd8-093b350cf547.png) + +Section in the Extensions view. Unlike the other section types, extension sections behave differently. + +#### Lookup +Get a section handle from an open side bar. +```typescript +import { SideBarView, ExtensionsViewSection } from 'vscode-extension-tester'; +... +// in this case the cast is required if you wish to use all the available functionality +const section = await new SideBarView().getContent().getSection('enabled') as ExtensionsViewSection; +``` + +#### Finding Items +Item lookup behaves in a completely different way to the tree sections. In this case it is based on the search bar on top of the view and as such is able to find items beyond the initial section. In this case you will need to manually clear the search bar in order to gain back access to the original section. +```typescript +// get all visible items inside the section +const items = await section.getVisibleItems(); + +// find an extension anywhere (including the marketplace) +const item = await section.findItem('npm'); +// clear the search bar so the original section reappears +await section.clearSearch(); + +// find an extension in the installed section +const item2 = await section.findItem('@installed java'); +// clear the search bar so the original section reappears +await section.clearSearch(); + +// open an item in the editor view +await section.openItem('@installed java'); +``` + +#### ExtensionsViewItem +![extension](https://user-images.githubusercontent.com/4181232/65508733-24e48d00-ded1-11e9-9f53-1e47e8d79943.png) + +Item representing an extension in the extensions view. + +##### Get information about the extension +```typescript +// get title +const title = await item.getTitle(); + +// get version +const version = await item.getVersion(); + +// get author +const author = await item.getAuthor(); + +// get description +const description = await item.getDescription(); + +// find if it is installed +const installed = await item.isInstalled(); +``` + +##### Operations +```typescript +// manage the item - open its context menu +const menu = await item.manage(); + +// install the extension +await item.install(); +``` \ No newline at end of file diff --git a/wiki/FindWidget.md b/wiki/FindWidget.md new file mode 100644 index 000000000..af729ae89 --- /dev/null +++ b/wiki/FindWidget.md @@ -0,0 +1,55 @@ +![find](https://user-images.githubusercontent.com/4181232/122541820-5fd60f00-d02a-11eb-99de-2c304bdc5dfe.png) + +#### Lookup +```typescript +// open the find widget from text editor +const editor = new TextEditor(); +const widget = await editor.openFindWidget(); +``` + +#### Search +```typescript +// set search text +await widget.setSearchText(); +// get search text +const text = await widget.getSearchText(); +// find next +await widget.nextMatch(); +// find previous +await widget.previousMatch(); +// get result counts +const counts = await widget.getResultCount(); +const currentResultNumber = counts[0]; +const totalCount = counts[1]; +``` + +#### Replace +```typescript +// toggle replace on/off +await widget.toggleReplace(true); // or false +// set replace text +await widget.setReplaceText(); +// get replace text +const text = await widget.getReplaceText(); +// replace current match +await widget.replace(); +// replace all matches +await widget.replaceAll(); +``` + +#### Switches +```typescript +// switch 'Match Case' on/off +await widget.toggleMatchCase(true/false); +// switch 'Match Whole Word' on/off +await widget.toggleMatchWholeWord(true/false); +// switch 'Use Regular Expression' on/off +await widget.toggleUseRegularExpression(true/false); +// switch 'Preserve Case' on/off +await widget.togglePreserveCase(true/false); +``` + +#### Close +```typescript +await widget.close(); +``` \ No newline at end of file diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 000000000..46754bb1d --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,67 @@ +# VS Code Extension Tester +VS Code Extension Tester is a package designed to help you run UI tests for your VS Code extensions using selenium-webdriver. +Simply install it into your extension devDependencies to get started: + +```npm install --save-dev vscode-extension-tester``` + + +As such there are two major parts to the project: + +## Test Setup +The first part automates all the steps necessary to launch UI tests of your VSCode extension: + - Download a test instance of VS Code + - Download the appropriate version of ChromeDriver + - Package and install your extension into the VS Code instance + - Launch the VS Code instance using webdriver + - Run your tests + +Find more about the test setup: [[Test-Setup]] + +See how to change the Mocha test runner configuration: [[Mocha-Configuration]] + +Once the setup is complete, check out the sample test file: [[Writing-Simple-Tests]]. + +Debugging UI tests from VSCode: [[Debugging-Tests]] + +*** + + +## Page Object APIs +Once your tests are set up and running, you may use the convenient page object API to handle parts of VSCode without sifting through its DOM. Though, if using pure webdriver is preferred, all the webdriver APIs are exported as well. + +Find documentation about the Page Object APIs: [[Page-Object-APIs]]. + +*** + + +## Opening Files and Folders +Opening files and folders is basic functionality of VS Code. + +Since 4.2.0, the most convenient way to open files and folders is the `openResources` method in `VSBrowser`. Use it to open potentially multiple folders or files at the same time (method also waits for workbench to refresh). Files are opened in the editor, single folder will be opened directly in the explorer, multiple folders will be opened as a multi-workspace. + +It is recommended to use absolute paths, relative paths are based on process' cwd. +```typescript +await VSBrowser.openResources(`${path/to/folder1}`, ${path/to/file1}, ${path/to/folder2}) +``` + +### Using Dialogs +As of vscode 1.50 files/folders can be opened using the Simple Dialog, which uses an input box to select paths. +If you are running extension tester 4.2.0 or newer, the Simple Dialog is enabled by default. On older versions, make sure to use the following settings. +``` +Files > Simple Dialog > Enable: True +Window > Dialog Style: Custom +``` +You can also pass in custom options JSON file to the command that runs the tests using the `-o` flag. The property names are as follows: +``` +{ + "files.simpleDialog.enable": true, + "window.dialogStyle": "custom" +} +``` +Invoking the open dialog (e.g. via `File > Open Folder...`) then opens an input box. To open a folder, you can use the following code: +```typescript +const input = await InputBox.create(); +await input.setText('/path/to/your/folder/'); +await input.confirm(); +``` +Make sure to use a trailing separator for folders, otherwise confirming will only autocorrect the path and you'll need to call it again. \ No newline at end of file diff --git a/wiki/Input.md b/wiki/Input.md new file mode 100644 index 000000000..f23af8ad3 --- /dev/null +++ b/wiki/Input.md @@ -0,0 +1,101 @@ +![input](https://user-images.githubusercontent.com/4181232/56664195-fce5c900-66a7-11e9-950f-566a975f9adc.png) + +There are two types of input boxes in VS Code: 'quick input' and 'quick open'. They can both be made to look the same, have nearly the same functionality, but different DOM. Hence each is handled by a different page object. Make sure you look for the correct type, otherwise you will likely receive a lookup error. + +Another caveat of an input box in VS Code is that unless it has already been opened, the underlying DOM element does not exist. This may result in lookup errors when using the constructor to search for input boxes. Instead, we recommend using the static `create` method that will safely wait for the element to appear. + +### InputBox +This is the page object for the plain and simple input box you either put some text into, or just pick from the quick pick selection. + +#### Lookup +There are no methods that explicitly open and return a handle to an input box. So first make sure it is opened before you try to handle it. +```typescript +import { InputBox } from 'vscode-extension-tester'; +... +// safe way to find an inputbox +const input = InputBox.create(); + +// only use when manually waiting for the element, or when inputbox has already been opened +const input = new InputBox(); +``` + +#### Quick Input Message +The only difference in the GUI to quick open box is the option to display a message underneath a quick input box. To get it, use: +```typescript +const message = await input.getMessage(); +``` + +### QuickOpenBox +**As of VS Code 1.44.0, QuickOpenBox is no longer in use. InputBox is used exclusively.** + +Analogically to quick input, this is the page object handling quick open box. This type of input is usually used to (apart from other functions) open files or folders. The command prompt is also built on this element. + +#### Lookup +```typescript +import { QuickOpenBox } from 'vscode-extension-tester'; +... +// safe way to find a quickopenbox +const input = QuickOpenBox.create(); + +// only use when manually waiting for the element, or when quickopenbox has already been opened +const input = new QuickOpenBox(); +``` + +### Common Functionality +#### Text Manipulation +```typescript +// get text in the input box +const text = await input.getText(); +// replace text in the input box with a string +await input.setText('amazing text'); +// get the placeholder text +const placeholder = await input.getPlaceHolder(); +``` + +#### Actions +```typescript +// confirm (press enter in the input) +await input.confirm(); +// cancel (press escape in the input) +await input.cancel(); +// get quick pick options +const picks = await input.getQuickPicks(); +// search for a quick pick item by text or index and click it (includes scrolling if item is not visible) +await input.selectQuickPick(1); +await input.selectQuickPick('Input.d.ts'); +// search for a quick pick item and get a handle for it (includes scrolling if item is not visible) +const pick = await input.findQuickPick(1); +const pick2 = await input.findQuickPick('Input.d.ts'); +``` + +#### Progress +```typescript +// find if there is an active progress bar +const hasProgress = await input.hasProgress(); +``` + +#### Title Bar +An input box can have its own title bar for the multi step flow +```typescript +// get the current title, returns undefined if no title bar is present +const title = await input.getTitle(); +// click the back button from the title bar +await input.back(); +``` + +### QuickPickItem +Page objects retrieved when calling ```getQuickPick``` +```typescript +const picks = await input.getQuickPicks(); +const pick = picks[0]; +``` + +#### Actions +```typescript +// get text +const text = await pick.getText(); +// get index +const index = pick.getIndex(); +// select (click) the item (recommend to use input.selectQuickPick() if possible) +await pick.select(); +``` \ No newline at end of file diff --git a/wiki/Mocha-Configuration.md b/wiki/Mocha-Configuration.md new file mode 100644 index 000000000..fb8f966bc --- /dev/null +++ b/wiki/Mocha-Configuration.md @@ -0,0 +1,36 @@ +Since this framework is using Mocha programatically, we lose certain ways to configure the test runner. We do however support using mocha configuration files, with roughly the same functionality as described in [mochajs documentation](https://mochajs.org/#configuring-mocha-nodejs). + +### Configuration Options +Extester supports three formats of mocha config files: + - javascript file (.js) + - json file (.json) + - yaml file (.yml | .yaml) + +When using a JS file, make sure the options object is being exported as demonstrated [here](https://github.com/mochajs/mocha/blob/master/example/config/.mocharc.js). + +You can check out what options are supported in the [Mocha API documentation](https://mochajs.org/api/mocha). Any invalid mocha options declared will be ignored. + +### Loading your Config File +By default, the framework is going to scan the root of your project for files named `.mocharc` with one of the supported extensions (as does Mocha). If multiple files are present, the priority then is `JS (.mocharc.js) > JSON (.mocharc.json) > YAML (.mocharc.yml, .mocharc.yaml)`. + +Alternatively, you may use the `-m` flag with the command that runs your tests to specify a different path to your config file. For example +``` +extest setup-and-run -m +``` + +### Type-safe Configuration Files +If you wish to have your configuration type-checked, you can write the configuration in TypeScript using the `MochaOptions` interface. Make sure the .ts file is compiled, then use the `-m` flag to point to the compiled configuration. + +An example config.ts file might look like this: +```typescript +import { MochaOptions } from "vscode-extension-tester"; + +const options: MochaOptions = { + reporter: 'spec', + slow: 75, + timeout: 2000, + ui: 'bdd' +} + +export default options; +``` diff --git a/wiki/ModalDialog.md b/wiki/ModalDialog.md new file mode 100644 index 000000000..9a13165f2 --- /dev/null +++ b/wiki/ModalDialog.md @@ -0,0 +1,26 @@ +![dialog](https://user-images.githubusercontent.com/4181232/108175328-ec1a0900-7100-11eb-97f5-e9c52ad19c78.png) + +Only available if you have the custom dialog style enabled, use `"window.dialogStyle": "custom"` in the settings to do so. + +#### Look up +```typescript +const dialog = new ModalDialog(); +``` + +#### Get the contents +```typescript +// get the message (the bold text) +const message = await dialog.getMessage(); + +// get the details (the not so bold text) +const details = await dialog.getDetails(); + +// get the button web elements +const buttons = await dialog.getButtons() +``` + +#### Push a button +```typescript +// push button with a given title +await dialog.pushButton('Save All'); +``` \ No newline at end of file diff --git a/wiki/Notification.md b/wiki/Notification.md new file mode 100644 index 000000000..c92af2031 --- /dev/null +++ b/wiki/Notification.md @@ -0,0 +1,34 @@ +![notification](https://user-images.githubusercontent.com/4181232/56662617-b478dc00-66a4-11e9-9b1a-193efb96440b.png) + +#### Lookup +To get notifications outside the notifications center, one should use a ```Workbench``` object: +```typescript +import { Workbench } from 'vscode-extension-tester'; +... +const notifications = await new Workbench().getNotifications(); +const notification = notifications[0]; +``` + +#### Get Some Info +```typescript +// get the message +const message = await notification.getMessage(); +// get the type (error/warning/info) +const type = await notification.getType(); +// get the source as string, if shown +const source = await notification getSource(); +// find if there is an active progress bar +const hasProgress = await notification.hasProgress(); +``` + +#### Take Action +```typescript +// dismiss the notification +await notification.dismiss(); +// get available actions (buttons) +const actions = await notification.getActions(); +// get an action's title (text) +const title = actions[0].getTitle(); +// take action (i.e. click a button with title) +await notification.takeAction('Install All'); +``` \ No newline at end of file diff --git a/wiki/NotificationsCenter.md b/wiki/NotificationsCenter.md new file mode 100644 index 000000000..2e4afc64d --- /dev/null +++ b/wiki/NotificationsCenter.md @@ -0,0 +1,26 @@ +![center](https://user-images.githubusercontent.com/4181232/56663420-55b46200-66a6-11e9-8567-4dbde9a1ecb4.png) + +#### Lookup +To open the notifications center, use either ```Workbench``` or ```StatusBar``` object: +```typescript +import { Workbench, StatusBar, NotificationType } from 'vscode-extension-tester'; +... +center = await new Workbench().openNotificationsCenter(); +center1 = await new StatusBar().openNotificationsCenter(); +``` + +#### Get the Notifications +```typescript +// get all notifications +const notifications = await center.getNotifications(NotificationType.Any); +// get info notifications +const infos = await center.getNotifications(NotificationType.Info); +``` + +#### Clear and Close +```typescript +// clear all notifications +await center.clearAllNotifications(); +// close the notifications center +await center.close(); +``` \ No newline at end of file diff --git a/wiki/OpenDialog.md b/wiki/OpenDialog.md new file mode 100644 index 000000000..fd72aadd5 --- /dev/null +++ b/wiki/OpenDialog.md @@ -0,0 +1,21 @@ +/!\ DEPRECATED + +Dialog for opening/adding files and folders into workspace. + +#### Initialize +Since the dialogs are different on each system, you will need to initialize the dialog through ```DialogHandler```. +```typescript +import { DialogHandler } from 'vscode-extension-tester-native'; +... +const dialog = await DialogHandler.getOpenDialog(); +``` + +#### Select a Folder +```typescript +// navigate to a folder/file +await dialog.selectPath('/my/awesome/folder'); +// confirm +await dialog.confirm(); +// or cancel +await dialog.cancel(); +``` \ No newline at end of file diff --git a/wiki/OutputView.md b/wiki/OutputView.md new file mode 100644 index 000000000..6a4102e72 --- /dev/null +++ b/wiki/OutputView.md @@ -0,0 +1,24 @@ +![output](https://user-images.githubusercontent.com/4181232/56642182-2c301200-6677-11e9-9ef3-70fdb914254c.png) + +#### Lookup +```typescript +import { BottomBarPanel, OutputView } from 'vscode-extension-tester'; +... +const outputView = await new BottomBarPanel().openOutputView(); +``` + +#### Text Actions +```typescript +// get all text +const text = await outputView.getText(); +// clear text +await outputView.clearText(); +``` + +#### Channel Selection +```typescript +// get names of all available channels +const names = await outputView.getChannelNames(); +// select a channel from the drop box by name +await outputView.selectChannel('Git'); +``` \ No newline at end of file diff --git a/wiki/Page-Object-APIs.md b/wiki/Page-Object-APIs.md new file mode 100644 index 000000000..4007cb3eb --- /dev/null +++ b/wiki/Page-Object-APIs.md @@ -0,0 +1,70 @@ +In order to keep everyone from having to go through the VS Code DOM, which is quite complicated, the framework provides page objects to work with parts of VS Code directly. + +See the individual page object pages below for quick usage guide. For complete API reference, you can generate typedoc with ```npm run doc``` and check the ```docs``` folder. + +##### Activity Bar + - [[ActionsControl]] + - [[ActivityBar]] + - [[ViewContol]] + +##### Bottom Bar + - [[BottomBarPanel]] + - [[DebugConsoleView]] + - [[ProblemsView]] + - [[OutputView]] + - [[TerminalView]] + +##### Dialogs + - [[ModalDialog]] + +##### Editor + - [[ContentAssist]] + - [[FindWidget]] + - [[TextEditor]] + - [[EditorView]] + - [[SettingsEditor]] + - [[Setting]] + - [[WebView]] + - [[DiffEditor]] + - [[CustomEditor]] + +##### Menu + - [[ContextMenu]] + - [[ContextMenuItem]] + +##### Title Bar + - [[TitleBar]] + - [[TitleBarItem]] + - [[WindowControls]] + +##### Side Bar + - [[SideBarView]] + - [[ViewContent]] + - [[ViewItem]] + - [[ViewSection]] + - [[DefaultTreeSection]] + - [[CustomTreeSection]] + - [[ExtensionsViewSection]] + - [[ViewTitlePart]] + - [[ScmView]] + - [[DebugView]] + +##### Status Bar + - [[StatusBar]] + +##### Workbench + - [[Notification]] + - [[NotificationsCenter]] + - [[Workbench]] + - [[Input]] + - [[DebugToolbar]] + +*** + +##### Native Dialogs +There is also a limited support for handling native dialogs done via virtual key presses. This requires installing the module `vscode-extension-tester-native` alongside the main extension tester package. + +If you venture into this territory, make sure the application does not lose focus and you are not touching the keyboard while a native dialog is being handled. + - [[OpenDialog]] + +Extest now also contributes new commands to open files or folders without using native dialogs. If you simply need either open, we recommend using the commands instead of the open dialog for increased stability. Learn more about opening files and folders [here](https://github.com/redhat-developer/vscode-extension-tester/wiki#opening-files-and-folders). diff --git a/wiki/ProblemsView.md b/wiki/ProblemsView.md new file mode 100644 index 000000000..b7a1cb663 --- /dev/null +++ b/wiki/ProblemsView.md @@ -0,0 +1,54 @@ +![problems](https://user-images.githubusercontent.com/4181232/56641152-fe49ce00-6674-11e9-9a5d-096a61c0b835.png) + +#### Lookup +```typescript +import { BottomBarPanel, ProblemsView } from 'vscode-extension-tester'; +... +const problemsView = await new BottomBarPanel().openProblemsView(); +``` + +#### Set Filter +Fill in a string into the filter box. +```typescript +await problemsView.setFilter('**/filter/glob*'); +``` + +#### Collapse All Markers +```typescript +await problemsView.collapseAll(); +``` + +#### Get Handles to All Markers +```typescript +import { MarkerType } from 'vscode-extension-tester'; +... +// get all markers regardless of type +const markers = await problemsView.getAllMarkers(MarkerType.Any); +// get all error markers +const errors = await problemsView.getAllMarkers(MarkerType.Error); +// get all warning markers +const errors = await problemsView.getAllMarkers(MarkerType.Warning); +// get all file markers +const errors = await problemsView.getAllMarkers(MarkerType.File); +``` + +### Marker +Markers represent items displayed in the problems view. + +#### Retrieval +``` typescript +const markers = await problemsView.getAllMarkers(MarkerType.Any); +const marker = markers[0]; +``` + +#### Actions +``` typescript +// get the marker type +const type = await marker.getType(); +// get the text of the marker +const text = await marker.getText(); +// expand the marker if available +await marker.toggleExpand(true); +// collapse +await marker.toggleExpand(false); +``` \ No newline at end of file diff --git a/wiki/ScmView.md b/wiki/ScmView.md new file mode 100644 index 000000000..3a272d09e --- /dev/null +++ b/wiki/ScmView.md @@ -0,0 +1,76 @@ +![scm](https://user-images.githubusercontent.com/4181232/83526397-af60a000-a4e6-11ea-9f3f-c628a866ace3.png) + +#### Lookup +The best way to open the view is to use the activity bar on the left. It might take a second for the view to initialize, so make sure to account for it. +```typescript +view = await new ActivityBar().getViewControl('Source Control').openView() as ScmView; +``` + +#### Actions +In an SCM view we can do two things: initialize a repository if none exists, or retrieve the scm providers open in the current workspace: +```typescript +// get a provider (repository) by title +const provider = await view.getProvider('vscode-extension-tester'); +// get all providers (useful if you have multiple repositories open) +const providers = await view.getProviders(); +// initialize repository if none is present in current workspace +await view.initializeRepository(); +``` + +### Working with SCM Providers +Each `ScmProvider` object corresponds to a section in the `ScmView`. + +#### Info Retrieval +```typescript +// get the title +const title = await provider.getTitle(); +// get the type of scm (e.g. git or svn) +const type = await provider.getType(); +// get the number of changed files +const changes = await provider.getChangeCount(); +``` + +#### Basic Actions +There are several buttons to push and an input field to fill, which you can do as follows: +```typescript +// click an action button (the buttons are either on the title part on the top for a single repo, or next to the provider title for multiple repos). For instance, refresh: +await provider.takeAction('Refresh'); + +// click the `More Actions` button to open a context menu with all the available commands +const contextmenu = await provider.openMoreActions(); + +// Fill in the commit message and make a commit +await provider.commitChanges('Commit message'); +``` + +#### Handling Changes +The displayed changes are represented by the `ScmChange` page object. You can retrieve them from the `ScmProvider`: +```typescript +// get unstaged changes +const changes = await provider.getChanges(); + +// get staged changes +const staged = await provider.getChanges(true); +``` + +### Working with SCM Changes +Lets look at how to handle the tree items in the SCM View, using `ScmChange` page objects. + +```typescript +const change = changes[0]; + +// get the label (file name) +const label = await change.getLabel(); +// get description (if available, e.g. path in hierarchical display) +const description = await change.getDescription(); +// get git status (e.g. 'Modified', 'Untracked', etc.) +const status = await change.getStatus(); +// find if the item is expanded (only makes sense in hierarchical display) +const expanded = await change.isExpanded(); + +// toggle expand state to whichever you like, works only for folders in hierarchical display +await change.toggleExpand(true); // or false to collapse it :) + +// use on of the action buttons for the item, e.g. stage +await change.takeAction('Stage Changes'); +``` \ No newline at end of file diff --git a/wiki/Setting.md b/wiki/Setting.md new file mode 100644 index 000000000..474b838e6 --- /dev/null +++ b/wiki/Setting.md @@ -0,0 +1,42 @@ +![setting](https://user-images.githubusercontent.com/4181232/62535346-76668900-b84b-11e9-8aa3-a07f25e1e37e.png) + +#### Lookup +Settings can be located through a [[SettingsEditor]] object: +```typescript +import { Workbench } from 'vscode-extension-tester'; +... +// open the settings editor and get a handle on it +const settingsEditor = await new Workbench().openSettings(); + +// look for a setting named 'Auto Save' under 'Editor' category +const setting = await settingsEditor.findSetting('Auto Save', 'Files'); +``` + +#### Retrieve Information +```typescript +// get the title +const title = setting.getTitle(); + +// get the category +const category = setting.getCategory(); + +// get the description +const decription = await setting.getDescription(); +``` + +#### Handling Values +All setting types share the same functions to manipulate their values, however the value types and possible options vary between setting types. +```typescript +// generic value retrieval +const value = await setting.getValue(); + +// generic setting of a value +await setting.setValue('off'); +``` + +##### Setting Value Types +Currently, there are four supported types of setting values: text box, combo box, checkbox and link. + - Text box allows putting in an arbitrary string value, though there might be value checks afterwards that are not handled by this class. + - Combo box only allows inputs from its range of options. If you cast the setting to `ComboSetting`, you will be able to retrieve these options by calling the `getValues` method. + - Checkbox only accepts boolean values, other values are ignored + - Link does not have any value, `getValue` and `setValue` throw an error. Instead, casting the object to `LinkSetting` will allow you to call the `openLink` method, which will open settings.json file in a text editor. \ No newline at end of file diff --git a/wiki/SettingsEditor.md b/wiki/SettingsEditor.md new file mode 100644 index 000000000..1d44e78d3 --- /dev/null +++ b/wiki/SettingsEditor.md @@ -0,0 +1,26 @@ +![settings](https://user-images.githubusercontent.com/4181232/62535349-78304c80-b84b-11e9-80ae-25b587f11354.png) + +#### Lookup +Settings editor can be opened through variety of ways, recommended way is using the [[Workbench]] class: +```typescript +import { Workbench, SettingsEditor } from 'vscode-extension-tester' +... +const settingsEditor = await new Workbench().openSettings(); +``` + +#### Find a Setting Item in the Editor +Search for a setting with a given name and category, see more about the [[Setting]] object: +```typescript +// look for a setting named 'Auto Save' under 'Editor' category +const setting = await settingsEditor.findSetting('Auto Save', 'Editor'); + +// find a setting in nested categories, e.g. 'Enable' in 'Files' > 'Simple Dialog' +const setting1 = await settingsEditor.findSetting('Enable', 'Files', 'Simple Dialog'); +``` + +#### Switch Settings Perspectives +VSCode has two perspectives for its settings: 'User' and 'Workspace'. If your VSCode instance loads from both user and workspace settings.json files, you will be able to switch the perspectives in the editor: +```typescript +// switch to Workspace perspective +await settingsEditor.switchToPerspective('Workspace'); +``` \ No newline at end of file diff --git a/wiki/SideBarView.md b/wiki/SideBarView.md new file mode 100644 index 000000000..b51022cbb --- /dev/null +++ b/wiki/SideBarView.md @@ -0,0 +1,19 @@ +![sideBar](https://user-images.githubusercontent.com/4181232/56655128-327fb780-6692-11e9-83d0-d19ff1f8a836.png) + +#### Lookup +```typescript +import { ActivityBar, SideBarView } from 'vscode-extension-tester'; +// to look up the currently open view (if any is open) +const view = new SideBarView(); +// to open a specific view and look it up +const control = await new ActivityBar().getViewControl('Explorer'); +const view1 = await control.openView(); +``` + +#### Get Individual Parts +```typescript +// to get the title part +const titlePart = await view.getTitlePart(); +// to get the content part +const content = await view.getContent(); +``` \ No newline at end of file diff --git a/wiki/StatusBar.md b/wiki/StatusBar.md new file mode 100644 index 000000000..22d31a61c --- /dev/null +++ b/wiki/StatusBar.md @@ -0,0 +1,48 @@ +![status](https://user-images.githubusercontent.com/4181232/56661682-91e5c380-66a2-11e9-859d-1974cb98006d.png) + +#### Lookup +```typescript +import { StatusBar } from 'vscode-extension-tester'; +... +const statusbar = new StatusBar(); +``` + +#### Notifications Center +```typescript +// open notifications center +const center = await statusbar.openNotificationsCenter(); +// close +await statusbar.closeNotificationsCenter(); +``` + +#### Editor Status +```typescript +// open language selection input +await statusbar.openLanguageSelection(); +// get current language as string +const langString = await statusbar.getCurrentLanguage(); +// open line ending selection input +await statusbar.openLineEndingSelection(); +// get current line ending as string +const endingString = await statusbar.getCurrentLineEnding(); +// open encoding selection input +await statusbar.openEncodingSelection(); +// get current encoding as string +const encodingString = await statusbar.getCurrentEncoding(); +// open indentation selection input +await statusbar.openIndentationSelection(); +// get current indentation as string +const indentString = await statusbar.getCurrentIndentation(); +// open line selection input +await statusbar.openLineSelection(); +// get current position as string (Ln X, Col Y) +const posString = await statusbar.getCurrentPosition(); +``` + +#### Arbitrary Status Items +```typescript +// find a status item by title +const item = await statusbar.getItem('Select Encoding'); +// get all status items as web elements +const items = await statusbar.getItems(); +``` \ No newline at end of file diff --git a/wiki/Taking Screenshots.md b/wiki/Taking Screenshots.md new file mode 100644 index 000000000..834d80f4f --- /dev/null +++ b/wiki/Taking Screenshots.md @@ -0,0 +1,21 @@ +It is possible to take screenshots when testing Visual Studio Code extensions. +Screenshot can be captured by calling: + +```js +VSBrowser.instance.takeScreenshot(basename: string) +``` + +Captured screenshots will be saved in *screenshots* folder which can be found in *test-resources* folder. +File name will be generated from given basename in the following format: `${basename}.png`. + +#### Mocha integration + +Tester takes screenshots on all failed test cases. Screenshot name is +determined by calling `this.currentTest.fullTitle()`. This feature does not apply to Mocha +hooks by default. In order to capture screenshots on failed hooks, one +must import vscode-extension-tester hook. + +```js +// Supported hooks: before, beforeEach, after and afterEach +import { before } from 'vscode-extension-tester' +``` diff --git a/wiki/TerminalView.md b/wiki/TerminalView.md new file mode 100644 index 000000000..74da05388 --- /dev/null +++ b/wiki/TerminalView.md @@ -0,0 +1,30 @@ +![term](https://user-images.githubusercontent.com/4181232/56642706-53d3aa00-6678-11e9-92b5-35c535ab39af.png) + +#### Lookup +```typescript +import { BottomBarPanel, TerminalView } from 'vscode-extension-tester'; +... +const terminalView = await new BottomBarPanel().openTerminalView(); +``` + +#### Terminal Selection +```typescript +// get names of all available terminals +const names = await terminalView.getChannelNames(); +// select a terminal from the drop box by name +await terminalView.selectChannel('Git'); +``` + +#### Execute Commands +```typescript +await terminalView.executeCommand('git status'); +``` + +#### Get Text +Select all text and copy it to a variable. No formatting provided. + +- To allow copy text in terminal on macOS, you need to add specific setup in .vscode/settings.json `"terminal.integrated.copyOnSelection": true` + +```typescript +const text = await terminalView.getText(); +``` \ No newline at end of file diff --git a/wiki/Test-Setup.md b/wiki/Test-Setup.md new file mode 100644 index 000000000..409bc4198 --- /dev/null +++ b/wiki/Test-Setup.md @@ -0,0 +1,227 @@ +The Extension Tester offers both CLI and API to perform all the setup actions. That way you can simply integrate it into your npm scripts, or just call it from your code if that is more preferable. + +## Using the CLI +All the CLI actions are available with the command ```extest``` which is available to your npm scripts once the package is installed. The default storage folder for all test resources is ```test-resources``` in the extension's root. To avoid build problems, make sure to exclude it from your ```tsconfig``` and ```vsce```. + +#### Download VS Code +If you wish to manually download VS Code of a given version +``` +Usage: extest get-vscode [options] + +Download VSCode for testing + +Options: + -s, --storage Use this folder for all test resources + -c, --code_version Version of VSCode to download + -t, --type Type of VSCode release (stable/insider) + -h, --help output usage information +``` + +#### Download ChromeDriver +Download chrome driver for a given version of VS Code +``` +Usage: extest get-chromedriver [options] + +Download ChromeDriver binary + +Options: + -s, --storage Use this folder for all test resources + -c, --code_version Version of VSCode you want to run with the ChromeDriver + -t, --type Type of VSCode release (stable/insider) + -h, --help display help for command +``` + +#### Build and Install Extension from vsix +To manually build and install your extension. This step is not necessary to run the tests, since the framework will run the extension directly from source. + +``` +Usage: extest install-vsix [options] + +Install extension from vsix file into test instance of VSCode + +Options: + -s, --storage Use this folder for all test resources + -e, --extensions_dir VSCode will use this directory for managing extensions + -f, --vsix_file path/URL to vsix file containing the extension + -y, --yarn Use yarn to build the extension via vsce instead of npm (default: false) + -t, --type Type of VSCode release (stable/insider) + -h, --help display help for command + +``` + +#### Install Extensions from Marketplace +To also install arbitrary extensions by ID into your test instance. +``` +Usage: extest install-from-marketplace [options] [ids...] + +Install extension from marketplace with given into test instance of VSCode + +Options: + -s, --storage Use this folder for all test resources + -e, --extensions_dir VSCode will use this directory for managing extensions + -t, --type Type of VSCode release (stable/insider) + -h, --help display help for command + +``` + + +#### Perform All Test Setup +To perform all test setup steps in one command +``` +Usage: extest setup-tests [options] + +Set up all necessary requirements for tests to run + +Options: + -s, --storage Use this folder for all test resources + -e, --extensions_dir VSCode will use this directory for managing extensions + -c, --code_version Version of VSCode to download + -t, --type Type of VSCode release (stable/insider) + -y, --yarn Use yarn to build the extension via vsce instead of npm (default: false) + -i, --install_dependencies Automatically install extensions your extension depends on (default: false) + -h, --help display help for command + +``` + +#### Run Tests +To run test files +``` +Usage: extest run-tests [options] + +Run the test files specified by a glob pattern + +Options: + -s, --storage Use this folder for all test resources + -e, --extensions_dir VSCode will use this directory for managing extensions + -c, --code_version Version of VSCode to be used + -t, --type Type of VSCode release (stable/insider) + -o, --code_settings Path to custom settings for VS Code json file + -u, --uninstall_extension Uninstall the extension after the test run (default: false) + -m, --mocha_config Path to Mocha configuration file + -l, --log_level Log messages from webdriver with a given level (default: "Info") + -f, --offline Attempt to run without internet connection, make sure to have all requirements downloaded (default: false) + -h, --help display help for command + +``` + +#### Set up and Run Tests +Perform all test setup and run tests in a single command +``` +Usage: extest setup-and-run [options] + +Perform all setup and run tests specified by glob pattern + +Options: + -s, --storage Use this folder for all test resources + -e, --extensions_dir VSCode will use this directory for managing extensions + -c, --code_version Version of VSCode to download + -t, --type Type of VSCode release (stable/insider) + -o, --code_settings Path to custom settings for VS Code json file + -y, --yarn Use yarn to build the extension via vsce instead of npm (default: false) + -u, --uninstall_extension Uninstall the extension after the test run (default: false) + -m, --mocha_config Path to Mocha configuration file + -i, --install_dependencies Automatically install extensions your extension depends on (default: false) + -l, --log_level Log messages from webdriver with a given level (default: "Info") + -f, --offline Attempt to run without internet connection, make sure to have all requirements downloaded (default: false) + -h, --help display help for command + +``` + + +## Using the API +The same actions are available in the ```ExTester``` class as API: +```typescript +export interface SetupOptions { + /** version of VSCode to test against, defaults to latest */ + vscodeVersion?: string; + /** when true run `vsce package` with the `--yarn` flag */ + useYarn?: boolean; + /** install the extension's dependencies from the marketplace. Defaults to `false`. */ + installDependencies?: boolean; +} +export declare const DEFAULT_SETUP_OPTIONS: { + vscodeVersion: string; + installDependencies: boolean; +}; + +export interface RunOptions { + /** version of VSCode to test against, defaults to latest */ + vscodeVersion?: string; + /** path to custom settings json file */ + settings?: string; + /** remove the extension's directory as well (if present) */ + cleanup?: boolean; + /** path to a custom mocha configuration file */ + config?: string; + /** logging level of the webdriver */ + logLevel?: VSBrowserLogLevel; + /** try to perform all setup without internet connection, needs all requirements pre-downloaded manually */ + offline?: boolean; +} +/** defaults for the [[RunOptions]] */ +export declare const DEFAULT_RUN_OPTIONS: { + vscodeVersion: 'latest', + settings: '', + logLevel: logging.Level.INFO, + offline: false +}; + +/** + * VSCode Extension Tester + */ +export declare class ExTester { + private code; + private chrome; + constructor(storageFolder?: string, releaseType?: ReleaseQuality, extensionsDir?: string); + /** + * Download VSCode of given version and release quality stream + * @param version version to download, default latest + */ + downloadCode(version?: string): Promise; + /** + * Install the extension into the test instance of VS Code + * @param vsixFile path to extension .vsix file. If not set, default vsce path will be used + * @param useYarn when true run `vsce package` with the `--yarn` flag + */ + installVsix({ vsixFile, useYarn }?: { + vsixFile?: string; + useYarn?: boolean; + }): Promise; + /** + * Install an extension from VS Code marketplace into the test instance + * @param id id of the extension to install + */ + installFromMarketplace(id: string): Promise; + /** + * Download the matching chromedriver for a given VS Code version + * @param vscodeVersion selected versio nof VSCode, default latest + */ + downloadChromeDriver(vscodeVersion?: string): Promise; + /** + * Performs all necessary setup: getting VSCode + ChromeDriver + * and packaging/installing extension into the test instance + * + * @param options Additional options for setting up the tests + */ + setupRequirements(options?: SetupOptions): Promise; + /** + * Performs requirements setup and runs extension tests + * + * @param testFilesPattern glob pattern for test files to run + * @param vscodeVersion version of VSCode to test against, defaults to latest + * @param setupOptions Additional options for setting up the tests + * @param runOptions Additional options for running the tests + * + * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise + */ + setupAndRunTests(testFilesPattern: string, vscodeVersion?: string, setupOptions?: Omit, runOptions?: Omit): Promise; + /** + * Runs the selected test files in VS Code using mocha and webdriver + * @param testFilesPattern glob pattern for selected test files + * @param runOptions Additional options for running the tests + * + * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise + */ + runTests(testFilesPattern: string, runOptions?: RunOptions): Promise; +} +``` \ No newline at end of file diff --git a/wiki/TextEditor.md b/wiki/TextEditor.md new file mode 100644 index 000000000..6cd9a43d0 --- /dev/null +++ b/wiki/TextEditor.md @@ -0,0 +1,98 @@ +![editor](https://user-images.githubusercontent.com/4181232/56643754-81b9ee00-667a-11e9-9c7a-de39f342d676.png) + +#### Lookup +```typescript +import { TextEditor, EditorView } from 'vscode-extension-tester'; +... +// to look up current editor +const editor = new TextEditor(); +// to look up an open editor by name +const editor1 = await new EditorView().openEditor('package.json'); +``` + +#### Text Retrieval +Note: Most text retrieval and editing actions will make use of the clipboard. +```typescript +// get all text +const text = await editor.getText(); +// get text at a given line number +const line = await editor.getTextAtLine(1); +// get number of lines in the document +const numberOfLines = await editor.getNumberOfLines(); +``` + +#### Editing Text +```typescript +// replace all text with a string +await editor.setText('my fabulous text'); +// replace text at a given line number +await editor.setTextAtLine(1, 'my fabulous line'); +// type text at the current coordinates +await editor.typeText('I have the best text'); +// type text starting at given coordinates (line, column) +await editor.typeTextAt(1, 3, ' absolutely'); +// format the whole document with built-in tools +await editor.formatDocument(); +// get the current cursor coordinates as number array [x,y] +const coords = await editor.getCoordinates(); +``` + +#### Save Changes +```typescript +// find if the editor has changes +const hasChanges = await editor.isDirty(); +// save the document +await editor.save(); +// save as, this only opens the save prompt +const prompt = await editor.saveAs(); +``` + +#### Get Document File Path +```typescript +const path = await editor.getFilePath(); +``` + +#### Content Assist +```typescript +// open content assist at current position +const contentAssist = await editor.toggleContentAssist(true); +// close content assist +await editor.toggleContentAssist(false); +``` + +#### Search for Text +```typescript +// get line number that contains some text +const lineNum = await editor.getLineOfText('some text'); +// find and select text +await editor.selectText('some text'); +// get selected text as string +const text = await editor.getSelectedText(); +// get selection block as a page object +const selection = await editor.getSelection(); +// open the Find (search) widget +const find = await editor.openFindWidget(); +``` + +#### Breakpoints +```typescript +// toggle breakpoint on a line with given number +await editor.toggleBreakpoint(1); +``` + +#### CodeLenses +```typescript +// get a code lens by (partial) text +const lens = await editor.getCodeLens('my code lens text'); +// get code lens by index (zero-based from the top of the editor) +const firstLens = await editor.getCodeLens(0); +// get all code lenses +const lenses = await editor.getCodeLenses(); + +// now you can trigger the lens actions by clicking +await lens.click(); + +// or just get the text +const text = await lens.getText(); +const tooltip = await lens.getTooltip(); +``` \ No newline at end of file diff --git a/wiki/TitleBar.md b/wiki/TitleBar.md new file mode 100644 index 000000000..15890e349 --- /dev/null +++ b/wiki/TitleBar.md @@ -0,0 +1,30 @@ +![titleBar](https://user-images.githubusercontent.com/4181232/56653724-986a4000-668e-11e9-9d5c-3d1998585f35.png) + +Page object for the title bar. Works only if the selected title bar type is 'custom'. Native title bar is not supported. + +#### Lookup +```typescript +import { TitleBar } from 'vscode-extension-tester'; +... +const titleBar = new TitleBar(); +``` + +#### Item Retrieval +```typescript +// find if an item with title exists +const exists = await titleBar.hasItem('File'); +// get a handle for an item +const item = await titleBar.getItem('File'); +// get all displayed items +const items = await titleBar.getItems(); +``` + +#### Get Displayed Title +```typescript +const title = await titleBar.getTitle(); +``` + +#### Get Window Controls Handle +```typescript +const controls = titleBar.getWindowControls(); +``` \ No newline at end of file diff --git a/wiki/TitleBarItem.md b/wiki/TitleBarItem.md new file mode 100644 index 000000000..301a4e49f --- /dev/null +++ b/wiki/TitleBarItem.md @@ -0,0 +1,16 @@ +![titleBarItem](https://user-images.githubusercontent.com/4181232/56654111-918ffd00-668f-11e9-82a0-0e1cc2db2ad7.png) + +#### Lookup +```typescript +import { TitleBar } from 'vscode-extension-tester'; + +// get an item from the title bar +const item = await new TitleBar().getItem('File'); +``` + +#### Select the Item +```typescript +const contextMenu = item.select(); +``` + +The rest of the functionality is exactly the same as other menu items, like [[ContextMenuItem]]. \ No newline at end of file diff --git a/wiki/ViewContent.md b/wiki/ViewContent.md new file mode 100644 index 000000000..c2e70b410 --- /dev/null +++ b/wiki/ViewContent.md @@ -0,0 +1,22 @@ +![contentPart](https://user-images.githubusercontent.com/4181232/56655995-9c995c00-6694-11e9-963b-e7dd159c26d7.png) + +#### Lookup +```typescript +import { SideBarView } from 'vscode-extension-tester'; +... +const contentPart = new SideBarView().getContent(); +``` + +#### Get Sections +```typescript +// get a section by title, case insensitive +const section = await contentPart.getSection('Open Editors'); +// get all sections +const sections = await contentPart.getSections(); +``` + +#### Progress Bar +```typescript +// look if there is an active progress bar +const hasProgress = await contentPart.hasProgress(); +``` \ No newline at end of file diff --git a/wiki/ViewContol.md b/wiki/ViewContol.md new file mode 100644 index 000000000..49702c913 --- /dev/null +++ b/wiki/ViewContol.md @@ -0,0 +1,34 @@ +![viewControl](https://user-images.githubusercontent.com/4181232/56588505-c47cb700-65e3-11e9-8636-8c35c1c1e648.png) + +#### Look up the ViewControl by title +Import and find the control through activity bar +```typescript +import { ActivityBar, ViewControl } from 'vscode-extension-tester'; +... +// get view control for Explorer +const control: ViewControl = new ActivityBar().getViewControl('Explorer'); +``` + +#### Open view +Open the associated view if not already open and get a handler for it +```typescript +const view = await control.openView(); +``` + +#### Close view +Close the associated view if open +```typescript +await control.closeView(); +``` + +#### Get title +Get the control's/view's title +```typescript +const title = control.getTitle(); +``` + +#### Open context menu +Left click on the control to open the context menu +```typescript +const menu = await control.openContextMenu(); +``` \ No newline at end of file diff --git a/wiki/ViewItem.md b/wiki/ViewItem.md new file mode 100644 index 000000000..89a7acfa0 --- /dev/null +++ b/wiki/ViewItem.md @@ -0,0 +1,26 @@ +![item](https://user-images.githubusercontent.com/4181232/56657225-c7d17a80-6697-11e9-8690-5055d6737a7a.png) + +#### Lookup +The best way to get an item reference is to use the ```findItem``` method from ```ViewSection```. +```typescript +const viewSection = ...; +const item = await viewSection.findItem('package.json'); +``` + +#### Actions +```typescript +// get item's label +const label = item.getLabel() +// find if the item can be expanded +const isExpandable = await item.isExpandable(); +// try to expand the item and find if it has children +const isParent = await item.hasChildren(); +// find if item is expanded +const isExpanded = await item.isExpanded(); +// collapse the item if expanded +await item.collapse(); +// select the item and get its children if it ends up expanded, otherwise get an empty array +const children = await item.select(); +// get the tooltip if present +const tooltip = await item.getTooltip(); +``` \ No newline at end of file diff --git a/wiki/ViewSection.md b/wiki/ViewSection.md new file mode 100644 index 000000000..1d4c26691 --- /dev/null +++ b/wiki/ViewSection.md @@ -0,0 +1,48 @@ +![section](https://user-images.githubusercontent.com/4181232/56656305-76c08700-6695-11e9-8630-e878ff478201.png) + +This is an abstract class for side bar view sections. Most behaviour is defined here, but for specifics, check out the specific subtypes. + +#### Lookup +Get a section handle from an open side bar. +```typescript +import { SideBarView } from 'vscode-extension-tester'; +... +const section = await new SideBarView().getContent().getSection('workspace'); +``` + +#### Section Manipulation +```typescript +// get the section title +const title = section.getTitle(); +// collapse section if possible +await section.collapse(); +// expand if possible +await section.expand(); +// find if section is expanded +const expanded = await section.isExpanded(); +``` + +#### Action Buttons +Section header may also contain some action buttons. +```typescript +// get an action button by label +const action = await section.getAction('New File'); +// get all action buttons for the section +const actions = await section.getActions(); +// click an action button +await action.click(); +``` + +#### (Tree) Items Manipulation +```typescript +// get all visible items, note that currently not shown on screen will not be retrieved +const visibleItems = await section.getVisibleItems(); +// find an item with a given label, involves scrolling to items currently not showing +const item = await section.findItem('package.json'); +// recursively navigate to an item and click it + // if the item has children (./src/webdriver/components folder) + const children = await section.openItem('src', 'webdriver', 'components'); + // if the item is a leaf + await section.openItem('src', 'webdriver', 'components', 'AbstractElement.ts'); + +``` \ No newline at end of file diff --git a/wiki/ViewTitlePart.md b/wiki/ViewTitlePart.md new file mode 100644 index 000000000..e83a58237 --- /dev/null +++ b/wiki/ViewTitlePart.md @@ -0,0 +1,24 @@ +![titlePart](https://user-images.githubusercontent.com/4181232/56655603-935bbf80-6693-11e9-98f3-0e20a3256047.png) + +#### Lookup +```typescript +import { SideBarView } from 'vscode-extension-tester'; +... +const titlePart = new SideBarView().getTitlePart(); +``` + +#### Get Title +```typescript +const title = await titlePart.getTitle(); +``` + +#### ActionButtons +Some views have action buttons in their title part. +```typescript +// get action button by title +const button = await titlePart.getActionButton('Clear'); +// get all action buttons +const buttons = await titlePart.getActionButtons(); +// click a button +await button.click(); +``` diff --git a/wiki/WebView.md b/wiki/WebView.md new file mode 100644 index 000000000..20cfb558e --- /dev/null +++ b/wiki/WebView.md @@ -0,0 +1,37 @@ +![](https://raw.githubusercontent.com/microsoft/vscode-extension-samples/master/webview-sample/demo.gif) + +#### Lookup +```typescript +import { EditorView, WebView } from 'vscode-extension-tester'; +... +// using EditorView +const webview = new EditorView().openEditor('webview-title'); + +// using the constructor assuming the editor is opened +const webview1 = new WebView(); +``` + +#### Switching Context +In order to access the elements inside the web view frame, it is necessary to switch webdriver context into the frame. Analogically, to stop working with the web view, switching back is necessary. +```typescript +// to switch inside the web view frame +await webview.switchToFrame(); + +// to switch back to the default window +await webview.switchBack(); +``` + +#### Searching for Elements Inside a Web View +Make sure when searching for and manipulating with elements inside (or outside) the web view that you have switched webdriver to the appropriate context. Also, be aware that referencing an element from the default window while switched to the web view (and vice versa) will throw a `StaleElementReference` error. +```typescript +// first, switch inside the web view +await webview.switchToFrame(); + +// look for desired elements +const element = await webview.findWebElement(); +const elements = await webview.findWebElements(); + +... +// after all web view manipulation is done, switch back to the default window +await webview.switchBack(); +``` \ No newline at end of file diff --git a/wiki/WindowControls.md b/wiki/WindowControls.md new file mode 100644 index 000000000..93aa6eaff --- /dev/null +++ b/wiki/WindowControls.md @@ -0,0 +1,22 @@ +![windowcontrols](https://user-images.githubusercontent.com/4181232/56654449-622dc000-6690-11e9-9222-f8dc0dbd59dc.png) + +Controls to the whole window. Use at your own risk. + +#### Lookup +```typescript +import { TitleBar } from 'vscode-extension-tester'; +... +const controls = new TitleBar().getWindowControls(); +``` + +#### Manipulate Window +```typescript +// minimize +await controls.minimize(); +// maximize +await controls.maximize(); +// restore +await controls.restore(); +// close... if you dare +await controls.close(); +``` \ No newline at end of file diff --git a/wiki/Workbench.md b/wiki/Workbench.md new file mode 100644 index 000000000..b8d185c55 --- /dev/null +++ b/wiki/Workbench.md @@ -0,0 +1,49 @@ +Workbench is the container for all the other elements. As such it mainly offers convenience methods to get handles for its subparts. It also retrieves handles for elements that are not accessible from a particular subpart. + +#### Lookup +```typescript +import { Workbench } from 'vscode-extension-tester'; +... +const workbench = new Workbench(); +``` + +#### Subparts Handles +```typescript +// get title bar handle +const titleBar = workbench.getTitleBar(); + +// get side bar handle +const sideBar = workbench.getSideBar(); + +// get activity bar handle +const activityBar = workbench.getActivityBar(); + +// get bottom bar handle +const bottomBar = workbench.getBottomBar(); + +// get editor view handle +const editorView = workbench.getEditorView(); + +// get notifications (outside notifications center) +const notifications = workbench.getNotifications(); + +// open notifications center +const center = workbench.openNotificationsCenter(); +``` + +#### Command Prompt +You can also use ```Workbench``` to open the command prompt and execute commands. +```typescript +// open command prompt, can then be handled as a QuickOpenBox +const commandInput = await workbench.openCommandPrompt(); + +/* open command prompt and execute a command in it, the text does not need to be a perfect match + uses VS Code's fuzzy search to find the best match */ +await workbench.executeCommand('close workspace'); +``` + +#### Settings +Opening the VSCode Settings editor is also available. +```typescript +const settingsEditor = await workbench.openSettings(); +``` diff --git a/wiki/Writing-Simple-Tests.md b/wiki/Writing-Simple-Tests.md new file mode 100644 index 000000000..2807c2f08 --- /dev/null +++ b/wiki/Writing-Simple-Tests.md @@ -0,0 +1,26 @@ +VSCode Extension Tester is integrated with Mocha framework (as such requires Mocha 5.2+ to be present in your extension). To write a simple tests, one would write it just like a standard BDD Mocha test. + +This is what a really simple test case might look like. Note that here we are only using pure webdriver. To use the provided page objects, see the [[Page-Object-APIs]]. +```typescript +import { assert } from 'chai'; +// import the webdriver and the high level browser wrapper +import { VSBrowser, WebDriver } from 'vscode-extension-tester'; + +// Create a Mocha suite +describe('My Test Suite', () => { + let browser: VSBrowser; + let driver: WebDriver + + // initialize the browser and webdriver + before(async () => { + browser = VSBrowser.instance; + driver = browser.driver; + }); + + // test whatever we want using webdriver, here we are just checking the page title + it('My Test Case', async () => { + const title = await driver.getTitle(); + assert.equal(title, 'whatever'); + }); +}); +```