Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial E2E Tests #387

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: "[E2E Tests] Minikube"

on:
workflow_dispatch:
inputs:
theia-cloud-helm-branch:
description: "Theia Cloud Helm Branch to use"
type: string
default: "main"
schedule:
- cron: "0 13 * * 0"

permissions:
contents: read

concurrency:
group: ci-e2e-theia-cloud-minikube-${{ github.ref }}
cancel-in-progress: true

jobs:
runtests:
name: "Run Tests on Minikube (K8s: ${{ matrix.kubernetes }}, Paths: ${{ matrix.paths }}, Ephemeral: ${{ matrix.ephemeral }}, Keycloak: ${{ matrix.keycloak }})"
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
kubernetes: [v1.32.0, v1.31.4, v1.30.8, v1.29.12]
paths: [true, false]
ephemeral: [true, false]
keycloak: [true, false]
steps:
- name: Set Helm Branch for Scheduled Runs
if: ${{ github.event_name == 'schedule' }}
run: echo "INPUT_THEIA_CLOUD_HELM_BRANCH=main" >> $GITHUB_ENV

- name: Set Helm Branch for Manual Runs
if: ${{ github.event_name == 'workflow_dispatch' }}
run: echo "INPUT_THEIA_CLOUD_HELM_BRANCH=${{ github.event.inputs.theia-cloud-helm-branch }}" >> $GITHUB_ENV

- name: Checkout Theia Cloud
uses: actions/checkout@v4
with:
path: "./theia-cloud"

- name: Checkout Theia Cloud Helm
uses: actions/checkout@v4
with:
repository: "eclipsesource/theia-cloud-helm"
ref: "${{ env.INPUT_THEIA_CLOUD_HELM_BRANCH }}"
path: "./theia-cloud-helm"

- name: Setup Minikube
uses: manusa/actions-setup-minikube@92af4db914ab207f837251cd53eb7060e6477614
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: add comment which version this commit hash resolves to.

with:
minikube version: v1.33.1
kubernetes version: ${{ matrix.kubernetes }}
github token: ${{ secrets.GITHUB_TOKEN }}
start args: "--force"

- name: Enable Minikube Addons
run: |
minikube addons enable dashboard
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the dashboard needed for in E2E tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I always enable it locally for debugging, so this is how I copied the instruction here as well. But it shouldn't be required, unless it also has some side-effect, I'll disable it and trigger a test run.

minikube addons enable default-storageclass
minikube addons enable ingress
minikube addons enable metrics-server

- name: List Minikube Addons
run: minikube addons list

- name: Patch Ingress to allow snippet annotations and restart
run: |
kubectl -n ingress-nginx patch cm ingress-nginx-controller --patch '{"data":{"allow-snippet-annotations":"true"}}'
kubectl -n ingress-nginx delete pod -l app.kubernetes.io/name=ingress-nginx

# we use the none driver, so minikube should see the same images on the host
- name: Build Theia Cloud Images
run: |
cd theia-cloud
docker build --no-cache -t theiacloud/theia-cloud-service:minikube-ci-e2e -f dockerfiles/service/Dockerfile .
docker build --no-cache -t theiacloud/theia-cloud-operator:minikube-ci-e2e -f dockerfiles/operator/Dockerfile .
docker build --no-cache -t theiacloud/theia-cloud-landing-page:minikube-ci-e2e -f dockerfiles/landing-page/Dockerfile .
docker build --no-cache -t theiacloud/theia-cloud-wondershaper:minikube-ci-e2e -f dockerfiles/wondershaper/Dockerfile .
docker build --no-cache -t theiacloud/theia-cloud-conversion-webhook:minikube-ci-e2e -f dockerfiles/conversion-webhook/Dockerfile .
docker build --no-cache -t theiacloud/theia-cloud-demo:latest -f demo/dockerfiles/demo-theia-docker/Dockerfile demo/dockerfiles/demo-theia-docker/.
docker tag theiacloud/theia-cloud-demo:latest theiacloud/theia-cloud-demo:minikube-ci-e2e
docker build --no-cache -t theiacloud/theia-cloud-activity-demo:minikube-ci-e2e -f demo/dockerfiles/demo-theia-monitor-vscode/Dockerfile demo/dockerfiles/demo-theia-monitor-vscode/.
docker build --no-cache -t theiacloud/theia-cloud-activity-demo-theia:minikube-ci-e2e -f demo/dockerfiles/demo-theia-monitor-theia/Dockerfile .

- name: Get NGINX Ingress Controller Host
id: ingress_info
run: |
INGRESS_HOST=$(kubectl get svc -n ingress-nginx ingress-nginx-controller -o jsonpath='{.spec.clusterIP}')
echo "INGRESS_HOST=$INGRESS_HOST" >> $GITHUB_ENV

