-
Notifications
You must be signed in to change notification settings - Fork 37
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
base: main
Are you sure you want to change the base?
Initial E2E Tests #387
Changes from all commits
2014e84
f3b94bb
cfe21cf
d86d7d6
c4232a9
30a1935
510739a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the dashboard needed for in E2E tests? |
||
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 |
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 |
---|---|---|
@@ -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" | ||
} |
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; |
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" | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
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'; |
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 | ||
); | ||
} | ||
} |
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'); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how many versions we should include here. Kubernetes typically provides patches for the last 3-4 releases. I think we should at least test the latest version and the oldest one within this range.
It might also be useful to include all versions, especially when updating our Kubernetes client API libraries. In such cases, if something breaks, it would be helpful to pinpoint exactly which versions have the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense to include all versions that are not end of life. Thus, I'd just leave it like this :)