PromucFlow_constructor/app/client/cypress/scripts/cypress-local-setup.js
Sagar Khalasi cd80904369
chore: Run a local server with docker compose while running from yarn setup (#35636)
## Description
As a user, I want to run my backend server in local machine for running
cypress test cases.


Fixes #`35635`  


## Automation

/ok-to-test tags="@tag.Sanity"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/10469851734>
> Commit: 33c2dbbcf389a6840830e8914738a068d8ab41d9
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10469851734&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Sanity`
> Spec:
> <hr>Tue, 20 Aug 2024 10:54:22 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No

<img width="1440" alt="Screenshot 2024-08-12 at 10 12 04 PM"
src="https://github.com/user-attachments/assets/4ad51bb1-30f6-4d6c-aa60-f658848492b0">
<img width="1440" alt="Screenshot 2024-08-12 at 10 12 10 PM"
src="https://github.com/user-attachments/assets/03a3242b-302f-422c-b0df-c9da5f08d0c1">
<img width="1440" alt="Screenshot 2024-08-12 at 10 13 03 PM"
src="https://github.com/user-attachments/assets/00b71397-12a4-4471-b6a5-83642d7edc83">
<img width="1440" alt="Screenshot 2024-08-12 at 10 13 10 PM"
src="https://github.com/user-attachments/assets/2d15a205-95fa-4201-b1d1-45453064da8d">
<img width="1029" alt="Screenshot 2024-08-12 at 10 14 11 PM"
src="https://github.com/user-attachments/assets/e1778a43-9690-42ca-8adc-351d7ec95206">



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **New Features**
- Introduced a new function to manage local server setup using Docker
Compose, enhancing the setup process.
	- Added user prompts to guide decisions regarding local server setup.

- **Improvements**
- Implemented a retry mechanism to check Docker services, improving
reliability during setup.
- Enhanced error handling to provide clear feedback when setup
conditions are not met.

- **Refactor**
- Integrated new functionalities into the existing setup process for a
smoother user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-08-20 16:49:13 +05:30

281 lines
8.7 KiB
JavaScript

const { exec, execSync } = require("child_process");
const { existsSync, readFileSync, writeFileSync } = require("fs");
const path = require("path");
const prompt = require("prompt-sync")();
function isContainerRunning(containerName) {
try {
const output = execSync(
`docker ps --filter "name=^/${containerName}$" --format '{{.Names}}'`,
);
return output.length > 0;
} catch (error) {
return false;
}
}
// Helper function to execute commands and return a Promise
async function execCommand(command, options) {
return new Promise((resolve, reject) => {
exec(command, options, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve({ stdout, stderr });
}
});
});
}
function checkDockerCompose() {
try {
execSync("docker-compose --version", { stdio: "ignore" });
return true;
} catch (error) {
console.error(
"ERROR: docker-compose is not installed. Please install Docker Compose.",
);
process.exit(1);
}
}
async function runLocalServer() {
try {
let user_input = prompt(
`Do you wish to continue without setting up the local server with docker? (yes/no): `,
);
user_input = (user_input || "").trim().toLowerCase();
if (user_input === "yes" || user_input === "y") {
console.log(
"INFO",
"Continuing without setting up local backend docker based server.",
);
} else {
// Adjust the path to point to the correct directory
const dockerDir = path.join(__dirname, "../../../../deploy/docker");
if (!existsSync(dockerDir)) {
console.error(`ERROR: Directory ${dockerDir} does not exist.`);
process.exit(1); // Exit if the directory is missing
}
await checkDockerCompose(); // Ensure Docker Compose is available
console.log("INFO: Starting local server using Docker Compose...");
execSync(`cd ${dockerDir} && pwd && docker-compose up -d`, {
stdio: "inherit",
});
// Wait for the services to be fully up and running
let servicesRunning = false;
const maxRetries = 30;
const retryInterval = 7000; // 7 seconds
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const { stdout } = await execCommand("docker-compose ps", {
cwd: dockerDir,
});
if (stdout.includes("Up")) {
servicesRunning = true;
break;
}
} catch (error) {
console.error("ERROR: Error checking service status:", error.message);
process.exit(1); // Exit if checking service status fails
}
console.log(
`INFO: Waiting for services to be fully up and running... (attempt ${attempt}/${maxRetries})`,
);
await new Promise((resolve) => setTimeout(resolve, retryInterval));
}
if (servicesRunning) {
console.log("INFO: Local server is up and running.");
return true;
} else {
console.error(
"ERROR: Services did not become available within the expected time.",
);
process.exit(1); // Exit if services are not up
}
}
} catch (error) {
console.error("ERROR: Error starting local server:", error.message);
process.exit(1); // Exit if starting local server fails
}
}
function ensureTEDIsRunning() {
// Check if TED is running. If not, then ask user if they wish to pull and run the TED container
const isTedRunning = isContainerRunning("ted");
if (isTedRunning) {
console.log("INFO", "TED (TestEventDriver) is already running");
} else {
try {
let user_input = prompt(
"TED (TestEventDriver) is not running. Do you want to pull & run the latest Docker container for TED (TestEventDriver)? (yes/no): ",
);
user_input = user_input.trim().toLowerCase();
switch (user_input) {
case "yes":
case "y":
console.log(
"INFO",
"Running the Docker container for TED (TestEventDriver)",
);
try {
execSync(
"docker run --name ted --rm -d --pull always -p 2022:22 -p 5001:5001 -p 3306:3306 -p 28017:27017 -p 5433:5432 -p 25:25 -p 4200:4200 appsmith/test-event-driver",
{ stdio: "inherit" },
);
console.log(
"INFO",
"Please check https://github.com/appsmithorg/TestEventDriver for more details and functionalities of TED",
);
} catch (error) {
console.error("ERROR", `Error installing TED: ${error.message}`);
}
break;
case "no":
case "n":
console.log("INFO", "Proceeding without TED");
break;
default:
console.log("ERROR", "Invalid input. Please enter yes or no.");
process.exit(1);
}
} catch (error) {
console.error("ERROR", `Error: ${error.message}`);
process.exit(1);
}
}
}
async function checkIfAppsmithIsRunning(baseUrl) {
// Check if appsmith is running. If it's not running, check if we want the user to continue without it.
let isDevAppsmithAccessible;
try {
const response = await fetch(baseUrl);
isDevAppsmithAccessible = response.ok;
} catch (error) {
console.error(
"ERROR",
`Error checking availability of dev.appsmith.com: ${error.message}`,
);
isDevAppsmithAccessible = false;
}
if (!isDevAppsmithAccessible) {
let user_input = prompt(
`https://dev.appsmith.com is not accessible. Do you wish to continue without setting it up? (yes/no): `,
);
user_input = user_input.trim().toLowerCase();
switch (user_input) {
case "yes":
case "y":
console.log("INFO", "Continuing without setting up dev.appsmith.com");
break;
case "no":
case "n":
process.exit(1);
default:
console.log("ERROR", "Invalid input. Please enter yes or no.");
process.exit(1);
}
}
}
function getBaseUrl(repoRoot) {
try {
const cypressConfig = readFileSync(`${repoRoot}/cypress.config.ts`, "utf8");
const baseUrlMatch = cypressConfig.match(/baseUrl\s*:\s*"([^"]+)"/);
if (baseUrlMatch) {
baseUrl = baseUrlMatch[1];
console.log(
"INFO",
`Base url is ${baseUrl}. Please verify if it is correct. If not, please update it in cypress.config.ts file.`,
);
return baseUrl;
} else {
console.error(
"ERROR",
"Base url not found in cypress.config.ts. Please configure `baseUrl` property in cypress.config.ts file.",
);
process.exit(1);
}
} catch (err) {
if (err.code === "ENOENT") {
console.error("ERROR", "cypress.config.ts file not found");
} else {
console.error("ERROR", "Error reading cypress.config.ts file:", err);
}
process.exit(1);
}
}
function ensureCypressEnvFileExists(repoRoot) {
// Check if cypress.env.json file exists. If not, create it.
const filePath = `${repoRoot}/cypress.env.json`;
if (!existsSync(filePath)) {
const testEnvData = {
USERNAME: "testUser@test.com",
PASSWORD: "testPass",
TESTUSERNAME1: "viewerappsmith@test.com",
TESTPASSWORD1: "viewerPass",
TESTUSERNAME2: "developerappsmith@test.com",
TESTPASSWORD2: "developerPass",
};
writeFileSync(filePath, JSON.stringify(testEnvData, null, 2));
console.log("INFO", `${repoRoot}/cypress.env.json file created`);
} else {
console.log("INFO", `${repoRoot}/cypress.env.json file already exists`);
}
}
async function setupCypress() {
// Get the baseUrl from cypress.config.ts file
let repoRoot = path.join(__dirname, "..", "..");
let baseUrl = getBaseUrl(repoRoot);
await runLocalServer();
await checkIfAppsmithIsRunning(baseUrl);
// Install Cypress using yarn install on the app/client repository
console.log("INFO", "Installing Cypress..");
try {
execSync("yarn install", { cwd: `${repoRoot}` });
} catch (error) {
console.error("ERROR", `Error installing Cypress: ${error.message}`);
}
ensureCypressEnvFileExists(repoRoot);
console.log(
"INFO",
"Please add APPSMITH_GIT_ROOT=./container-volumes/git-storage into server-side .env for running Git cases locally along with the server.",
);
ensureTEDIsRunning();
console.log(
"INFO",
"Please start cypress using the command: npx cypress open",
);
console.log(
"INFO",
`In order to run single spec, please use the command: cd ${repoRoot} && npx cypress run --spec <specpath> --browser chrome`,
);
console.log(
"INFO",
"For more details check https://github.com/appsmithorg/appsmith/blob/master/contributions/ClientSetup.md#integration-tests",
);
}
async function main() {
await setupCypress();
process.exit(0);
}
main();