- name: Run Terraform
run: |
cd theia-cloud/terraform/ci-configurations
terraform init
terraform apply \
-var="ingress_ip=${{ env.INGRESS_HOST }}" \
-var="use_paths=${{ matrix.paths }}" \
-var="use_ephemeral_storage=${{ matrix.ephemeral }}" \
-var="enable_keycloak=${{ matrix.keycloak }}" \
-auto-approve

- name: List All Services in All Namespaces
run: kubectl get services --all-namespaces

- name: List All AppDefinitions in All Namespaces
run: kubectl get appdefinitions --all-namespaces

- name: List all Pods Images
run: kubectl get pods --all-namespaces -o jsonpath="{.items[*].spec.containers[*].image}" | tr -s '[[:space:]]' '\n' | sort | uniq

- name: Wait for Deployments to be Ready
run: |
kubectl wait --namespace ingress-nginx --for=condition=available deployment/ingress-nginx-controller --timeout=300s
kubectl wait --namespace theiacloud --for=condition=available deployment/conversion-webhook --timeout=300s
kubectl wait --namespace theiacloud --for=condition=available deployment/landing-page-deployment --timeout=300s
kubectl wait --namespace theiacloud --for=condition=available deployment/operator-deployment --timeout=300s
kubectl wait --namespace theiacloud --for=condition=available deployment/service-deployment --timeout=300s

# URLs
# service: servicex
# landing: trynow
# instance: instances
- name: Access NGINX Ingress URL
if: ${{ matrix.paths == false }}
run: |
curl -LkI "https://trynow.${{ env.INGRESS_HOST }}.nip.io/"

- name: Access NGINX Ingress URL
if: ${{ matrix.paths == true }}
run: |
curl -LkI "https://${{ env.INGRESS_HOST }}.nip.io/trynow/"

- name: Get CA cert
run: |
kubectl get secret theia-cloud-ca-key-pair -n cert-manager -o jsonpath='{.data.tls\.crt}' | base64 --decode > ca.crt
ls -al

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies and run tests
run: |
cd theia-cloud/node
npm ci
npm run build -w e2e-tests
export MATRIX_PATHS=${{ matrix.paths }}
export MATRIX_EPHEMERAL=${{ matrix.ephemeral }}
export MATRIX_KEYCLOAK=${{ matrix.keycloak }}
npm run ui-tests -w e2e-tests

- name: Upload Playwright test failure screenshots
if: failure()
uses: actions/upload-artifact@v3
with:
name: test-failure-screenshots
path: theia-cloud/node/e2e-tests/test-results/**/*.png
retention-days: 7
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.openapi-generator
.openapi-generator-ignore
openapitools.json
.DS_Store
.DS_Store
node/e2e-tests/test-results/.last-run.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ public Optional<PersistentVolume> createAndApplyPersistentVolume(String correlat
e);
return Optional.empty();
}
return client.persistentVolumesClient().loadAndCreate(correlationId, persistentVolumeYaml,
volume -> volume.addOwnerReference(workspace));
return client.persistentVolumesClient().loadAndCreate(correlationId, persistentVolumeYaml);
}

@Override
Expand Down
3 changes: 2 additions & 1 deletion node/configs/license-check-exclusions.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"npm/npmjs/-/landing-page/0.1.0": "Theia Cloud Internal Package",
"npm/npmjs/-/testing-page/0.1.0": "Theia Cloud Internal Package"
"npm/npmjs/-/testing-page/0.1.0": "Theia Cloud Internal Package",
"npm/npmjs/-/e2e-tests/0.1.0": "Theia Cloud Internal Package"
}
24 changes: 24 additions & 0 deletions node/e2e-tests/configs/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
testDir: '../lib/tests',
testMatch: ['**/*.js'],
workers: 1,
fullyParallel: false,
// Timeout for each test in milliseconds.
timeout: 60 * 1000,
use: {
baseURL:
process.env.MATRIX_PATHS !== 'true'
? `https://trynow.${process.env.INGRESS_HOST}.nip.io`
: `https://${process.env.INGRESS_HOST}.nip.io/trynow`,
browserName: 'chromium',
permissions: ['clipboard-read'],
screenshot: 'only-on-failure',
ignoreHTTPSErrors: true
},
preserveOutput: 'failures-only',
reporter: [['list']]
};

