From af602bfb0fddbd60908761062cf79824b7a097ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Posp=C3=AD=C5=A1il?= <55882695+pospisilf@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:11:59 +0200 Subject: [PATCH] feat: Add support for Extension Editor View (#1527) Co-authored-by: Dominik Jelinek --- docs/ExtensionEditorDetailsSections.md | 23 +++ docs/ExtensionEditorView.md | 37 +++++ packages/locators/lib/1.37.0.ts | 21 +++ .../editor/ExtensionEditorDetailsSection.ts | 92 ++++++++++++ .../components/editor/ExtensionEditorView.ts | 105 +++++++++++++ packages/page-objects/src/index.ts | 2 + .../page-objects/src/locators/locators.ts | 21 +++ .../src/test/editor/extensionEditor.test.ts | 140 ++++++++++++++++++ 8 files changed, 441 insertions(+) create mode 100644 docs/ExtensionEditorDetailsSections.md create mode 100644 docs/ExtensionEditorView.md create mode 100644 packages/page-objects/src/components/editor/ExtensionEditorDetailsSection.ts create mode 100644 packages/page-objects/src/components/editor/ExtensionEditorView.ts create mode 100644 tests/test-project/src/test/editor/extensionEditor.test.ts diff --git a/docs/ExtensionEditorDetailsSections.md b/docs/ExtensionEditorDetailsSections.md new file mode 100644 index 000000000..82ac926d5 --- /dev/null +++ b/docs/ExtensionEditorDetailsSections.md @@ -0,0 +1,23 @@ +ExtensionEditorDetailsSections + +#### Lookup + +```typescript +import { ExtensionEditorDetailsSection } from 'vscode-extension-tester'; +... +const extensionEditorDetails = new ExtensionEditorDetailsSection(); +``` + +You can get values using following functions: + +```typescript +await extensionEditorDetails.getCategories(); + +await extensionEditorDetails.getResources(); + +await extensionEditorDetails.getMoreInfo(); + +await extensionEditorDetails.getMoreInfoItem("Identifier"); + +await extensionEditorDetails.getReadme(); // currently not supported (Blocked by https://github.com/redhat-developer/vscode-extension-tester/issues/1492) +``` diff --git a/docs/ExtensionEditorView.md b/docs/ExtensionEditorView.md new file mode 100644 index 000000000..d6564abd2 --- /dev/null +++ b/docs/ExtensionEditorView.md @@ -0,0 +1,37 @@ +ExtensionEditorView + +#### Lookup + +```typescript +import { ExtensionEditorView } from 'vscode-extension-tester'; +... +const extensionEditor = new ExtensionEditorView(); +``` + +#### Get values from opened extension + +You can get individual values using following functions: + +```typescript +await extensionEditor.getName(); + +await extensionEditor.getVersion(); + +await extensionEditor.getPublisher(); + +await extensionEditor.getDescription(); + +await extensionEditor.getCount(); +``` + +#### Manage tabs + +Tabs section can be managed by this editor as well. For work with 'Details' you can use ExtensionEditorDetailsSection. + +```typescript +await extensionEditor.getTabs(); + +await extensionEditor.switchToTab("tabname"); + +await extensionEditor.getActiveTab(); +``` diff --git a/packages/locators/lib/1.37.0.ts b/packages/locators/lib/1.37.0.ts index 62916eb00..2d2556a48 100644 --- a/packages/locators/lib/1.37.0.ts +++ b/packages/locators/lib/1.37.0.ts @@ -211,6 +211,27 @@ const editor = { container: (id: string) => By.id(id), attribute: 'aria-flowto', }, + ExtensionEditorView: { + constructor: By.className('extension-editor'), + name: By.className('name'), + version: By.className('version'), + publisher: By.className('publisher-name'), + description: By.className('description'), + count: By.className('count'), + navbar: By.className('navbar'), + tab: By.className('action-label'), + activeTab: By.xpath(`.//a[starts-with(@class, 'action-label checked')]`), + specificTab: (tabname: string) => By.xpath(`.//a[starts-with(@class, 'action-label') and text()='${tabname}']`), + }, + ExtensionEditorDetailsSection: { + categoriesContainer: By.className('categories'), + category: By.className('category'), + resourcesContainer: By.className('resources'), + resource: By.className('resource'), + moreInfoContainer: By.className('more-info'), + moreInfo: By.className('more-info-entry'), + moreInfoElements: By.xpath('./*'), + }, }; const menu = { diff --git a/packages/page-objects/src/components/editor/ExtensionEditorDetailsSection.ts b/packages/page-objects/src/components/editor/ExtensionEditorDetailsSection.ts new file mode 100644 index 000000000..3159d5e59 --- /dev/null +++ b/packages/page-objects/src/components/editor/ExtensionEditorDetailsSection.ts @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License", destination); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://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 { ExtensionEditorView, WebView } from '../..'; + +export class ExtensionEditorDetailsSection extends ExtensionEditorView { + /** + * Get all categories of extension. + * @returns Promise resolving categories of extension. + */ + async getCategories(): Promise { + const categories: string[] = []; + + const container = await this.findElement(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.categoriesContainer); + const categoriesInContainer = await container.findElements(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.category); + + for (const cat of categoriesInContainer) { + categories.push(await cat.getText()); + } + return categories; + } + + /** + * Get all resources of extension. + * @returns Promise resolving resources of extension. + */ + async getResources(): Promise { + const resources: string[] = []; + + const container = await this.findElement(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.resourcesContainer); + const resourcesInContainer = await container.findElements(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.resource); + + for (const res of resourcesInContainer) { + resources.push(await res.getText()); + } + + return resources; + } + + /** + * Get content of More Info section. + * @returns Promise resolving content of 'More Info' section. + */ + async getMoreInfo(): Promise<{ [key: string]: string }> { + const moreInfo: { [key: string]: string } = {}; + + const container = await this.findElement(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.moreInfoContainer); + const moreInfoInContainer = await container.findElements(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.moreInfo); + + for (const entry of moreInfoInContainer) { + const elmnts = await entry.findElements(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.moreInfoElements); + const name = await (await elmnts.at(0))?.getText(); + const value = await (await elmnts.at(1))?.getText(); + if (name !== undefined && value !== undefined) { + moreInfo[name] = value; + } + } + + return moreInfo; + } + + /** + * Get value of specific attribute from 'More Info' section. + * @param key Name of attribute. + * @returns Promise resolving value for specified attribute. + */ + async getMoreInfoItem(key: string): Promise { + const moreInfo = await this.getMoreInfo(); + return moreInfo[key]; + } + + /** + * Blocked by https://github.com/redhat-developer/vscode-extension-tester/issues/1492 + */ + async getReadme(): Promise { + throw Error('Not implemented yet.'); + } +} diff --git a/packages/page-objects/src/components/editor/ExtensionEditorView.ts b/packages/page-objects/src/components/editor/ExtensionEditorView.ts new file mode 100644 index 000000000..b0982b595 --- /dev/null +++ b/packages/page-objects/src/components/editor/ExtensionEditorView.ts @@ -0,0 +1,105 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License", destination); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://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 { Editor, EditorGroup, EditorView } from '../..'; + +export class ExtensionEditorView extends Editor { + constructor(view: EditorView | EditorGroup = new EditorView()) { + super(view, ExtensionEditorView.locators.ExtensionEditorView.constructor); + } + + /** + * Get name of extension. + * @returns Promise resolving name of extension. + */ + async getName(): Promise { + const name = await this.findElement(ExtensionEditorView.locators.ExtensionEditorView.name); + return await name?.getText(); + } + + /** + * Get version of extension. + * @returns Promise resolving version of extension. + */ + async getVersion(): Promise { + const name = await this.findElement(ExtensionEditorView.locators.ExtensionEditorView.version); + return await name?.getText(); + } + + /** + * Get publisher of extension. + * @returns Promise resolving publisher of extension. + */ + async getPublisher(): Promise { + const name = await this.findElement(ExtensionEditorView.locators.ExtensionEditorView.publisher); + return await name?.getText(); + } + + /** + * Get description of extension. + * @returns Promise description name of opened extension. + */ + async getDescription(): Promise { + const name = await this.findElement(ExtensionEditorView.locators.ExtensionEditorView.description); + return await name?.getText(); + } + + /** + * Get count of extension. + * @returns Promise resolving count of opened extension. + */ + async getCount(): Promise { + const count = await this.findElement(ExtensionEditorView.locators.ExtensionEditorView.count); + return await count?.getText(); + } + + /** + * Get available tabs. + * @returns Promise resolving tabs of opened extension. + */ + async getTabs(): Promise { + const tabs: string[] = []; + const navbar = await this.findElement(ExtensionEditorView.locators.ExtensionEditorView.navbar); + const els = await navbar.findElements(ExtensionEditorView.locators.ExtensionEditorView.tab); + for (const element of els) { + tabs.push(await element.getText()); + } + return tabs; + } + + /** + * Switch to different tab. + * @param tabName Name of required tab to be switched on. + * @returns Promise resolving to true if tabs were switched successfully, false otherwise. + */ + async switchToTab(tabName: string): Promise { + const navbar = await this.findElement(ExtensionEditorView.locators.ExtensionEditorView.navbar); + const requiredTab = await navbar.findElement(ExtensionEditorView.locators.ExtensionEditorView.specificTab(tabName)); + await requiredTab.click(); + return (await this.getActiveTab()).toLowerCase() === tabName.toLowerCase(); + } + + /** + * Get name of opened tab. + * @returns Promise resolving name of opened tab. + */ + async getActiveTab(): Promise { + const navbar = await this.findElement(ExtensionEditorView.locators.ExtensionEditorView.navbar); + const features = await navbar.findElement(ExtensionEditorView.locators.ExtensionEditorView.activeTab); + return await features.getText(); + } +} diff --git a/packages/page-objects/src/index.ts b/packages/page-objects/src/index.ts index a5db8065e..bc78af405 100644 --- a/packages/page-objects/src/index.ts +++ b/packages/page-objects/src/index.ts @@ -74,6 +74,8 @@ export * from './components/editor/DiffEditor'; export * from './components/editor/WebView'; export * from './components/editor/ContentAssist'; export * from './components/editor/CustomEditor'; +export * from './components/editor/ExtensionEditorView'; +export * from './components/editor/ExtensionEditorDetailsSection'; export { Notification, NotificationType } from './components/workbench/Notification'; export * from './components/workbench/NotificationsCenter'; diff --git a/packages/page-objects/src/locators/locators.ts b/packages/page-objects/src/locators/locators.ts index 824526822..d6e4c5576 100644 --- a/packages/page-objects/src/locators/locators.ts +++ b/packages/page-objects/src/locators/locators.ts @@ -215,6 +215,27 @@ export interface Locators { container: (id: string) => By; attribute: string; }; + ExtensionEditorView: { + constructor: By; + name: By; + version: By; + publisher: By; + description: By; + count: By; + navbar: By; + tab: By; + activeTab: By; + specificTab: (tabname: string) => By; + }; + ExtensionEditorDetailsSection: { + categoriesContainer: By; + category: By; + resourcesContainer: By; + resource: By; + moreInfoContainer: By; + moreInfo: By; + moreInfoElements: By; + }; // Menus ContextMenu: { diff --git a/tests/test-project/src/test/editor/extensionEditor.test.ts b/tests/test-project/src/test/editor/extensionEditor.test.ts new file mode 100644 index 000000000..11dfc69e5 --- /dev/null +++ b/tests/test-project/src/test/editor/extensionEditor.test.ts @@ -0,0 +1,140 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License", destination); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://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 } from 'chai'; +import { + ActivityBar, + EditorView, + ExtensionEditorView, + ExtensionEditorDetailsSection, + ExtensionsViewItem, + ExtensionsViewSection, + SideBarView, + ViewControl, + VSBrowser, + WebDriver, +} from 'vscode-extension-tester'; +import * as pjson from '../../../package.json'; + +describe('Extension Editor', function () { + let driver: WebDriver; + let viewControl: ViewControl; + let extensionsView: SideBarView; + let item: ExtensionsViewItem; + + let section: ExtensionsViewSection; + + let extensionEditor: ExtensionEditorView; + let extensionEditorDetails: ExtensionEditorDetailsSection; + + before(async function () { + driver = VSBrowser.instance.driver; + viewControl = (await new ActivityBar().getViewControl('Extensions')) as ViewControl; + extensionsView = await viewControl.openView(); + await driver.wait(async function () { + return (await extensionsView.getContent().getSections()).length > 0; + }); + + const view = await viewControl.openView(); + + await driver.wait(async function () { + return (await view.getContent().getSections()).length > 0; + }); + section = (await view.getContent().getSection('Installed')) as ExtensionsViewSection; + + await driver.wait(async function () { + item = (await section.findItem(`@installed ${pjson.displayName}`)) as ExtensionsViewItem; + return item !== undefined; + }); + + await item.click(); + }); + + after(async function () { + await viewControl.closeView(); + await new EditorView().closeAllEditors(); + }); + + describe('ExtensionEditorView', function () { + before(async function () { + extensionEditor = new ExtensionEditorView(); + }); + + it('getName', async function () { + expect(await extensionEditor.getName()).equal('Test Project'); + }); + + it('getVersion', async function () { + expect(await extensionEditor.getVersion()).equal('v0.1.0'); + }); + + it('getPublisher', async function () { + expect(await extensionEditor.getPublisher()).equal('ExTester'); + }); + + it('getDescription', async function () { + expect(await extensionEditor.getDescription()).equal('Extension dedicated to self-testing the ExTester package'); + }); + + // Not working on test project. + it.skip('getCount', async function () {}); + + it('getTabs', async function () { + const tabs = await extensionEditor.getTabs(); + expect(tabs).to.have.lengthOf(2); + expect(tabs).to.contain('FEATURES'); + }); + + it('switchToTab', async function () { + expect(await extensionEditor.switchToTab('Features')).to.be.true; + }); + + it('getActiveTab', async function () { + expect(await extensionEditor.getActiveTab()).equal('FEATURES'); + }); + }); + + describe('ExtensionEditorDetailsSections', function () { + before(async function () { + await extensionEditor.switchToTab('Details'); + extensionEditorDetails = new ExtensionEditorDetailsSection(); + }); + + it('getCategories', async function () { + const categories = await extensionEditorDetails.getCategories(); + expect(categories).to.have.lengthOf(2); + expect(categories).to.contain('Testing'); + }); + + // Not working on test project. + it.skip('getResources', async function () {}); + + it('getMoreInfo', async function () { + const moreInfo = await extensionEditorDetails.getMoreInfo(); + expect(moreInfo).not.to.be.undefined; + expect(Object.keys(moreInfo)[0]).equal('Last updated'); + expect(Object.values(moreInfo)[1]).equal('extester.extester-test'); + }); + + it('getMoreInfoItem', async function () { + expect(await extensionEditorDetails.getMoreInfoItem('Identifier')).equal('extester.extester-test'); + }); + + // Blocked by https://github.com/redhat-developer/vscode-extension-tester/issues/1492 + it.skip('getReadme', async function () {}); + }); +});