Add new test for checking git plus ssh workflow (#15561)

Add new test for checking Git plus SSH workflaw
7.20.x
Maxim Musienko 2020-02-25 19:16:53 +02:00 committed by GitHub
parent 68ac4db40f
commit 3af3742d6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 509 additions and 238 deletions

View File

@ -49,7 +49,10 @@ pipeline {
value: ''),
booleanParam(name: 'createTestWorkspace',
value: true)
value: true),
string(name: 'e2eTestParameters',
value: '')
]
}
}
@ -83,7 +86,10 @@ pipeline {
value: ''),
booleanParam(name: 'createTestWorkspace',
value: false)
value: false),
string(name: 'e2eTestParameters',
value: '')
]
}
}

View File

@ -19,7 +19,7 @@ pipeline {
stages {
stage("Run E2E tests") {
parallel {
stage('Run Happy path tests against nightly build') {
stage('Run Happy path tests') {
steps {
build job: 'basic-MultiUser-Che-check-e2e-tests-against-k8s',
parameters: [
@ -48,12 +48,15 @@ pipeline {
value: ''),
booleanParam(name: 'createTestWorkspace',
value: true)
value: true),
string(name: 'e2eTestParameters',
value: '')
]
}
}
stage('Run devfile tests against nightly build') {
stage('Run devfile tests') {
steps {
build job: 'basic-MultiUser-Che-check-e2e-tests-against-k8s',
parameters: [
@ -82,10 +85,52 @@ pipeline {
value: ''),
booleanParam(name: 'createTestWorkspace',
value: false)
value: false),
string(name: 'e2eTestParameters',
value: '')
]
}
}
stage('Run Git SSH flow tests') {
steps {
withCredentials([string(credentialsId: 'e45af3e6-8061-4d02-b187-b1c3bb133d3a', variable: 'github_oauth_token')]) {
build job: 'basic-MultiUser-Che-check-e2e-tests-against-k8s',
parameters: [
string(name: 'cheImageRepo',
value: 'quay.io/eclipse/che-server'),
string(name: 'cheImageTag',
value: 'nightly'),
booleanParam(name: 'buildChe',
value: false),
string(name: 'ghprbSourceBranch',
value: 'CRW-391'),
string(name: 'ghprbPullId',
value: ''),
string(name: 'e2eTestToRun',
value: 'test-git-ssh'),
string(name: 'testWorkspaceDevfileUrl',
value: ''),
text(name: 'customResourceFileContent',
value: ''),
booleanParam(name: 'createTestWorkspace',
value: false),
string(name: 'e2eTestParameters',
value: "-e TS_GITHUB_TEST_REPO_ACCESS_TOKEN=$github_oauth_token -e TS_GITHUB_TEST_REPO=chepullreq4/Spoon-Knife -e NODE_TLS_REJECT_UNAUTHORIZED=0")
]
}
}
}
}
}
}

View File

@ -55,6 +55,9 @@ pipeline {
string(name: 'ghprbSourceBranch',
defaultValue: "master")
string(name: 'e2eTestParameters',
defaultValue: "")
}
stages {
@ -311,7 +314,7 @@ pipeline {
-e TS_SELENIUM_MULTIUSER="true" \\
-e TS_SELENIUM_USERNAME="admin" \\
-e TS_SELENIUM_PASSWORD="admin" \\
-e TEST_SUITE="${e2eTestToRun}" \\
-e TEST_SUITE="${e2eTestToRun}" ${e2eTestParameters} \\
-v ${WORKSPACE}/tests/e2e:/tmp/e2e:Z \\
quay.io/eclipse/che-e2e:nightly
"""

View File

@ -116,7 +116,10 @@ pipeline {
value: "$customResourceFileContent"),
booleanParam(name: 'createTestWorkspace',
value: true)
value: true),
string(name: 'e2eTestParameters',
value: '')
]
}
}
@ -152,11 +155,53 @@ pipeline {
value: "$customResourceFileContent"),
booleanParam(name: 'createTestWorkspace',
value: false)
value: false),
string(name: 'e2eTestParameters',
value: '')
]
}
}
}
stage('Run Git SSH flow tests') {
steps {
withCredentials([string(credentialsId: 'e45af3e6-8061-4d02-b187-b1c3bb133d3a', variable: 'github_oauth_token')]) {
build job: 'basic-MultiUser-Che-check-e2e-tests-against-k8s',
parameters: [
string(name: 'cheImageRepo',
value: 'quay.io/eclipse/che-server'),
string(name: 'cheImageTag',
value: 'nightly'),
booleanParam(name: 'buildChe',
value: false),
string(name: 'ghprbSourceBranch',
value: 'CRW-391'),
string(name: 'ghprbPullId',
value: ''),
string(name: 'e2eTestToRun',
value: 'test-git-ssh'),
string(name: 'testWorkspaceDevfileUrl',
value: ''),
text(name: 'customResourceFileContent',
value: ''),
booleanParam(name: 'createTestWorkspace',
value: false),
string(name: 'e2eTestParameters',
value: "-e TS_GITHUB_TEST_REPO_ACCESS_TOKEN=$github_oauth_token -e TS_GITHUB_TEST_REPO=chepullreq4/Spoon-Knife -e NODE_TLS_REJECT_UNAUTHORIZED=0")
]
}
}
}
}
}
}

View File

@ -89,11 +89,6 @@ export const TestConstants = {
*/
TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS: Number(process.env.TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS) || 20,
/**
* Delay in milliseconds between checking plugin precence.
*/
TS_SELENIUM_PLUGIN_PRECENCE_POLLING: Number(process.env.TS_SELENIUM_PLUGIN_PRECENCE_POLLING) || 2000,
/**
* Name of workspace created for 'Happy Path' scenario validation.
*/
@ -228,5 +223,16 @@ export const TestConstants = {
/**
* Running test suite - possible variants can be found in package.json scripts part.
*/
TEST_SUITE: process.env.TEST_SUITE || 'test-happy-path'
TEST_SUITE: process.env.TEST_SUITE || 'test-happy-path',
/**
* The repo (with README.md in root) and access token are needed for to run test-git-ssh
*/
TS_GITHUB_TEST_REPO: process.env.TS_GITHUB_TEST_REPO || '',
/**
* Token for a github repository with permissions which allow add the ssh keys
*/
TS_GITHUB_TEST_REPO_ACCESS_TOKEN: process.env.TS_GITHUB_TEST_REPO_ACCESS_TOKEN || ''
};

View File

@ -48,7 +48,7 @@ class CheReporter extends mocha.reporters.Spec {
TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS: ${TestConstants.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS}
TS_SELENIUM_WORKSPACE_STATUS_POLLING: ${TestConstants.TS_SELENIUM_WORKSPACE_STATUS_POLLING}
TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS: ${TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS}
TS_SELENIUM_PLUGIN_PRECENCE_POLLING: ${TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_POLLING}
TS_SELENIUM_PLUGIN_PRECENCE_POLLING: ${TestConstants.TS_SELENIUM_DEFAULT_POLLING}
TS_SELENIUM_HAPPY_PATH_WORKSPACE_NAME: ${TestConstants.TS_SELENIUM_HAPPY_PATH_WORKSPACE_NAME}
TS_SELENIUM_USERNAME: ${TestConstants.TS_SELENIUM_USERNAME}
TS_SELENIUM_PASSWORD: ${TestConstants.TS_SELENIUM_PASSWORD}

View File

@ -16,6 +16,8 @@ export * from './utils/requestHandlers/headers/IAuthorizationHeaderHandler';
export * from './utils/requestHandlers/tokens/CheMultiuserTokenHandler';
export * from './utils/requestHandlers/tokens/ITokenHandler';
export * from './utils/ScreenCatcher';
export * from './utils/VCS/CheGitApi';
export * from './utils/VCS/github/GitHubUtil';
export * from './utils/workspace/ITestWorkspaceUtil';
export * from './utils/workspace/TestWorkspaceUtil';
export * from './utils/workspace/WorkspaceStatus';
@ -28,7 +30,7 @@ export * from './pageobjects/ide/ContextMenu';
export * from './pageobjects/ide/DebugView';
export * from './pageobjects/ide/DialogWindow';
export * from './pageobjects/ide/Editor';
export * from './pageobjects/ide/GitHubPlugin';
export * from './pageobjects/ide/GitPlugin';
export * from './pageobjects/ide/Ide';
export * from './pageobjects/ide/NotificationCenter';
export * from './pageobjects/ide/OpenWorkspaceWidget';

View File

@ -34,7 +34,7 @@ import { Editor } from './pageobjects/ide/Editor';
import { TopMenu } from './pageobjects/ide/TopMenu';
import { QuickOpenContainer } from './pageobjects/ide/QuickOpenContainer';
import { PreviewWidget } from './pageobjects/ide/PreviewWidget';
import { GitHubPlugin } from './pageobjects/ide/GitHubPlugin';
import { GitPlugin } from './pageobjects/ide/GitPlugin';
import { RightToolbar } from './pageobjects/ide/RightToolbar';
import { Terminal } from './pageobjects/ide/Terminal';
import { DebugView } from './pageobjects/ide/DebugView';
@ -52,7 +52,8 @@ import { CheMultiuserTokenHandler } from './utils/requestHandlers/tokens/CheMult
import { CheSingleUserAuthorizationHeaderHandler } from './utils/requestHandlers/headers/CheSingleUserAuthorizationHeaderHandler';
import { ITokenHandler } from './utils/requestHandlers/tokens/ITokenHandler';
import { CheApiRequestHandler } from './utils/requestHandlers/CheApiRequestHandler';
import { CheGitApi } from './utils/VCS/CheGitApi';
import { GitHubUtil} from './utils/VCS/github/GitHubUtil';
const e2eContainer: Container = new Container();
@ -89,7 +90,7 @@ e2eContainer.bind<Editor>(CLASSES.Editor).to(Editor).inSingletonScope();
e2eContainer.bind<TopMenu>(CLASSES.TopMenu).to(TopMenu).inSingletonScope();
e2eContainer.bind<QuickOpenContainer>(CLASSES.QuickOpenContainer).to(QuickOpenContainer).inSingletonScope();
e2eContainer.bind<PreviewWidget>(CLASSES.PreviewWidget).to(PreviewWidget).inSingletonScope();
e2eContainer.bind<GitHubPlugin>(CLASSES.GitHubPlugin).to(GitHubPlugin).inSingletonScope();
e2eContainer.bind<GitPlugin>(CLASSES.GitPlugin).to(GitPlugin).inSingletonScope();
e2eContainer.bind<RightToolbar>(CLASSES.RightToolbar).to(RightToolbar).inSingletonScope();
e2eContainer.bind<Terminal>(CLASSES.Terminal).to(Terminal).inSingletonScope();
e2eContainer.bind<DebugView>(CLASSES.DebugView).to(DebugView).inSingletonScope();
@ -102,5 +103,6 @@ e2eContainer.bind<CheLoginPage>(CLASSES.CheLoginPage).to(CheLoginPage).inSinglet
e2eContainer.bind<NotificationCenter>(CLASSES.NotificationCenter).to(NotificationCenter).inSingletonScope();
e2eContainer.bind<PreferencesHandler>(CLASSES.PreferencesHandler).to(PreferencesHandler).inSingletonScope();
e2eContainer.bind<CheApiRequestHandler>(CLASSES.CheApiRequestHandler).to(CheApiRequestHandler).inSingletonScope();
e2eContainer.bind<CheGitApi>(CLASSES.CheGitApi).to(CheGitApi).inSingletonScope();
e2eContainer.bind<GitHubUtil>(CLASSES.GitHubUtil).to(GitHubUtil).inSingletonScope();
export { e2eContainer };

View File

@ -44,6 +44,9 @@ const CLASSES = {
OpenWorkspaceWidget: 'OpenWorkspaceWidget',
ContextMenu: 'ContextMenu',
CheLoginPage: 'CheLoginPage',
GitHubUtil: 'GitHubUtil',
CheGitApi: 'CheGitApi',
GitPlugin: 'GitPlugin',
TestWorkspaceUtil: 'TestWorkspaceUtil',
NotificationCenter: 'NotificationCenter',
PreferencesHandler: 'PreferencesHandler',

View File

@ -0,0 +1,7 @@
--timeout 2200000
--reporter 'dist/driver/CheReporter.js'
-u tdd
--bail
--full-trace
--spec dist/tests/e2e/GitSsh.spec.js
--require source-map-support/register

View File

@ -14,6 +14,7 @@
"test-operatorhub-installation": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-che-operatorhub.opts",
"test-wkspc-creation-and-ls": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-wkspc-creation-and-ls.opts",
"test-java-vertx": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-java-vertx.opts",
"test-git-ssh": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-git-ssh.opts",
"test-all-devfiles": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-all-devfiles.opts",
"lint": "tslint --fix -p .",
"tsc": "tsc -p ."
@ -35,7 +36,8 @@
"ts-node": "^8.0.3",
"tslint": "5.10.0",
"typed-rest-client": "^1.2.0",
"typescript": "^3.4.3"
"typescript": "^3.4.3",
"@eclipse-che/api": "^7.5.0-SNAPSHOT"
},
"dependencies": {
"inversify": "^5.0.1",

View File

@ -1,85 +0,0 @@
import { injectable, inject } from 'inversify';
import { CLASSES } from '../../inversify.types';
import { DriverHelper } from '../../utils/DriverHelper';
import { TestConstants } from '../../TestConstants';
import { By, WebElement } from 'selenium-webdriver';
import { Ide, RightToolbarButton } from './Ide';
import { Logger } from '../../utils/Logger';
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
@injectable()
export class GitHubPlugin {
constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper,
@inject(CLASSES.Ide) private readonly ide: Ide) { }
async openGitHubPluginContainer(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitHubPlugin.openGitHubPluginContainer');
const selectedGitButtonLocator: By = By.xpath(Ide.SELECTED_GIT_BUTTON_XPATH);
await this.ide.waitRightToolbarButton(RightToolbarButton.Git, timeout);
const isButtonEnabled: boolean = await this.driverHelper.waitVisibilityBoolean(selectedGitButtonLocator);
if (!isButtonEnabled) {
await this.ide.waitAndClickRightToolbarButton(RightToolbarButton.Git);
}
await this.waitGitHubContainer(timeout);
}
async waitGitHubContainer(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitHubPlugin.waitGitHubContainer');
const githubContainerLocator: By = By.css('#theia-gitContainer .theia-git-main-container');
await this.driverHelper.waitVisibility(githubContainerLocator, timeout);
}
async getChangesList(): Promise<string[]> {
Logger.debug('GitHubPlugin.getChangesList');
const gitHubChangesLocator: By = By.xpath('//div[@id=\'theia-gitContainer\']//div[@id=\'unstagedChanges\']//div[contains(@class, \'gitItem\')]');
const changesElements: WebElement[] = await this.driverHelper.waitAllPresence(gitHubChangesLocator);
const changesCount: number = changesElements.length;
let gitHubChanges: string[] = [];
for (let i = 0; i < changesCount; i++) {
const gitHubChangesItemLocator: By = By.xpath(this.getGitHubChangesItemXpathLocator(i));
const changesText: string = await this.driverHelper.waitAndGetText(gitHubChangesItemLocator);
gitHubChanges.push(changesText);
}
return gitHubChanges;
}
async waitChangesPresence(changesText: string, timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug(`GitHubPlugin.waitChangesPresence "${changesText}"`);
await this.driverHelper
.getDriver()
.wait(async () => {
const changes: string[] = await this.getChangesList();
const isChangesPresent: boolean = changes.indexOf(changesText) !== -1;
if (isChangesPresent) {
return true;
}
}, timeout);
}
private getGitHubChangesItemXpathLocator(index: number): string {
return `(//div[@id='theia-gitContainer']//div[@id='unstagedChanges']//div[contains(@class, 'gitItem')])[${index + 1}]`;
}
}

View File

@ -0,0 +1,97 @@
import { injectable, inject } from 'inversify';
import { CLASSES } from '../../inversify.types';
import { DriverHelper } from '../../utils/DriverHelper';
import { TestConstants } from '../../TestConstants';
import { By } from 'selenium-webdriver';
import { Logger } from '../../utils/Logger';
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
@injectable()
export class GitPlugin {
private static readonly COMMIT_MESSAGE_TEXTAREA_CSS: string = 'textarea#theia-scm-input-message';
constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { }
async openGitPluginContainer(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.openGitPluginContainer');
const sourceControlGitBtnXpathLocator: string = '//li[@id=\'shell-tab-scm-view-container\' and contains(@style, \'height\')]';
await this.driverHelper.waitAndClick(By.xpath(sourceControlGitBtnXpathLocator), timeout);
await this.waitViewOfContainer(timeout);
}
async waitViewOfContainer(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.waitViewOfContainer');
const gitHubContainerIdLocator: By = By.id('scm-view-container--scm-view');
await this.driverHelper.waitVisibility(gitHubContainerIdLocator, timeout);
}
async waitCommitMessageTextArea(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.waitCommitMessageTextArea');
const textAreaCssLocator: By = By.css(GitPlugin.COMMIT_MESSAGE_TEXTAREA_CSS);
await this.driverHelper.waitVisibility(textAreaCssLocator, timeout);
}
async typeCommitMessage(commitMessage: string, timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.typeCommitMessage');
await this.waitCommitMessageTextArea(timeout);
await this.driverHelper.type(By.css(GitPlugin.COMMIT_MESSAGE_TEXTAREA_CSS), commitMessage, timeout);
}
async selectCommandInMoreActionsMenu(commandName: string, timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.selectCommandInMoreActionsMenu');
await this.clickOnMoreActions(timeout);
await this.driverHelper.waitAndClick(By.xpath(`//li[@data-command]/div[text()=\'${commandName}\']`), timeout);
}
async clickOnMoreActions(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.clickOnMoreActions');
await this.driverHelper.waitAndClick(By.id('__more__'), timeout);
}
async waitChangedFileInChagesList(expectedItem: string, timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.waitChangedFileInChagesList');
await this.driverHelper.waitPresence(By.xpath(`//div[@class='changesContainer']//span[text()=\'${expectedItem}\']`), timeout);
}
async waitStagedFileInStagedChanges(expectedStagedItem: string, timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.waitStagedFileInStagedChanges');
await this.driverHelper.waitPresence(By.xpath(`//div[text()='Staged Changes']/parent::div/following-sibling::div//span[text()=\'${expectedStagedItem}\']`), timeout);
}
async commitFromScmView(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.commitFromScmView');
await this.driverHelper.waitAndClick(By.id('__scm-view-container_title:__plugin.scm.title.action.git.commit'), timeout);
}
async stageAllChanges(expectedStagedItem: string, timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.stageAllChanges');
await this.driverHelper.scrollTo(By.xpath('//div[@class=\'changesContainer\']//div[text()=\'Changes\']'), timeout);
await this.driverHelper.waitAndClick(By.xpath('//a[@title=\'Stage All Changes\']'), timeout);
await this.waitStagedFileInStagedChanges(expectedStagedItem);
}
async waitDataIsSynchronized(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug('GitPlugin.waitDataIsSynchronized');
await this.driverHelper.waitDisappearance(By.xpath(`//div[contains(@title,'Synchronize Changes')]//span[contains(.,' 0↓')]`), timeout);
}
}

View File

@ -18,7 +18,6 @@ import { Logger } from '../../utils/Logger';
export class OpenWorkspaceWidget {
private static readonly OPEN_WORKSPACE_MAIN_VIEW_XPATH = '//div[@class=\'dialogTitle\']/div[text()=\'Open Workspace\']';
private static readonly OPEN_WORKSPACE_OPEN_BTN_CSS = 'div.dialogControl>button.main';
private static readonly THEIA_LOCATION_LIST_CSS = 'select.theia-LocationList';
constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {
}
@ -63,12 +62,4 @@ export class OpenWorkspaceWidget {
await this.driverHelper.waitAndClick(By.id(`/${currentPath}`));
}
}
async selectRootWorkspaceItemInDropDawn(rootProject: string) {
Logger.debug(`OpenWorkspaceWidget.selectRootWorkspaceItemInDropDawn "${rootProject}"`);
await this.driverHelper.waitAndClick(By.css(OpenWorkspaceWidget.THEIA_LOCATION_LIST_CSS));
await this.driverHelper.waitAndClick(By.css(`option[value=\'file:///${rootProject}']`));
}
}

View File

@ -36,8 +36,7 @@ export class QuickOpenContainer {
public async clickOnContainerItem(itemText: string, timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) {
Logger.debug(`QuickOpenContainer.clickOnContainerItem "${itemText}"`);
const quickContainerItemLocator: By = By.xpath(`//div[@class='quick-open-entry']//span[text()='${itemText}']`);
const quickContainerItemLocator: By = By.css(`div[aria-label="${itemText}, picker"]`);
await this.waitContainer(timeout);
await this.driverHelper.waitAndClick(quickContainerItemLocator, timeout);
await this.waitContainerDisappearance();
@ -45,8 +44,14 @@ export class QuickOpenContainer {
public async type(text: string) {
Logger.debug(`QuickOpenContainer.type "${text}"`);
await this.driverHelper.enterValue(By.css('.quick-open-input input'), text);
}
public async typeAndSelectSuggestion(text: string, suggestedText: string) {
Logger.debug('QuickOpenContainer.typeAndSelectSuggestion');
await this.driverHelper.type(By.css('div.monaco-inputbox input.input'), text);
await this.clickOnContainerItem(suggestedText);
}
}

View File

@ -0,0 +1,127 @@
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
import { assert } from 'chai';
import { test } from 'mocha';
import { e2eContainer } from '../../inversify.config';
import { CLASSES, TYPES } from '../../inversify.types';
import { Editor } from '../../pageobjects/ide/Editor';
import { GitPlugin } from '../../pageobjects/ide/GitPlugin';
import { Ide } from '../../pageobjects/ide/Ide';
import { ProjectTree } from '../../pageobjects/ide/ProjectTree';
import { QuickOpenContainer } from '../../pageobjects/ide/QuickOpenContainer';
import { ICheLoginPage } from '../../pageobjects/login/ICheLoginPage';
import { TestConstants } from '../../TestConstants';
import { DriverHelper } from '../../utils/DriverHelper';
import { NameGenerator } from '../../utils/NameGenerator';
import { CheGitApi } from '../../utils/VCS/CheGitApi';
import { GitHubUtil } from '../../utils/VCS/github/GitHubUtil';
import { TestWorkspaceUtil } from '../../utils/workspace/TestWorkspaceUtil';
import { TopMenu } from '../../pageobjects/ide/TopMenu';
const driverHelper: DriverHelper = e2eContainer.get(CLASSES.DriverHelper);
const ide: Ide = e2eContainer.get(CLASSES.Ide);
const quickOpenContainer: QuickOpenContainer = e2eContainer.get(CLASSES.QuickOpenContainer);
const editor: Editor = e2eContainer.get(CLASSES.Editor);
const namespace: string = TestConstants.TS_SELENIUM_USERNAME;
const workspaceName: string = TestConstants.TS_SELENIUM_HAPPY_PATH_WORKSPACE_NAME;
const topMenu: TopMenu = e2eContainer.get(CLASSES.TopMenu);
const loginPage: ICheLoginPage = e2eContainer.get<ICheLoginPage>(TYPES.CheLogin);
const gitHubUtils: GitHubUtil = e2eContainer.get<GitHubUtil>(CLASSES.GitHubUtil);
const cheGitAPI: CheGitApi = e2eContainer.get(CLASSES.CheGitApi);
const projectTree: ProjectTree = e2eContainer.get(CLASSES.ProjectTree);
const gitPlugin: GitPlugin = e2eContainer.get(CLASSES.GitPlugin);
const testWorkspaceUtils: TestWorkspaceUtil = e2eContainer.get<TestWorkspaceUtil>(TYPES.WorkspaceUtil);
suite('Git with ssh workflow', async () => {
const workspacePrefixUrl: string = `${TestConstants.TS_SELENIUM_BASE_URL}/dashboard/#/ide/TestConstants.TS_SELENIUM_USERNAME/`;
const wsNameCheckGeneratingKeys = 'checkGeneraringSsh';
const wsNameCheckPropagatingKeys = 'checkPropagatingSsh';
const committedFile = 'README.md';
suiteSetup(async function () {
const wsConfig = await testWorkspaceUtils.getBaseDevfile();
wsConfig.metadata!.name = wsNameCheckGeneratingKeys;
await testWorkspaceUtils.createWsFromDevFile(wsConfig);
});
test('Login into workspace and open tree container', async () => {
await driverHelper.navigateToUrl(workspacePrefixUrl + wsNameCheckGeneratingKeys);
await loginPage.login();
await ide.waitWorkspaceAndIde(namespace, workspaceName);
await projectTree.openProjectTreeContainer();
});
test('Generate a SSH key', async () => {
await topMenu.selectOption('View', 'Find Command...');
await quickOpenContainer.typeAndSelectSuggestion('SSH', 'SSH: generate key pair...');
await ide.waitNotificationAndClickOnButton('Key pair successfully generated, do you want to view the public key', 'View');
await editor.waitEditorOpened('Untitled-0');
await editor.waitText('Untitled-0', 'ssh-rsa');
});
test('Add a SSH key to GitHub side and clone by ssh link', async () => {
const sshName: string = NameGenerator.generate('test-SSH-', 5);
const publicSshKey = await cheGitAPI.getPublicSSHKey();
await gitHubUtils.addPublicSshKeyToUserAccount(TestConstants.TS_GITHUB_TEST_REPO_ACCESS_TOKEN, sshName, publicSshKey);
await cloneTestRepo();
});
test('Change commit and push', async function changeCommitAndPushFunc() {
const currentDate: string = Date.now().toString();
await projectTree.expandPathAndOpenFile('Spoon-Knife', committedFile);
await editor.type(committedFile, currentDate + '\n', 1);
await gitPlugin.openGitPluginContainer();
await gitPlugin.waitChangedFileInChagesList(committedFile);
await gitPlugin.stageAllChanges(committedFile);
await gitPlugin.waitChangedFileInChagesList(committedFile);
await gitPlugin.typeCommitMessage(this.test!.title + currentDate);
await gitPlugin.commitFromScmView();
await gitPlugin.selectCommandInMoreActionsMenu('Push');
await gitPlugin.waitDataIsSynchronized();
const rawDataFromFile: string = await gitHubUtils.getRawContentFromFile(TestConstants.TS_GITHUB_TEST_REPO + '/master/' + committedFile);
assert.isTrue(rawDataFromFile.includes(currentDate));
await testWorkspaceUtils.cleanUpAllWorkspaces();
});
test('Check ssh key in a new workspace', async () => {
const data = await testWorkspaceUtils.getBaseDevfile();
data.metadata!.name = wsNameCheckPropagatingKeys;
await testWorkspaceUtils.createWsFromDevFile(data);
await driverHelper.navigateToUrl(workspacePrefixUrl + wsNameCheckPropagatingKeys);
await ide.waitWorkspaceAndIde(namespace, workspaceName);
await projectTree.openProjectTreeContainer();
await cloneTestRepo();
await projectTree.waitItem('Spoon-Knife');
});
});
suite('Cleanup', async () => {
test('Remove test workspace', async () => {
await testWorkspaceUtils.cleanUpAllWorkspaces();
});
});
async function cloneTestRepo() {
const sshLinkToRepo: string = 'git@github.com:' + TestConstants.TS_GITHUB_TEST_REPO + '.git';
const confirmMessage = 'Repository URL (Press \'Enter\' to confirm your input or \'Escape\' to cancel)';
await topMenu.selectOption('View', 'Find Command...');
await quickOpenContainer.typeAndSelectSuggestion('clone', 'Git: Clone');
await quickOpenContainer.typeAndSelectSuggestion(sshLinkToRepo, confirmMessage);
}

View File

@ -107,7 +107,6 @@ export class DriverHelper {
await this.wait(polling);
continue;
}
throw err;
}
}

View File

@ -0,0 +1,26 @@
import { injectable, inject } from 'inversify';
import { CLASSES } from '../../inversify.types';
import { CheApiRequestHandler } from '../../utils/requestHandlers/CheApiRequestHandler';
@injectable()
export class CheGitApi {
static readonly GIT_API_ENTRIPOINT_URL = 'api/ssh/vcs';
constructor(@inject(CLASSES.CheApiRequestHandler) private readonly processRequestHandler: CheApiRequestHandler) { }
public async getPublicSSHKey(): Promise<string> {
try {
const responce = await this.processRequestHandler.get(CheGitApi.GIT_API_ENTRIPOINT_URL);
return responce.data[0].publicKey;
} catch (error) {
console.error('Cannot get public ssh key with API \n' + error);
throw error;
}
}
}

View File

@ -0,0 +1,47 @@
import { injectable } from 'inversify';
import axios from 'axios';
@injectable()
export class GitHubUtil {
private static readonly GITHUB_API_ENTRIPOINT_URL = 'https://api.github.com/';
/**
* add public part of ssh key to the defied github account
* @param authToken
* @param title
* @param key
*/
async addPublicSshKeyToUserAccount(authToken: string, title: string, key: string) {
const gitHubApiSshURL: string = GitHubUtil.GITHUB_API_ENTRIPOINT_URL + 'user/keys';
const authHeader = { headers: { 'Authorization': 'token ' + authToken, 'Content-Type': 'application/json' } };
const data = {
title: `${title}`,
key: `${key}`
};
try { await axios.post(gitHubApiSshURL, JSON.stringify(data), authHeader); } catch (error) {
console.error('Cannot add the public key to the GitHub account: ');
console.error(error);
throw error;
}
}
async getRawContentFromFile(pathToFile: string): Promise<string> {
const gitHubContentEntryPointUrl: string = 'https://raw.githubusercontent.com/';
const pathToRawContent: string = `${gitHubContentEntryPointUrl}${pathToFile}`;
const authorization: string = 'Authorization';
const contentType: string = 'Content-Type';
try {
delete axios.defaults.headers.common[authorization];
delete axios.defaults.headers.common[contentType];
const response = await axios.get(`${gitHubContentEntryPointUrl}${pathToFile}`);
return response.data;
} catch (error) {
console.error('Cannot get content form the raw github content: ' + pathToRawContent);
console.error(error);
throw error;
}
}
}

View File

@ -22,7 +22,7 @@ export class CheApiRequestHandler {
return await axios.get(this.assembleUrl(relativeUrl), await this.headerHandler.get());
}
async post(relativeUrl: string, data?: string): Promise<AxiosResponse> {
async post(relativeUrl: string, data?: string | any ): Promise<AxiosResponse> {
return await axios.post(this.assembleUrl(relativeUrl), data, await this.headerHandler.get());
}

View File

@ -8,39 +8,35 @@
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
import { che } from '@eclipse-che/api';
import { TestConstants } from '../../TestConstants';
import { injectable, inject } from 'inversify';
import { DriverHelper } from '../DriverHelper';
import { CLASSES } from '../../inversify.types';
import 'reflect-metadata';
import { WorkspaceStatus } from './WorkspaceStatus';
import { ITestWorkspaceUtil } from './ITestWorkspaceUtil';
import axios from 'axios';
import querystring from 'querystring';
import { error } from 'selenium-webdriver';
enum RequestType {
GET,
POST,
DELETE
}
import { CheApiRequestHandler } from '../requestHandlers/CheApiRequestHandler';
import { CLASSES } from '../../inversify.types';
@injectable()
export class TestWorkspaceUtil implements ITestWorkspaceUtil {
workspaceApiUrl: string = `${TestConstants.TS_SELENIUM_BASE_URL}/api/workspace`;
static readonly WORKSPACE_API_URL: string = 'api/workspace';
constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {
}
constructor(
@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper,
@inject(CLASSES.CheApiRequestHandler) private readonly processRequestHandler: CheApiRequestHandler
) { }
public async waitWorkspaceStatus(namespace: string, workspaceName: string, expectedWorkspaceStatus: WorkspaceStatus) {
const workspaceStatusApiUrl: string = `${this.workspaceApiUrl}/${namespace}:${workspaceName}`;
const workspaceStatusApiUrl: string = `${TestWorkspaceUtil.WORKSPACE_API_URL}/${namespace}:${workspaceName}`;
const attempts: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_ATTEMPTS;
const polling: number = TestConstants.TS_SELENIUM_WORKSPACE_STATUS_POLLING;
let workspaceStatus: string = '';
for (let i = 0; i < attempts; i++) {
const response = await this.processRequest(RequestType.GET, workspaceStatusApiUrl);
const response = await this.processRequestHandler.get(workspaceStatusApiUrl);
if (response.status !== 200) {
await this.driverHelper.wait(polling);
@ -60,12 +56,12 @@ export class TestWorkspaceUtil implements ITestWorkspaceUtil {
}
public async waitPluginAdding(namespace: string, workspaceName: string, pluginName: string) {
const workspaceStatusApiUrl: string = `${this.workspaceApiUrl}/${namespace}:${workspaceName}`;
const workspaceStatusApiUrl: string = `${TestWorkspaceUtil.WORKSPACE_API_URL}/${namespace}:${workspaceName}`;
const attempts: number = TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS;
const polling: number = TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_POLLING;
const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING;
for (let i = 0; i < attempts; i++) {
const response = await this.processRequest(RequestType.GET, workspaceStatusApiUrl);
const response = await this.processRequestHandler.get(workspaceStatusApiUrl);
if (response.status !== 200) {
await this.driverHelper.wait(polling);
@ -87,25 +83,34 @@ export class TestWorkspaceUtil implements ITestWorkspaceUtil {
}
}
public async getListOfWorkspaceId() {
const getAllWorkspacesResponse = await this.processRequest(RequestType.GET, this.workspaceApiUrl);
public async getListOfWorkspaceId(): Promise<string[]> {
const getAllWorkspacesResponse = await this.processRequestHandler.get(TestWorkspaceUtil.WORKSPACE_API_URL);
interface IMyObj {
id: string;
status: string;
}
let stringified = JSON.stringify(getAllWorkspacesResponse.data);
let arrayOfWorkspaces = <IMyObj[]>JSON.parse(stringified);
let wsList: Array<string> = [];
for (let entry of arrayOfWorkspaces) {
wsList.push(entry.id);
}
return wsList;
}
public async getIdOfRunningWorkspace(wsName: string): Promise<string> {
const getWorkspacesByNameResponse = await this.processRequestHandler.get(`${TestWorkspaceUtil.WORKSPACE_API_URL}/:${wsName}`);
return getWorkspacesByNameResponse.data.id;
}
public async getIdOfRunningWorkspaces(): Promise<Array<string>> {
try {
const getAllWorkspacesResponse = await this.processRequest(RequestType.GET, this.workspaceApiUrl);
const getAllWorkspacesResponse = await this.processRequestHandler.get(TestWorkspaceUtil.WORKSPACE_API_URL);
interface IMyObj {
id: string;
@ -123,97 +128,46 @@ export class TestWorkspaceUtil implements ITestWorkspaceUtil {
return idOfRunningWorkspace;
} catch (err) {
console.log(`Getting id of running workspaces failed. URL used: ${this.workspaceApiUrl}`);
console.log(`Getting id of running workspaces failed. URL used: ${TestWorkspaceUtil.WORKSPACE_API_URL}`);
throw err;
}
}
getIdOfRunningWorkspace(namespace: string): Promise<string> {
throw new Error('Method not implemented.');
}
public async removeWorkspaceById(id: string) {
const workspaceIdUrl: string = `${this.workspaceApiUrl}/${id}`;
const attempts: number = TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS;
const polling: number = TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_POLLING;
let stopped: Boolean = false;
for (let i = 0; i < attempts; i++) {
const getInfoResponse = await this.processRequest(RequestType.GET, workspaceIdUrl);
if (getInfoResponse.data.status === 'STOPPED') {
stopped = true;
break;
}
await this.driverHelper.wait(polling);
}
if (stopped) {
try {
const deleteWorkspaceResponse = await this.processRequest(RequestType.DELETE, workspaceIdUrl);
// response code 204: "No Content" expected
if (deleteWorkspaceResponse.status !== 204) {
throw new Error(`Can not remove workspace. Code: ${deleteWorkspaceResponse.status} Data: ${deleteWorkspaceResponse.data}`);
}
} catch (err) {
console.log(`Removing of workspace failed.`);
throw err;
}
} else {
throw new Error(`Can not remove workspace with id ${id}, because it is still not in STOPPED state.`);
}
}
async getCheBearerToken(): Promise<string> {
let params = {};
let keycloakUrl = TestConstants.TS_SELENIUM_BASE_URL;
if ( keycloakUrl.substr(7, 4).includes('che')) {
const keycloakAuthSuffix = '/auth/realms/che/protocol/openid-connect/token';
keycloakUrl = keycloakUrl.replace('che', 'keycloak') + keycloakAuthSuffix;
params = {
client_id: 'che-public',
username: TestConstants.TS_SELENIUM_USERNAME,
password: TestConstants.TS_SELENIUM_PASSWORD,
grant_type: 'password'
};
} else {
const keycloakAuthSuffix = '/auth/realms/codeready/protocol/openid-connect/token';
keycloakUrl = keycloakUrl.replace('codeready', 'keycloak') + keycloakAuthSuffix;
params = {
client_id: 'codeready-public',
username: TestConstants.TS_SELENIUM_USERNAME,
password: TestConstants.TS_SELENIUM_PASSWORD,
grant_type: 'password'
};
}
const workspaceIdUrl: string = `${TestWorkspaceUtil.WORKSPACE_API_URL}/${id}`;
try {
const responseToObtainBearerToken = await axios.post(keycloakUrl, querystring.stringify(params));
return responseToObtainBearerToken.data.access_token;
const deleteWorkspaceResponse = await this.processRequestHandler.delete(workspaceIdUrl);
// response code 204: "No Content" expected
if (deleteWorkspaceResponse.status !== 204) {
throw new Error(`Can not remove workspace. Code: ${deleteWorkspaceResponse.status} Data: ${deleteWorkspaceResponse.data}`);
}
} catch (err) {
console.log(`Can not get bearer token. URL used: ${keycloakUrl}`);
console.log(`Removing of workspace failed.`);
throw err;
}
}
public async stopWorkspaceById(id: string) {
const stopWorkspaceApiUrl: string = `${this.workspaceApiUrl}/${id}/runtime`;
try {
const stopWorkspaceResponse = await this.processRequest(RequestType.DELETE, stopWorkspaceApiUrl);
const stopWorkspaceApiUrl: string = `${TestWorkspaceUtil.WORKSPACE_API_URL}/${id}`;
try {
const stopWorkspaceResponse = await this.processRequestHandler.delete(`${stopWorkspaceApiUrl}/runtime`);
// response code 204: "No Content" expected
if (stopWorkspaceResponse.status !== 204) {
throw new Error(`Can not stop workspace. Code: ${stopWorkspaceResponse.status} Data: ${stopWorkspaceResponse.data}`);
}
for (let i = 0; i < TestConstants.TS_SELENIUM_PLUGIN_PRECENCE_ATTEMPTS; i++) {
const wsStatus = await this.processRequestHandler.get(stopWorkspaceApiUrl);
if (wsStatus.data.status === 'STOPPED') {
break;
}
await this.driverHelper.wait(TestConstants.TS_SELENIUM_DEFAULT_POLLING);
}
} catch (err) {
console.log(`Stopping workspace failed. URL used: ${stopWorkspaceApiUrl}`);
throw err;
}
}
public async cleanUpAllWorkspaces() {
@ -225,40 +179,29 @@ export class TestWorkspaceUtil implements ITestWorkspaceUtil {
let listAllWorkspaces: Array<string> = await this.getListOfWorkspaceId();
for (const entry of listAllWorkspaces) {
this.removeWorkspaceById(entry);
await this.removeWorkspaceById(entry);
}
}
removeWorkspace(namespace: string, workspaceId: string): void {
throw new Error('Method not implemented.');
async createWsFromDevFile(customTemplate: che.workspace.devfile.Devfile) {
try {
await this.processRequestHandler.post(TestWorkspaceUtil.WORKSPACE_API_URL + '/devfile', customTemplate);
} catch (error) {
console.error(error);
throw error;
}
}
stopWorkspace(namespace: string, workspaceId: string): void {
throw new Error('Method not implemented.');
}
async getBaseDevfile(): Promise<che.workspace.devfile.Devfile> {
const baseDevfile: che.workspace.devfile.Devfile = {
apiVersion: '1.0.0',
metadata: {
name: 'test-workspace'
}
};
async processRequest(reqType: RequestType, url: string) {
let response;
// maybe this check can be moved somewhere else at the begining so it will be executed just once
if (TestConstants.TS_SELENIUM_MULTIUSER === true) {
let authorization = 'Authorization';
axios.defaults.headers.common[authorization] = 'Bearer ' + await this.getCheBearerToken();
}
switch (reqType) {
case RequestType.GET: {
response = await axios.get(url);
break;
}
case RequestType.DELETE: {
response = await axios.delete(url);
break;
}
default: {
throw new Error('Unknown RequestType: ' + reqType);
}
}
return response;
return baseDevfile;
}
}