diff --git a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/destination-list-item/index.tsx b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/destination-list-item/index.tsx index 8ce00ad1e7..80eaeaf54b 100644 --- a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/destination-list-item/index.tsx +++ b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/destination-list-item/index.tsx @@ -92,7 +92,7 @@ export const DestinationListItem: React.FC = ({ item, }; return ( - onSelect(item)}> + onSelect(item)}> destination diff --git a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/index.tsx b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/index.tsx index 5ab9f18b2a..41bb135ec8 100644 --- a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/index.tsx +++ b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/index.tsx @@ -49,7 +49,7 @@ const DestinationsList: React.FC = ({ items, setSelectedI {item.items.map((categoryItem) => ( - + ))} ); diff --git a/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-fast/sources-list/index.tsx b/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-fast/sources-list/index.tsx index 600511e929..1b95461fc3 100644 --- a/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-fast/sources-list/index.tsx +++ b/frontend/webapp/containers/main/sources/choose-sources/choose-sources-body/choose-sources-body-fast/sources-list/index.tsx @@ -136,7 +136,7 @@ export const SourcesList: React.FC = ({ const hasFilteredSources = !!filtered.length; return ( - + onSelectNamespace(namespace)}> onSelectAll(bool, namespace)} /> diff --git a/frontend/webapp/cypress.config.ts b/frontend/webapp/cypress.config.ts index 46927c59ca..a53e56af08 100644 --- a/frontend/webapp/cypress.config.ts +++ b/frontend/webapp/cypress.config.ts @@ -1,10 +1,12 @@ import Cypress from 'cypress'; +const PORT = 3000; +const BASE_URL = `http://localhost:${PORT}`; + const config: Cypress.ConfigOptions = { e2e: { - // this uses the "production" build, if you want to use the "development" build, you can use "port=3000" instead - baseUrl: 'http://localhost:3001', setupNodeEvents(on, config) {}, + baseUrl: BASE_URL, supportFile: false, waitForAnimations: true, }, diff --git a/frontend/webapp/cypress/e2e/connection.cy.ts b/frontend/webapp/cypress/e2e/connection.cy.ts new file mode 100644 index 0000000000..3e9d515d68 --- /dev/null +++ b/frontend/webapp/cypress/e2e/connection.cy.ts @@ -0,0 +1,10 @@ +describe('Root Connection', () => { + it('Should fetch a config with GraphQL. A redirect of any kind confirms Frontend + Backend connections.', () => { + cy.visit('/'); + + // If GraphQL failed to fetch the config, the app will remain on "/", thereby failing the test. + cy.location().should((loc) => { + expect(loc.pathname).to.be.oneOf(['/choose-sources', '/overview']); + }); + }); +}); diff --git a/frontend/webapp/cypress/e2e/onboarding.cy.ts b/frontend/webapp/cypress/e2e/onboarding.cy.ts index e8d975e581..a265113f12 100644 --- a/frontend/webapp/cypress/e2e/onboarding.cy.ts +++ b/frontend/webapp/cypress/e2e/onboarding.cy.ts @@ -1,7 +1,28 @@ describe('Onboarding', () => { - it('Visiting the root path fetches a config with GraphQL. A fresh install will result in a redirect to the start of onboarding, confirming Front + Back connections', () => { - cy.visit('/'); - // If backend connection failed for any reason, teh default redirect would be "/overview" - cy.location('pathname').should('eq', '/choose-sources'); + it('Should contain at least a "default" namespace', () => { + cy.visit('/choose-sources'); + + cy.get('#no-data').should('not.exist'); + cy.get('#namespace-default').should('exist'); + }); + + it('Should contain at least a "Jaeger" destination', () => { + cy.visit('/choose-destination'); + + cy.get('button').contains('ADD DESTINATION').click(); + cy.get('#no-data').should('not.exist'); + + cy.get('input').should('have.attr', 'placeholder', 'Search...').type('Jaeger'); + cy.get('#destination-jaeger').should('exist'); + }); + + it('Should allow the user to pass every step, and end-up on the Overview page.', () => { + cy.visit('/choose-sources'); + + cy.get('button').contains('NEXT').click(); + cy.location('pathname').should('eq', '/choose-destination'); + + cy.get('button').contains('DONE').click(); + cy.location('pathname').should('eq', '/overview'); }); }); diff --git a/frontend/webapp/package.json b/frontend/webapp/package.json index b144f7bcfb..01785ed277 100644 --- a/frontend/webapp/package.json +++ b/frontend/webapp/package.json @@ -3,15 +3,11 @@ "version": "0.1.0", "private": true, "scripts": { - "back:build": "cd .. && go build -o ./odigos-backend", - "back:start": "cd .. && ./odigos-backend --port 8085 --debug --address 0.0.0.0", - "predev": "rm -rf .next", "dev": "next dev", - "prebuild": "rm -rf out", "build": "next build", "start": "next start", "lint": "next lint --fix", - "cy": "cypress run --e2e -q", + "cy:run": "cypress run --e2e -q", "cy:open": "cypress open --e2e -b electron" }, "dependencies": { @@ -39,6 +35,5 @@ "eslint-config-next": "15.0.3", "postcss": "^8.4.49", "typescript": "5.6.3" - }, - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" + } } diff --git a/frontend/webapp/reuseable-components/no-data-found/index.tsx b/frontend/webapp/reuseable-components/no-data-found/index.tsx index 9d847addf3..91127dbbee 100644 --- a/frontend/webapp/reuseable-components/no-data-found/index.tsx +++ b/frontend/webapp/reuseable-components/no-data-found/index.tsx @@ -34,7 +34,7 @@ const Container = styled.div` const NoDataFound: React.FC = ({ title = 'No data found', subTitle = 'Check your search phrase and try one more time' }) => { return ( - + no-found {title} diff --git a/tests/common/odigos_ui.sh b/tests/common/odigos_ui.sh deleted file mode 100755 index bf187c67b8..0000000000 --- a/tests/common/odigos_ui.sh +++ /dev/null @@ -1,125 +0,0 @@ -#!/bin/bash - -# Ensure the script fails if any command fails -set -e - -scripts_dir="$(cd "$(dirname "$0")" && pwd)" -# The above "$scripts_dir" key is used to identify where the script was called from, to ensure all paths are relative to the script. -# This is useful when the script is called from another location, and the paths are relative to the calling script (for exmaple YAML file). - -log_file="$scripts_dir/odigos_ui.log" -pid_file="$scripts_dir/odigos_ui.pid" - -function get_process_id() { - if [ ! -f "$1" ]; then - # File does not exist - echo "0" - return - fi - - pid=$(cat "$1" 2>/dev/null) - if ps -p "$pid" > /dev/null 2>&1; then - # Process is running - echo "$pid" - else - # Process is not running - echo "0" - fi -} - -function check_process() { - # Check if the process is running - if [ "$1" == 0 ]; then - echo "Odigos UI - ❌ Failed to start" - cat "$log_file" - exit 1 - else - echo "Odigos UI - ✅ Ready" - cat "$log_file" - fi -} - -function kill_process() { - # Kill the process - if [ "$1" != 0 ]; then - echo "Odigos UI - 💀 Killing process ($1)" - kill $1 - fi -} - -function kill_all() { - pid=$(get_process_id "$pid_file") - kill_process $pid -} - -function stop() { - kill_all - - # Cleanup - rm -f "$log_file" - rm -f "$pid_file" -} - -function start() { - kill_all - cd "$scripts_dir/../../frontend/webapp" - - # Install dependencies - echo "Odigos UI - ⏳ Installing..." - yarn install > /dev/null 2> "$log_file" - - # Create a production build - echo "Odigos UI - ⏳ Building..." - yarn build > /dev/null 2> "$log_file" - yarn back:build > /dev/null 2> "$log_file" - - # Start the production build - echo "Odigos UI - ⏳ Starting..." - cd .. - ./odigos-backend --port 8085 --debug --address 0.0.0.0 > /dev/null 2> "$log_file" & - - sleep 1 - echo $! > "$pid_file" - pid=$(get_process_id "$pid_file") - check_process $pid -} - -function test() { - # Run tests on the Frontend - cd "$scripts_dir/../../frontend/webapp" - echo "Odigos UI - 👀 Testing with Cypress..." - - set +e # Temporarily disable "exit on error" - yarn cy - test_exit_code=$? - set -e # Re-enable "exit on error" - - if [ $test_exit_code -ne 0 ]; then - echo "Odigos UI - ❌ Cypress tests failed" - exit 1 - else - echo "Odigos UI - ✅ Cypress tests passed" - fi -} - -# This is to allow the script to be used dynamically, we call the function name from the CLI (start/stop/test/etc.) -# This method prevents duplicated code across multiple-scripts -function main() { - if [ $# -lt 1 ]; then - echo "❌ Error: Incorrect usage - '$0 '" - exit 1 - fi - - func="$1" - shift # Shift arguments, so $@ contains only the arguments for the function - - # Check if the function exists and call it (with the remaining arguments) - if declare -f "$func" > /dev/null; then - $func "$@" - else - echo "❌ Error: Function '$func' not found." - exit 1 - fi -} - -main "$@" diff --git a/tests/e2e/ui/chainsaw-test.yaml b/tests/e2e/ui/chainsaw-test.yaml index af2936c42d..26bc61af86 100644 --- a/tests/e2e/ui/chainsaw-test.yaml +++ b/tests/e2e/ui/chainsaw-test.yaml @@ -1,23 +1,56 @@ apiVersion: chainsaw.kyverno.io/v1alpha1 kind: Test metadata: - name: ui-cypress + name: ui spec: description: Run E2E tests against Odigos UI using Cypress skipDelete: true steps: - - name: Start the UI + - name: Install Odigos CLI try: - script: - timeout: 300s - content: ../../common/odigos_ui.sh start - - name: Test the UI + timeout: 60s + content: | + ../../../cli/odigos install --version e2e-test + + - name: Install App - Simple Demo try: - script: - timeout: 300s - content: ../../common/odigos_ui.sh test - - name: End the UI + timeout: 120s + content: | + kubectl apply -f https://raw.githubusercontent.com/odigos-io/simple-demo/main/kubernetes/deployment.yaml + kubectl wait --for=condition=available --timeout=120s deployment --all -n default + + - name: Add Destination - Jaeger try: - script: - timeout: 60s - content: ../../common/odigos_ui.sh stop + timeout: 120s + content: | + kubectl apply -f https://raw.githubusercontent.com/odigos-io/simple-demo/main/kubernetes/jaeger.yaml + kubectl wait --for=condition=available --timeout=120s deployment/jaeger -n tracing + + - name: Start UI from CLI + try: + - script: + timeout: 10s + content: | + nohup ../../../cli/odigos ui --beta > odigos-ui.log 2>&1 & + sleep 5 + + - name: Wait for UI + try: + - script: + timeout: 30s + content: | + for i in {1..10}; do + curl -s http://localhost:3000 && break || sleep 2 + done + + - name: Run Cypress tests + try: + - script: + timeout: 300s + content: | + cd ../../../frontend/webapp + yarn install + yarn cy:run