export default config;
19 changes: 19 additions & 0 deletions node/e2e-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "e2e-tests",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "tsc && npx playwright install chromium",
"lint": "eslint -c ../.eslintrc.js --ext .ts ./src",
"ui-tests": "npm run build && playwright test --config=./configs/playwright.config.ts"
},
"dependencies": {
"@kubernetes/client-node": "^0.22.3",
"@playwright/test": "^1.41.2",
"@theia/playwright": "^1.34.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"cross-env": "^7.0.3"
}
}
6 changes: 6 additions & 0 deletions node/e2e-tests/src/constats.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the file name contains a typo and the file should be named constants.ts?

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const namespace = 'theiacloud';
export const resourceGroup = 'theia.cloud';
export const sessionVersion = 'v1beta8';
export const sessionPlural = 'sessions';
export const workspaceVersion = 'v1beta5';
export const workspacePlural = 'workspaces';
45 changes: 45 additions & 0 deletions node/e2e-tests/src/k8s.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CustomObjectsApi, KubeConfig } from '@kubernetes/client-node';

import { namespace, resourceGroup, sessionPlural, sessionVersion, workspacePlural, workspaceVersion } from './constats';

const kc = new KubeConfig();
kc.loadFromDefault();
export const k8sApi = kc.makeApiClient(CustomObjectsApi);

export async function deleteAllSessions(): Promise<void> {
const sessions: any = await k8sApi.listNamespacedCustomObject(
resourceGroup,
sessionVersion,
namespace,
sessionPlural
);

for (const resource of sessions.body.items) {
await k8sApi.deleteNamespacedCustomObject(
resourceGroup,
sessionVersion,
namespace,
sessionPlural,
resource.metadata.name
);
}
}

export async function deleteAllWorkspaces(): Promise<void> {
const sessions: any = await k8sApi.listNamespacedCustomObject(
resourceGroup,
workspaceVersion,
namespace,
workspacePlural
);

for (const resource of sessions.body.items) {
await k8sApi.deleteNamespacedCustomObject(
resourceGroup,
workspaceVersion,
namespace,
workspacePlural,
resource.metadata.name
);
}
}
73 changes: 73 additions & 0 deletions node/e2e-tests/src/tests/login.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect, test } from '@playwright/test';

import { deleteAllSessions, deleteAllWorkspaces } from '../k8s';

test.describe('Login', () => {
test.beforeEach(async () => {
deleteAllSessions();
deleteAllWorkspaces();
});

test('should work', async ({ page, baseURL }) => {
test.skip(process.env.MATRIX_KEYCLOAK !== 'true', 'Skipping test because keycloak not enabled');

expect(baseURL).toBeDefined();

/* Click on Login button */
await page.goto(baseURL!);
const loginButton = await page.locator('.App__try-now-button');
await expect(loginButton).toHaveText('Login');
await loginButton.click();
const signInHeading = await page.locator('#kc-page-title');
await expect(signInHeading).toHaveText('Sign in to your account');

/* Enter user data */
await page.fill('#username', 'foo');
await page.fill('#password', 'foo');
await page.click('#kc-login');
await page.waitForLoadState('networkidle');
const launchButton = await page.locator('.App__try-now-button');
await expect(launchButton).toContainText('Launch Theia with Theia Extension Monitor');
});

test('not required when already logged in', async ({ page, baseURL }) => {
test.skip(process.env.MATRIX_KEYCLOAK !== 'true', 'Skipping test because keycloak not enabled');

expect(baseURL).toBeDefined();

/* Click on Login button */
await page.goto(baseURL!);
const loginButton = await page.locator('.App__try-now-button');
await expect(loginButton).toHaveText('Login');
await loginButton.click();
const signInHeading = await page.locator('#kc-page-title');
await expect(signInHeading).toHaveText('Sign in to your account');

/* Enter user data */
await page.fill('#username', 'foo');
await page.fill('#password', 'foo');
await page.click('#kc-login');
await page.waitForLoadState('networkidle');

/* reload page */
await page.reload();
await page.waitForLoadState('networkidle');

/* check button and email */
const launchButton = await page.locator('.App__try-now-button');
await expect(launchButton).toContainText('Launch Theia with Theia Extension Monitor');
const email = await page.locator('#root .App .header p:nth-child(1)');
await expect(email).toHaveText('[email protected]');
});

test('not required when not using Keycloak', async ({ page, baseURL }) => {
test.skip(process.env.MATRIX_KEYCLOAK === 'true', 'Skipping test because keycloak is enabled');

expect(baseURL).toBeDefined();

/* Check no Login Button */
await page.goto(baseURL!);
const launchButton = await page.locator('.App__try-now-button');
await expect(launchButton).toContainText('Launch Theia with Theia Extension Monitor');
});
});
Loading
Loading