From 2d00444a5a285560bc4aa613ae069a1b8c166ba7 Mon Sep 17 00:00:00 2001 From: "lalitdeore12@gmail.com" Date: Thu, 20 Feb 2025 18:45:05 +0530 Subject: [PATCH 01/24] [Feature] - Add frontend testing script for onprem --- .github/workflows/quick-testing.yml | 19 +- frontend/frontend-testing.sh | 104 ++++++++++ frontend/package.json | 3 +- frontend/selenium-test.js | 249 +++++++++++++++++++++++ frontend/src/App.jsx | 1 + frontend/src/components/EditWorkflow.jsx | 2 + frontend/src/views/LoginPage.jsx | 1 + frontend/src/views/Workflows2.jsx | 1 + 8 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 frontend/frontend-testing.sh create mode 100644 frontend/selenium-test.js diff --git a/.github/workflows/quick-testing.yml b/.github/workflows/quick-testing.yml index 17ac28ac..db68f4ae 100644 --- a/.github/workflows/quick-testing.yml +++ b/.github/workflows/quick-testing.yml @@ -5,6 +5,7 @@ on: workflows: ["dockerbuild"] types: - completed + workflow_dispatch: jobs: build: @@ -17,9 +18,17 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - + + - name: Set up Docker + uses: docker/setup-buildx-action@v1 + + - name: Install Docker Compose + run: | + sudo apt-get update + sudo apt-get install -y docker-compose + - name: Set up opensearch directory - run: mkdir shuffle-database && chmod -R 777 shuffle-database + run: mkdir -p shuffle-database && chmod -R 777 shuffle-database - name: Build the stack run: docker-compose up -d @@ -95,6 +104,12 @@ jobs: echo "User registration failed after $MAX_RETRIES attempts." exit 1 + - name: Run Selenium testing for frontend + run: | + cd $GITHUB_WORKSPACE/frontend + chmod +x frontend-testing.sh + ./frontend-testing.sh + - name: Get the API key and run a health check run: | RESPONSE=$(curl -s -k -u admin:StrongShufflePassword321! 'https://localhost:9200/users/_search') diff --git a/frontend/frontend-testing.sh b/frontend/frontend-testing.sh new file mode 100644 index 00000000..454f9a12 --- /dev/null +++ b/frontend/frontend-testing.sh @@ -0,0 +1,104 @@ + +SERVER_URL="http://localhost:3000" + +declare -a Routes=() + +# Add all cloud routes here +if [[ "$SERVER_URL" == "http://localhost:3001" || "$SERVER_URL" == "http://localhost:3002" || "$SERVER_URL" == "https://sandbox.shuffler.io" || "$SERVER_URL" == "https://shuffler.io" ]]; then +Routes+=( +'homepage' +'home' +'contact' +'workflows/fa9314a7-bb9b-4b41-8885-1d02b199f04d' +'services' +'debug' +'creator' +'articles' +'partners' +'pricing' +'pricing2' +'training' +'professional-services' +'detections' +) +fi + +# Add all common routes here +Routes+=( +'workflows' +'workflows?tab=org_workflows' +'workflows?tab=my_workflows' +'workflows?tab=all_workflows' +'usecases' +'usecases2' +'getting-started' +'welcome' +'health' +'docs' +'settings' +'apps/new' +'apps/gmail' +'apis/gmail' +'forms' +'apps' +'apps?tab=my_apps' +'apps?tab=all_apps' +'search' +'search?tab=org_apps' +'search?tab=my_apps' +'search?tab=workflows' +'search?tab=docs' +'search?tab=creators' +'search?tab=discord' +"admin?tab=organization" +"admin?tab=users" +"admin?tab=app_auth" +"admin?tab=datastore" +"admin?tab=files" +"admin?tab=triggers" +"admin?tab=locations" +"admin?tab=tenants" +"admin?admin_tab=org_config" +"admin?admin_tab=sso" +"admin?admin_tab=notifications" +"admin?admin_tab=billingstats" +"admin?admin_tab=branding(beta)" +) + +ALL_ROUTES=("${Routes[@]}") + +# Stop frontend container to so it test unpushed changes to shuffle-frontend container +docker stop shuffle-frontend + +# Install dependencies +yarn install +echo "Starting frontend..." + +BROWSER=none yarn start & +SERVER_PID=$! +echo "Frontend started with PID: $SERVER_PID" + +echo "Waiting for 1 minute to ensure the server is fully up..." +sleep 60 + +echo "Server is up! Starting Selenium tests..." + +echo "Starting frontend tests..." +node selenium-test.js "$SERVER_URL" "${ALL_ROUTES[@]}" + +TEST_EXIT_CODE=$? + +if [[ $TEST_EXIT_CODE -ne 0 ]]; then +echo "Selenium tests failed. Exiting..." +kill $SERVER_PID +exit 1 +fi + +kill $SERVER_PID +echo "Testing complete. See above logs for errors if any." + +# Starting frontend container +echo "Starting shuffle-frontend container..." +docker start shuffle-frontend +echo "shuffle-frontend started successfully." +exit 0 \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 7426ee23..4f6d2166 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -88,6 +88,7 @@ "remark-rehype": "^11.0.0", "rsuite": "^5.23.0", "search-insights": "^2.2.1", + "selenium-webdriver": "^4.28.1", "shellwords": "^1.0.1", "simplebar": "^4.2.3", "styled-components": "^4.4.1", @@ -98,7 +99,7 @@ "zone.js": "~0.8.26" }, "scripts": { - "start": "HTTPS=false&&PORT=3000 GENERATE_SOURCEMAP=false react-scripts --openssl-legacy-provider start", + "start": "set HTTPS=false&& set PORT=3000&& set GENERATE_SOURCEMAP=true&& react-scripts --openssl-legacy-provider start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", diff --git a/frontend/selenium-test.js b/frontend/selenium-test.js new file mode 100644 index 00000000..eb9b731e --- /dev/null +++ b/frontend/selenium-test.js @@ -0,0 +1,249 @@ +const { Builder, By, until } = require('selenium-webdriver'); +const chrome = require('selenium-webdriver/chrome'); +const fs = require('fs'); +const path = require('path'); + +console.log('Starting Selenium script for testing pages...'); + +(async () => { + const userDataDir = path.join(__dirname, 'chrome-user-data', `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`); + fs.mkdirSync(userDataDir, { recursive: true }); + + let options = new chrome.Options(); + options.addArguments('--no-sandbox'); + options.addArguments('--headless'); + + let driver = await new Builder() + .forBrowser('chrome') + .setChromeOptions(options) + .build(); + + const SuccessfullyLoadedPath = []; + const FailedToLoadPath = []; + + try { + await driver.manage().window().setRect({ width: 1600, height: 1200 }); + await driver.manage().setTimeouts({ implicit: 15000 }); + + const frontendURL = process.argv[2]; + const routes = process.argv.slice(3); + + console.log('Frontend URL:', frontendURL); + const isCloud = frontendURL === 'http://localhost:3002' || frontendURL === 'https://sandbox.shuffler.io' || frontendURL === 'https://shuffler.io'; + + if (isCloud) { + // Login Credentials + const LOGIN_URL = `${frontendURL}/login`; + const USERNAME = 'shuffle-testing@gmail.com'; + const PASSWORD = 'testing@123'; + + console.log('Logging in...'); + await driver.get(LOGIN_URL); + + try { + await driver.wait(until.elementLocated(By.css('#emailfield')), 10000); + await driver.findElement(By.css('#emailfield')).sendKeys(USERNAME); + + await driver.wait(until.elementLocated(By.css('#outlined-password-input')), 10000); + await driver.findElement(By.css('#outlined-password-input')).sendKeys(PASSWORD); + + await driver.wait(until.elementLocated(By.css('#loginButton')), 10000); + await driver.findElement(By.css('#loginButton')).click(); + + // Ensure login success by checking URL change + await driver.wait(async () => { + const url = await driver.getCurrentUrl(); + return url.includes('welcome'); + }, 20000); + + console.log('Successfully logged in!'); + } catch (error) { + console.error('Login failed:', error.message); + FailedToLoadPath.push(LOGIN_URL); + await driver.quit(); + process.exit(1); + } + }else { + // Steps for onprem testing + // 1. Login + // 2. Create new workflow + + // Write your own login credentials here if you are testing onprem locally + const USERNAME = 'demo@demo.io'; + const PASSWORD = 'supercoolpassword'; + const WORKFLOW_URL = `${frontendURL}/workflows`; + + try { + // Login + console.log('Logging in...'); + await driver.get(`${frontendURL}/login`); + + await driver.wait(until.elementLocated(By.css('#emailfield')), 10000); + await driver.findElement(By.css('#emailfield')).sendKeys(USERNAME); + + await driver.wait(until.elementLocated(By.css('#outlined-password-input')), 10000); + await driver.findElement(By.css('#outlined-password-input')).sendKeys(PASSWORD); + + await driver.wait(until.elementLocated(By.css('#loginButton')), 10000); + await driver.findElement(By.css('#loginButton')).click(); + + + // Ensure signup success by checking URL change + await driver.wait(async () => { + const url = await driver.getCurrentUrl(); + return url.includes('welcome') || url.includes('workflows'); + }, 20000); + + + const isParentPresent = await driver.wait(async () => { + return await driver.executeScript( + "return document.querySelector('.parent-component') !== null;" + ); + }, 5000).catch(() => false); + + if (!isParentPresent) { + const logs = await driver.manage().logs().get('browser'); + const severeErrors = logs.filter(log => log.level.name === 'SEVERE'); + if (severeErrors.length > 0) { + const crashCausingErrors = severeErrors.filter(err => { + return !err.message.includes('Warning:') && + !err.message.includes('MUI:') + }); + + console.error(`Page (${frontendURL}/login) did not load correctly:`, crashCausingErrors); + FailedToLoadPath.push(`${frontendURL}/login`); + } + } + + console.log('Successfully logged in!'); + + } catch (error) { + console.error('Failed to login:', error.message); + FailedToLoadPath.push(WORKFLOW_URL); + await driver.quit(); + process.exit(1); + } + + try { + // Create new workflow + console.log('Creating new workflow...'); + await driver.get(WORKFLOW_URL); + + await driver.sleep(1000); + + await driver.wait(until.elementLocated(By.css('#create_workflow_button')), 10000); + const create_workflow_button = await driver.findElement(By.css('#create_workflow_button')); + + await driver.sleep(1000); + await create_workflow_button.click(); + + await driver.sleep(1500); + + await driver.wait(until.elementLocated(By.css('#Enter-Workflow-Name')), 10000); + const enter_workflow_name_field = await driver.findElement(By.css('#Enter-Workflow-Name')); + + await driver.sleep(1000); + await enter_workflow_name_field.sendKeys("Test Workflow"); + + // Wait before saving + await driver.sleep(1500); + + await driver.wait(until.elementLocated(By.css('#save_workflow_button')), 10000); + const save_workflow_button = await driver.findElement(By.css('#save_workflow_button')); + + await driver.sleep(1000); // Delay before clicking + await save_workflow_button.click(); + + // Wait for the parent component to appear + await driver.sleep(2000); + + const isParentPresent = await driver.wait(async () => { + return await driver.executeScript( + "return document.querySelector('.parent-component') !== null;" + ); + }, 5000).catch(() => false); + + if (!isParentPresent) { + const logs = await driver.manage().logs().get('browser'); + const severeErrors = logs.filter(log => log.level.name === 'SEVERE'); + if (severeErrors.length > 0) { + const crashCausingErrors = severeErrors.filter(err => { + return !err.message.includes('Warning:') && + !err.message.includes('MUI:'); + }); + + console.error(`Page (${WORKFLOW_URL}) did not load correctly:`, crashCausingErrors); + FailedToLoadPath.push(`${WORKFLOW_URL}`); + } + } + + } catch (error) { + console.error('Failed to create new workflow:', error.message); + FailedToLoadPath.push(WORKFLOW_URL); + await driver.quit(); + process.exit(1); + } + } + + // Get routes from command line arguments + if (routes.length === 0) { + console.error('No routes found to test.'); + await driver.quit(); + process.exit(1); + } + console.log(`Found ${routes.length} routes to test.`); + + for (const route of routes) { + const url = `${frontendURL}/${route}`; + console.log(`Testing route: ${url}`); + + try { + await driver.get(url); + await driver.sleep(3000); + + // Wait for document readiness + await driver.wait(async () => { + return await driver.executeScript('return document.readyState') === 'complete'; + }, 10000); + + const isParentPresent = await driver.wait(async () => { + return await driver.executeScript( + "return document.querySelector('.parent-component') !== null;" + ); + }, 5000).catch(() => false); + + if (!isParentPresent) { + const logs = await driver.manage().logs().get('browser'); + const severeErrors = logs.filter(log => log.level.name === 'SEVERE'); + if (severeErrors.length > 0) { + const crashCausingErrors = severeErrors.filter(err => { + return !err.message.includes('Warning:') && + !err.message.includes('MUI:') + }); + + console.error(`Page (${url}) did not load correctly:`, crashCausingErrors); + FailedToLoadPath.push(url); + } + continue; + } + + console.log(`Successfully loaded: ${url}`); + SuccessfullyLoadedPath.push(url); + } catch (error) { + console.error(`Failed to load ${url}:`, error.message); + } + } + } finally { + console.log("Total pages tested: ", SuccessfullyLoadedPath.length + FailedToLoadPath.length); + console.log("Successfully loaded pages: ", SuccessfullyLoadedPath.length); + if (FailedToLoadPath.length > 0) { + console.log("Failed to load pages: ", FailedToLoadPath.length); + console.log("Failed to load pages paths: ", FailedToLoadPath); + process.exit(1); + }else { + console.log("No pages failed to load. Congrats!"); + } + await driver.quit(); + fs.rmSync(userDataDir, { recursive: true, force: true }); + } +})(); \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8c0e8870..03d4c77e 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -186,6 +186,7 @@ const App = (message, props) => { color: "rgba(255, 255, 255, 0.65)", minHeight: "100vh", }} + className='parent-component' > {