diff --git a/.github/workflows/contributing-repositories-check.yml b/.github/workflows/contributing-repositories-check.yml index 06fd5c135b78..0500486097e5 100644 --- a/.github/workflows/contributing-repositories-check.yml +++ b/.github/workflows/contributing-repositories-check.yml @@ -8,7 +8,7 @@ # # Eclipse Che workflow for checking repository list in CONTRIBUTING.md file -name: CI +name: Check repository list in CONTRIBUTING.md file # Trigger the workflow on push or pull request on: diff --git a/.github/workflows/next-build.yml b/.github/workflows/next-build.yml index 0ab7aafb7f77..b513c575c937 100644 --- a/.github/workflows/next-build.yml +++ b/.github/workflows/next-build.yml @@ -7,7 +7,7 @@ # SPDX-License-Identifier: EPL-2.0 # -name: build-next +name: Build and push Next Che E2E image to quai.io on: workflow_dispatch: diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 000000000000..bfa8653ab0d0 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,169 @@ +# +# Copyright (c) 2021-2023 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 +# + +name: Empty Workspace test suite on minikube + +# Trigger the workflow on pull request +on: + workflow_dispatch: + pull_request: + branches: + - main + - 7.**.x + paths: + - 'tests/e2e/**' + - '.github/workflows/pr-check.yml' +env: + LOCAL_TEST_DIR: /tmp + +jobs: + pr-check: + runs-on: ubuntu-22.04 + + steps: + - name: Git checkout + uses: actions/checkout@v2 + + - name: Branch name + run: | + echo running on PR ${GITHUB_REF}, branch - ${GITHUB_HEAD_REF} + echo "pr_number=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')" >> "$GITHUB_ENV" + + - name: Configuring nodejs 16.x version + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Pull Che-Code Docker image + run: | + # + # pull che-code:latest docker image + # + docker pull quay.io/che-incubator/che-code:latest + + - name: Start minikube + id: run-minikube + uses: che-incubator/setup-minikube-action@next + with: + minikube-version: v1.23.2 + + - name: Install chectl + run: bash <(curl -sL https://www.eclipse.org/che/chectl/) --channel=next + + - name: Deploy Che + run: | + # + # load Che-Code image into minikube + # + minikube image load quay.io/che-incubator/che-code:latest + minikube image list + + # get patch file to set up resources + wget https://raw.githubusercontent.com/che-incubator/che-code/main/build/test/github-minikube-checluster-patch.yaml -P /tmp + + # + # deploy Che + # + chectl server:deploy \ + --batch \ + --platform minikube \ + --k8spodwaittimeout=120000 \ + --k8spodreadytimeout=120000 \ + --che-operator-cr-patch-yaml "/tmp/github-minikube-checluster-patch.yaml" + + # + # apply patch + # + kubectl patch devworkspaceoperatorconfigs \ + -n eclipse-che devworkspace-config \ + --patch '{"config": {"workspace": {"imagePullPolicy": "IfNotPresent"}}}' \ + --type merge + + - name: Pull Universal Base Image + run: | + minikube image pull quay.io/devfile/universal-developer-image:ubi8-latest + + - name: Run Empty Workspace API test + run: | + cd tests/e2e + npm ci + export TS_PLATFORM=kubernetes && + export TS_API_TEST_KUBERNETES_COMMAND_LINE_TOOL=kubectl && + export TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=false && + export TS_SELENIUM_BASE_URL=https://$(kubectl get ingress che -n eclipse-che -o jsonpath='{.spec.rules[0].host}') && + export NODE_TLS_REJECT_UNAUTHORIZED=0 && + export TS_SELENIUM_LOG_LEVEL=TRACE && + export TS_SELENIUM_DEFAULT_ATTEMPTS=2 && + export USERSTORY=EmptyWorkspaceAPI && + export TS_API_TEST_UDI_IMAGE=quay.io/devfile/universal-developer-image:ubi8-latest && + npm run driver-less-test + + - name: Build E2E test docker image + run: | + set -xe + + cd tests/e2e + docker build -t quay.io/eclipse/che-e2e:"${{ env.pr_number }}" -f build/dockerfiles/Dockerfile . + + - name: Run Empty Workspace UI test from che-e2e container + run: | + docker run \ + --shm-size=2048m \ + -p 5920:5920 \ + --network="host" \ + -e TS_PLATFORM=kubernetes \ + -e TS_API_TEST_KUBERNETES_COMMAND_LINE_TOOL=kubectl \ + -e TS_SELENIUM_K8S_USERNAME=che@eclipse.org \ + -e TS_SELENIUM_K8S_PASSWORD=admin \ + -e TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=false \ + -e TS_SELENIUM_BASE_URL=https://$(kubectl get ingress che -n eclipse-che -o jsonpath='{.spec.rules[0].host}') \ + -e TS_SELENIUM_LOAD_PAGE_TIMEOUT=60000 \ + -e TS_SELENIUM_START_WORKSPACE_TIMEOUT=120000 \ + -e TS_COMMON_DASHBOARD_WAIT_TIMEOUT=30000 \ + -e NODE_TLS_REJECT_UNAUTHORIZED=0 \ + -e DELETE_WORKSPACE_ON_FAILED_TEST=true \ + -e VIDEO_RECORDING=true \ + -e TS_SELENIUM_LOG_LEVEL=TRACE \ + -e TS_SELENIUM_DEFAULT_ATTEMPTS=2 \ + -v ${LOCAL_TEST_DIR}/tests/e2e/report:/tmp/e2e/report:Z \ + -v ${LOCAL_TEST_DIR}/tests/e2e/video:/tmp/ffmpeg_report:Z \ + -e TEST_SUITE=test \ + -e USERSTORY=EmptyWorkspace \ + quay.io/eclipse/che-e2e:"${{ env.pr_number }}" + + - name: Bump logs + if: always() + run: | + NS=admin-che + TARGET_DIR="/tmp/pr-check-artifacts/${NS}-info" + mkdir -p "$TARGET_DIR" + for POD in $(kubectl get pods -o name -n ${NS}); do + for CONTAINER in $(kubectl get -n ${NS} ${POD} -o jsonpath="{.spec.containers[*].name}"); do + echo "[INFO] Downloading logs $POD/$CONTAINER in $NS" + # container name includes `pod/` prefix. remove it + LOGS_FILE=$TARGET_DIR/$(echo ${POD}-${CONTAINER}.log | sed 's|pod/||g') + kubectl logs ${POD} -c ${CONTAINER} -n ${NS} > $LOGS_FILE || true + done + done + echo "[INFO] Bumping events in namespace ${NS}" + kubectl get events -n $NS > $TARGET_DIR/events.log || true + + - name: Store e2e artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: e2e-artifacts + path: /tmp/tests + + - name: Store k8s logs + if: always() + uses: actions/upload-artifact@v3 + with: + name: k8s-logs + path: /tmp/pr-check-artifacts/admin-che-info diff --git a/.github/workflows/typescript-publish.yml b/.github/workflows/typescript-publish.yml index 495dc126912a..63bc85f7f905 100644 --- a/.github/workflows/typescript-publish.yml +++ b/.github/workflows/typescript-publish.yml @@ -7,7 +7,7 @@ # SPDX-License-Identifier: EPL-2.0 # -name: typescript-publish-next +name: Publish Next Che E2E Tests to npmjs on: workflow_dispatch: diff --git a/tests/e2e/pageobjects/dashboard/Dashboard.ts b/tests/e2e/pageobjects/dashboard/Dashboard.ts index 4e3eb8f13173..747f10e36f00 100644 --- a/tests/e2e/pageobjects/dashboard/Dashboard.ts +++ b/tests/e2e/pageobjects/dashboard/Dashboard.ts @@ -15,27 +15,17 @@ import { DriverHelper } from '../../utils/DriverHelper'; import { TimeoutConstants } from '../../constants/TimeoutConstants'; import { Workspaces } from './Workspaces'; import { Logger } from '../../utils/Logger'; -import { OAuthConstants } from '../../constants/OAuthConstants'; import { BaseTestConstants } from '../../constants/BaseTestConstants'; @injectable() export class Dashboard { - private static readonly WORKSPACES_BUTTON_XPATH: string = `//div[@id='page-sidebar']//a[contains(text(), 'Workspaces (')]`; - private static readonly CREATE_WORKSPACE_BUTTON_XPATH: string = `//div[@id='page-sidebar']//a[text()='Create Workspace']`; - private static readonly LOADER_PAGE_STEP_TITLES_XPATH: string = '//*[@data-testid="step-title"]'; - private static readonly STARTING_PAGE_LOADER_CSS: string = '.main-page-loader'; - private static readonly LOADER_ALERT_XPATH: string = '//*[@data-testid="loader-alert"]'; - private static readonly LOGOUT_BUTTON_XPATH: string = '//button[text()="Logout"]'; - - private static getUserDropdownMenuButtonLocator(): By { - Logger.debug(`Dashboard.getUserDropdownMenuButtonLocator: get current user.`); - - const currentUser: string = OAuthConstants.TS_SELENIUM_OCP_USERNAME; - Logger.debug(`Dashboard.getUserDropdownMenuButtonLocator: ${currentUser}.`); - - return By.xpath(`//*[text()="${currentUser}"]//parent::button`); - - } + private static readonly WORKSPACES_BUTTON: By = By.xpath(`//div[@id='page-sidebar']//a[contains(text(), 'Workspaces (')]`); + private static readonly CREATE_WORKSPACE_BUTTON: By = By.xpath(`//div[@id='page-sidebar']//a[text()='Create Workspace']`); + private static readonly LOADER_PAGE_STEP_TITLES: By = By.xpath('//*[@data-testid="step-title"]'); + private static readonly STARTING_PAGE_LOADER: By = By.css('.main-page-loader'); + private static readonly LOADER_ALERT: By = By.xpath('//*[@data-testid="loader-alert"]'); + private static readonly LOGOUT_BUTTON: By = By.xpath('//button[text()="Logout"]'); + private static readonly USER_SETTINGS_DROPDOWN: By = By.xpath('//header//button/span[text()!=\'\']//parent::button'); constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, @inject(CLASSES.Workspaces) private readonly workspaces: Workspaces) { @@ -82,65 +72,47 @@ export class Dashboard { async waitPage(timeout: number = TimeoutConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT): Promise { Logger.debug('Dashboard.waitPage'); - await this.driverHelper.waitVisibility(By.xpath(Dashboard.WORKSPACES_BUTTON_XPATH), timeout); - await this.driverHelper.waitVisibility(By.xpath(Dashboard.CREATE_WORKSPACE_BUTTON_XPATH), timeout); + await this.driverHelper.waitVisibility(Dashboard.WORKSPACES_BUTTON, timeout); + await this.driverHelper.waitVisibility(Dashboard.CREATE_WORKSPACE_BUTTON, timeout); } async clickWorkspacesButton(timeout: number = TimeoutConstants.TS_CLICK_DASHBOARD_ITEM_TIMEOUT): Promise { Logger.debug('Dashboard.clickWorkspacesButton'); - await this.driverHelper.waitAndClick(By.xpath(Dashboard.WORKSPACES_BUTTON_XPATH), timeout); + await this.driverHelper.waitAndClick(Dashboard.WORKSPACES_BUTTON, timeout); } async clickCreateWorkspaceButton(timeout: number = TimeoutConstants.TS_CLICK_DASHBOARD_ITEM_TIMEOUT): Promise { Logger.debug('Dashboard.clickCreateWorkspaceButton'); - await this.driverHelper.waitAndClick(By.xpath(Dashboard.CREATE_WORKSPACE_BUTTON_XPATH), timeout); + await this.driverHelper.waitAndClick(Dashboard.CREATE_WORKSPACE_BUTTON, timeout); } async getLoaderAlert(timeout: number = TimeoutConstants.TS_WAIT_LOADER_PRESENCE_TIMEOUT): Promise { Logger.debug('Dashboard.getLoaderAlert'); - return await this.driverHelper.waitAndGetText(By.xpath(Dashboard.LOADER_ALERT_XPATH), timeout); + return await this.driverHelper.waitAndGetText(Dashboard.LOADER_ALERT, timeout); } async waitLoader(timeout: number = TimeoutConstants.TS_WAIT_LOADER_PRESENCE_TIMEOUT): Promise { Logger.debug('Dashboard.waitLoader'); - await this.driverHelper.waitAllPresence(By.xpath(Dashboard.LOADER_PAGE_STEP_TITLES_XPATH), timeout); - } - - async waitLoaderDisappearance(timeout: number = TimeoutConstants.TS_WAIT_LOADER_ABSENCE_TIMEOUT): Promise { - Logger.debug('Dashboard.waitLoaderDisappearance'); - - await this.driverHelper.waitDisappearance(By.xpath(Dashboard.LOADER_PAGE_STEP_TITLES_XPATH), timeout); - } - - async waitDisappearanceNavigationMenu(timeout: number = TimeoutConstants.TS_COMMON_DASHBOARD_WAIT_TIMEOUT): Promise { - Logger.debug('Dashboard.waitDisappearanceNavigationMenu'); - - await this.driverHelper.waitDisappearance(By.id('chenavmenu'), timeout); + await this.driverHelper.waitAllPresence(Dashboard.LOADER_PAGE_STEP_TITLES, timeout); } async waitStartingPageLoaderDisappearance(timeout: number = TimeoutConstants.TS_COMMON_DASHBOARD_WAIT_TIMEOUT): Promise { Logger.debug(`Dashboard.waitStartingPageLoaderDisappearance`); - await this.driverHelper.waitDisappearance(By.css(Dashboard.STARTING_PAGE_LOADER_CSS), timeout); + await this.driverHelper.waitDisappearance(Dashboard.STARTING_PAGE_LOADER, timeout); await this.driverHelper.wait(TimeoutConstants.TS_SELENIUM_DEFAULT_POLLING); } - async getRecentWorkspaceName(timeout: number = TimeoutConstants.TS_COMMON_DASHBOARD_WAIT_TIMEOUT): Promise { - Logger.debug(`Dashboard.getRecentWorkspaceName`); - - return await this.driverHelper.waitAndGetText(By.css('[data-testid="recent-workspace-item"]'), timeout); - } - async logout(timeout: number = TimeoutConstants.TS_COMMON_DASHBOARD_WAIT_TIMEOUT): Promise { Logger.debug(`Dashboard.logout`); await this.openDashboard(); - await this.driverHelper.waitAndClick(Dashboard.getUserDropdownMenuButtonLocator(), timeout); - await this.driverHelper.waitAndClick(By.xpath(Dashboard.LOGOUT_BUTTON_XPATH), timeout); - await this.driverHelper.waitDisappearance(Dashboard.getUserDropdownMenuButtonLocator(), timeout); + await this.driverHelper.waitAndClick(Dashboard.USER_SETTINGS_DROPDOWN, timeout); + await this.driverHelper.waitAndClick(Dashboard.LOGOUT_BUTTON, timeout); + await this.driverHelper.waitDisappearance(Dashboard.USER_SETTINGS_DROPDOWN, timeout); } } diff --git a/tests/e2e/specs/SmokeTest.spec.ts b/tests/e2e/specs/SmokeTest.spec.ts index 144c2c1c91f9..f5b9a7d165d5 100644 --- a/tests/e2e/specs/SmokeTest.spec.ts +++ b/tests/e2e/specs/SmokeTest.spec.ts @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -import { SideBarView, ViewSection } from 'monaco-page-objects'; +import { SideBarView, ViewItem, ViewSection } from 'monaco-page-objects'; import { ProjectAndFileTests } from '../tests-library/ProjectAndFileTests'; import { CLASSES } from '../configs/inversify.types'; import { e2eContainer } from '../configs/inversify.config'; @@ -17,14 +17,17 @@ import { Logger } from '../utils/Logger'; import { LoginTests } from '../tests-library/LoginTests'; import { StringUtil } from '../utils/StringUtil'; import { FactoryTestConstants } from '../constants/FactoryTestConstants'; +import { BrowserTabsUtil } from '../utils/BrowserTabsUtil'; +import { expect } from 'chai'; +import { BaseTestConstants } from '../constants/BaseTestConstants'; -const factoryUrl: string = FactoryTestConstants.TS_SELENIUM_FACTORY_GIT_REPO_URL || 'https://github.com/che-incubator/quarkus-api-example.git'; const projectAndFileTests: ProjectAndFileTests = e2eContainer.get(CLASSES.ProjectAndFileTests); const workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests); const loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests); -let projectName: string; +const browserTabsUtil: BrowserTabsUtil = e2eContainer.get(CLASSES.BrowserTabsUtil); suite(`The SmokeTest userstory`, async function (): Promise { + const factoryUrl: string = FactoryTestConstants.TS_SELENIUM_FACTORY_GIT_REPO_URL || 'https://github.com/che-incubator/quarkus-api-example.git'; let projectSection: ViewSection; suite(`Create workspace from factory:${factoryUrl}`, async function (): Promise { loginTests.loginIntoChe(); @@ -37,17 +40,22 @@ suite(`The SmokeTest userstory`, async function (): Promise { await projectAndFileTests.waitWorkspaceReadinessForCheCodeEditor(); }); test('Check a project folder has been created', async function (): Promise { - projectName = StringUtil.getProjectNameFromGitUrl(factoryUrl); + const projectName: string = StringUtil.getProjectNameFromGitUrl(factoryUrl); projectSection = await new SideBarView().getContent().getSection(projectName); Logger.debug(`new SideBarView().getContent().getSection: get ${projectName}`); }); test('Check the project files was imported', async function (): Promise { - const label: string = 'devfile.yaml'; - await projectSection.findItem(label); - Logger.debug(`projectSection.findItem: find ${label}`); + Logger.debug(`projectSection.findItem: find ${BaseTestConstants.TS_SELENIUM_PROJECT_ROOT_FILE_NAME}`); + const isFileImported: ViewItem | undefined = await projectSection.findItem(BaseTestConstants.TS_SELENIUM_PROJECT_ROOT_FILE_NAME); + expect(isFileImported).not.eqls(undefined); }); - test('Stopping and deleting the workspace', async function (): Promise { - await workspaceHandlingTests.stopAndRemoveWorkspace(WorkspaceHandlingTests.getWorkspaceName()); + test('Stop the workspace', async function (): Promise { + await workspaceHandlingTests.stopWorkspace(WorkspaceHandlingTests.getWorkspaceName()); + await browserTabsUtil.closeAllTabsExceptCurrent(); }); + test('Delete the workspace', async function (): Promise { + await workspaceHandlingTests.removeWorkspace(WorkspaceHandlingTests.getWorkspaceName()); + }); + loginTests.logoutFromChe(); }); }); diff --git a/tests/e2e/specs/dashboard-samples/EmptyWorkspace.spec.ts b/tests/e2e/specs/dashboard-samples/EmptyWorkspace.spec.ts index fffc6807ca1a..fc87d0904006 100644 --- a/tests/e2e/specs/dashboard-samples/EmptyWorkspace.spec.ts +++ b/tests/e2e/specs/dashboard-samples/EmptyWorkspace.spec.ts @@ -25,36 +25,32 @@ const browserTabsUtil: BrowserTabsUtil = e2eContainer.get(CLASSES.BrowserTabsUti const stackName: string = 'Empty Workspace'; suite(`${stackName} test`, async () => { - suite(`Create ${stackName} workspace`, async () => { - loginTests.loginIntoChe(); - workspaceHandlingTests.createAndOpenWorkspace(stackName); - workspaceHandlingTests.obtainWorkspaceNameFromStartingPage(); - test('Register running workspace', async () => { - registerRunningWorkspace(WorkspaceHandlingTests.getWorkspaceName()); - }); - test('Wait workspace readiness', async() => { - await projectAndFileTests.waitWorkspaceReadinessForCheCodeEditor(); + loginTests.loginIntoChe(); + workspaceHandlingTests.createAndOpenWorkspace(stackName); + workspaceHandlingTests.obtainWorkspaceNameFromStartingPage(); + test('Register running workspace', async () => { + registerRunningWorkspace(WorkspaceHandlingTests.getWorkspaceName()); + }); + test('Wait workspace readiness', async () => { + await projectAndFileTests.waitWorkspaceReadinessForCheCodeEditor(); - const workbench: Workbench = new Workbench(); - const activityBar: ActivityBar = workbench.getActivityBar(); - const activityBarControls: ViewControl[] = await activityBar.getViewControls(); + const workbench: Workbench = new Workbench(); + const activityBar: ActivityBar = workbench.getActivityBar(); + const activityBarControls: ViewControl[] = await activityBar.getViewControls(); - Logger.debug(`Editor sections:`); - for (const control of activityBarControls) { - Logger.debug(`${await control.getTitle()}`); - } - }); + Logger.debug(`Editor sections:`); + for (const control of activityBarControls) { + Logger.debug(`${await control.getTitle()}`); + } }); - suite('Stopping and deleting the workspace', async () => { - test('Stop the workspace', async function (): Promise { - await workspaceHandlingTests.stopWorkspace(WorkspaceHandlingTests.getWorkspaceName()); - await browserTabsUtil.closeAllTabsExceptCurrent(); - }); + test('Stop the workspace', async function (): Promise { + await workspaceHandlingTests.stopWorkspace(WorkspaceHandlingTests.getWorkspaceName()); + await browserTabsUtil.closeAllTabsExceptCurrent(); + }); - test('Delete the workspace', async function (): Promise { - await workspaceHandlingTests.removeWorkspace(WorkspaceHandlingTests.getWorkspaceName()); - }); - loginTests.logoutFromChe(); + test('Delete the workspace', async function (): Promise { + await workspaceHandlingTests.removeWorkspace(WorkspaceHandlingTests.getWorkspaceName()); }); + loginTests.logoutFromChe(); }); diff --git a/tests/e2e/specs/dashboard-samples/Quarkus.spec.ts b/tests/e2e/specs/dashboard-samples/Quarkus.spec.ts index 998f11da5e9f..d0aa1ca5e5ed 100644 --- a/tests/e2e/specs/dashboard-samples/Quarkus.spec.ts +++ b/tests/e2e/specs/dashboard-samples/Quarkus.spec.ts @@ -16,6 +16,7 @@ import { CLASSES } from '../../configs/inversify.types'; import { WorkspaceHandlingTests } from '../../tests-library/WorkspaceHandlingTests'; import { ProjectAndFileTests } from '../../tests-library/ProjectAndFileTests'; import { Logger } from '../../utils/Logger'; +import { BaseTestConstants } from '../../constants/BaseTestConstants'; const stackName: string = 'Java 11 with Quarkus'; const projectName: string = 'quarkus-quickstarts'; @@ -40,9 +41,8 @@ suite(`The ${stackName} userstory`, async function (): Promise { Logger.debug(`new SideBarView().getContent().getSection: get ${projectName}`); }); test('Check the project files was imported', async function (): Promise { - const label: string = 'devfile.yaml'; - await projectSection.findItem(label); - Logger.debug(`projectSection.findItem: find ${label}`); + await projectSection.findItem(BaseTestConstants.TS_SELENIUM_PROJECT_ROOT_FILE_NAME); + Logger.debug(`projectSection.findItem: find ${BaseTestConstants.TS_SELENIUM_PROJECT_ROOT_FILE_NAME}`); }); test('Stopping and deleting the workspace', async function (): Promise { await workspaceHandlingTests.stopAndRemoveWorkspace(WorkspaceHandlingTests.getWorkspaceName());