From b8b7da0004cbe3021cd89cfce4e35af4bfd85a9a Mon Sep 17 00:00:00 2001 From: Flora Thiebaut Date: Fri, 23 Feb 2024 12:45:46 +0100 Subject: [PATCH] fix(cypress): make session actions more robust (#3513) --- cypress-tests/config.ts | 1 + .../cypress/e2e/rstudioSession.cy.ts | 1 + cypress-tests/cypress/e2e/useSession.cy.ts | 67 ++++++++++--------- .../cypress/support/commands/sessions.ts | 59 +++++++++------- 4 files changed, 74 insertions(+), 54 deletions(-) diff --git a/cypress-tests/config.ts b/cypress-tests/config.ts index 7ed77bf7bf..bfdea3399e 100644 --- a/cypress-tests/config.ts +++ b/cypress-tests/config.ts @@ -5,4 +5,5 @@ export const TIMEOUTS = { standard: 20_000, long: 60_000, vlong: 180_000, + vvlong: 300_000, }; diff --git a/cypress-tests/cypress/e2e/rstudioSession.cy.ts b/cypress-tests/cypress/e2e/rstudioSession.cy.ts index 341c5a7dbc..00a5fa12b9 100644 --- a/cypress-tests/cypress/e2e/rstudioSession.cy.ts +++ b/cypress-tests/cypress/e2e/rstudioSession.cy.ts @@ -63,6 +63,7 @@ describe("Basic rstudio functionality", () => { { defaultCommandTimeout: TIMEOUTS.long }, () => { // Waits for the image to build and launches a session + // Note: rstudio image may take a while to build cy.startSession(projectIdentifier); // Opens the session in an iframe diff --git a/cypress-tests/cypress/e2e/useSession.cy.ts b/cypress-tests/cypress/e2e/useSession.cy.ts index 6348d10584..95e129199a 100644 --- a/cypress-tests/cypress/e2e/useSession.cy.ts +++ b/cypress-tests/cypress/e2e/useSession.cy.ts @@ -9,11 +9,6 @@ const projectTestConfig = { shouldCreateProject: true, projectName: generatorProjectName("useSession"), }; -const workflowNameSalt = uuidv4().substring(0, 4); -const workflow = { - name: `dummyworkflow-${workflowNameSalt}`, - output: `o${workflowNameSalt}.txt`, // ? Keep the name short or it won't show up entirely in the file browser -}; // ? Modify the config -- useful for debugging // projectTestConfig.shouldCreateProject = false; @@ -66,6 +61,13 @@ describe("Basic public project functionality", () => { it("Start a new session on the project and interact with the terminal.", () => { cy.stopAllSessionsForProject(projectIdentifier); + // Define workflow object + const workflowNameSalt = uuidv4().substring(0, 4); + const workflow = { + name: `dummyworkflow-${workflowNameSalt}`, + output: `o${workflowNameSalt}.txt`, // ? Keep the name short or it won't show up entirely in the file browser + }; + // Start a session with options let serversInvoked = false; cy.intercept("/ui-server/api/notebooks/servers*", (req) => { @@ -83,9 +85,7 @@ describe("Basic public project functionality", () => { cy.get(".renku-container") .contains("A session gives you an environment") .should("exist"); - cy.get(".renku-container .badge.bg-success", { timeout: TIMEOUTS.vlong }) - .contains("available") - .should("exist"); + cy.waitForImageToBuild(); cy.get(".renku-container button.btn-secondary", { timeout: TIMEOUTS.long }) .contains("Start Session") .should("exist") @@ -142,7 +142,9 @@ describe("Basic public project functionality", () => { .contains("Saving Session", { timeout: TIMEOUTS.long }) .should("be.visible"); cy.get(".modal") - .contains("Your session has been saved successfully", { timeout: TIMEOUTS.long }) + .contains("Your session has been saved successfully", { + timeout: TIMEOUTS.long, + }) .should("be.visible"); cy.get(".modal .btn-close").should("be.visible").click(); @@ -191,13 +193,14 @@ describe("Basic public project functionality", () => { .first() .click(); cy.get(".alert-info").contains("As an anonymous user").should("be.visible"); + cy.waitForImageToBuild(); // Quickstart a session and check it spins up cy.getDataCy("go-back-button").click(); cy.quickstartSession(); // Stop the session -- mind that anonymous users cannot pause sessions - cy.deleteSession(true); + cy.deleteSession({ fromSessionPage: true }); }); it("Start a new session on a project without permissions.", () => { @@ -213,6 +216,7 @@ describe("Basic public project functionality", () => { cy.get(".alert-info") .contains("You have limited permissions for this project") .should("be.visible"); + cy.waitForImageToBuild(); // Quickstart a session and check it spins up cy.getDataCy("go-back-button").click(); @@ -248,7 +252,7 @@ describe("Basic public project functionality", () => { if (storages.find(({ storage }) => storage.name === "data_s3")) { return; } - + cy.getDataCy("cloud-storage-section") .find("button") .contains("Add Cloud Storage") @@ -257,7 +261,7 @@ describe("Basic public project functionality", () => { cy.getDataCy("cloud-storage-edit-header") .contains("Add Cloud Storage") .should("be.visible"); - + cy.getDataCy("cloud-storage-edit-schema") .contains("s3") .should("be.visible") @@ -269,7 +273,7 @@ describe("Basic public project functionality", () => { cy.getDataCy("cloud-storage-edit-next-button") .should("be.visible") .click(); - + cy.getDataCy("cloud-storage-edit-options").should("be.visible"); cy.get("#sourcePath").should("have.value", "").type("giab"); cy.get("#endpoint") @@ -278,19 +282,19 @@ describe("Basic public project functionality", () => { cy.getDataCy("cloud-storage-edit-next-button") .should("be.visible") .click(); - + cy.getDataCy("cloud-storage-edit-mount").should("be.visible"); cy.get("#name").should("have.value", "").type("data_s3"); cy.get("#mountPoint") .should("have.value", "external_storage/data_s3") .type("{selectAll}data_s3"); cy.get("#readOnly").should("not.be.checked").check(); - + cy.getDataCy("cloud-storage-edit-update-button") .should("be.visible") .contains("Add") .click(); - + cy.getDataCy("cloud-storage-edit-body").contains( "storage data_s3 has been succesfully added" ); @@ -298,23 +302,23 @@ describe("Basic public project functionality", () => { .should("be.visible") .click(); }); - + cy.getDataCy("more-menu").should("be.visible").click(); cy.getProjectPageLink(projectIdentifier, "sessions/new") .should("be.visible") .first() .click(); - + // Wait for the image to be ready and start a session cy.get(".renku-container") .contains("A session gives you an environment") .should("exist"); - cy.get(".renku-container .badge.bg-success", { timeout: TIMEOUTS.vlong }) - .contains("available") - .should("exist"); + cy.waitForImageToBuild(); cy.getDataCy("cloud-storage-item").contains("data_s3").should("exist"); cy.get("#cloud-storage-data_s3-active").should("be.checked"); - cy.get(".renku-container button.btn-secondary", { timeout: TIMEOUTS.long }) + cy.get(".renku-container button.btn-secondary", { + timeout: TIMEOUTS.long, + }) .contains("Start Session") .should("exist") .click(); @@ -325,10 +329,10 @@ describe("Basic public project functionality", () => { cy.get(".progress-box .progress-title") .contains("Starting Session") .should("exist"); - cy.get(".progress-box .progress-title", { timeout: TIMEOUTS.vlong }).should( - "not.exist" - ); - + cy.get(".progress-box .progress-title", { + timeout: TIMEOUTS.vlong, + }).should("not.exist"); + // Verify that the S3 data is mounted cy.getIframe("iframe#session-iframe").within(() => { cy.get(".jp-DirListing-content", { timeout: TIMEOUTS.long }).should( @@ -338,21 +342,22 @@ describe("Basic public project functionality", () => { .contains("data_s3") .should("be.visible") .dblclick(); - + cy.get(".jp-DirListing-item") .contains("README.s3_structure") .should("be.visible") .dblclick(); - - cy.get(".jp-FileEditor", { timeout: TIMEOUTS.long }).should("be.visible"); + + cy.get(".jp-FileEditor", { timeout: TIMEOUTS.long }).should( + "be.visible" + ); cy.get(".jp-FileEditor") .contains("The GIAB s3 bucket and URLs") .should("be.visible"); }); - + cy.pauseSession(); cy.deleteSession(); }); }); - }); diff --git a/cypress-tests/cypress/support/commands/sessions.ts b/cypress-tests/cypress/support/commands/sessions.ts index 14744147af..851b923e1d 100644 --- a/cypress-tests/cypress/support/commands/sessions.ts +++ b/cypress-tests/cypress/support/commands/sessions.ts @@ -5,7 +5,8 @@ import type { ProjectIdentifier } from "./projects"; function startSession(identifier: ProjectIdentifier) { const id = fullProjectIdentifier(identifier); cy.visit(`/projects/${id.namespace}/${id.name}/sessions/new`); - cy.get(".renku-container button.btn-secondary", { timeout: TIMEOUTS.vlong }) + cy.waitForImageToBuild(); + cy.get(".renku-container button.btn-secondary", { timeout: TIMEOUTS.long }) .contains("Start Session") .should("be.visible") .should("be.enabled") @@ -19,13 +20,13 @@ function startSession(identifier: ProjectIdentifier) { }).should("not.exist"); } -function waitForImageToBuild(identifier: ProjectIdentifier) { - const id = fullProjectIdentifier(identifier); - cy.visit(`/projects/${id.namespace}/${id.name}/sessions/new`); - cy.get(".renku-container button.btn-secondary", { timeout: TIMEOUTS.vlong }) - .contains("Start Session") +function waitForImageToBuild() { + cy.get(".renku-container .badge.bg-success", { timeout: TIMEOUTS.vvlong }) + .contains("available") .should("be.visible") - .should("be.enabled"); + .parent() + .contains("Docker image") + .should("be.visible"); } function stopAllSessionsForProject( @@ -53,7 +54,7 @@ function stopAllSessionsForProject( .should("be.visible") .click(); cy.getDataCy("session-container") - .find(`[data-cy=delete-session-button]`) + .find("[data-cy=delete-session-button]") .first() .should("be.visible") .click(); @@ -63,10 +64,25 @@ function stopAllSessionsForProject( cy.contains("No currently running sessions.", { timeout: TIMEOUTS.vlong }); } -function deleteSession(fromSessionPage = false) { - if (!fromSessionPage) - cy.getDataCy("more-menu").first().should("be.visible").click(); - cy.getDataCy("delete-session-button").first().should("be.visible").click(); +function deleteSession(args?: { fromSessionPage?: boolean }) { + const fromSessionPage = args?.fromSessionPage ?? false; + + if (fromSessionPage) { + cy.getDataCy("delete-session-button").first().should("be.visible").click(); + } else { + cy.wait(TIMEOUTS.minimal); // eslint-disable-line cypress/no-unnecessary-waiting + cy.getDataCy("session-container") + .find("[data-cy=more-menu]") + .first() + .should("be.visible") + .click(); + cy.getDataCy("session-container") + .find("[data-cy=delete-session-button]") + .first() + .should("be.visible") + .click(); + } + cy.getDataCy("delete-session-modal-button").should("be.visible").click(); cy.getDataCy("stopping-btn").should("be.visible"); cy.get(".renku-container", { timeout: TIMEOUTS.vlong }) @@ -74,17 +90,14 @@ function deleteSession(fromSessionPage = false) { .should("be.visible"); } -function pauseSession(fromSessionPage = true) { - if (!fromSessionPage) - cy.getDataCy("more-menu").first().should("be.visible").click(); - cy.getDataCy("pause-session-button").should("be.visible").click(); +function pauseSession() { + cy.getDataCy("pause-session-button").first().should("be.visible").click(); cy.getDataCy("pause-session-modal-button").should("be.visible").click(); cy.get(`[data-cy="session-container"]`, { timeout: TIMEOUTS.long }) .should("be.visible") .contains("Paused"); } - function quickstartSession() { cy.get(".start-session-button").should("not.be.disabled").click(); cy.get(".progress-box .progress-title").should("exist"); @@ -118,12 +131,12 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { - startSession(identifier: ProjectIdentifier); - waitForImageToBuild(identifier: ProjectIdentifier); - deleteSession(fromSessionPage?: boolean); - pauseSession(fromSessionPage?: boolean); - quickstartSession(); - stopAllSessionsForProject(identifier: ProjectIdentifier, loadPage?: boolean); + startSession: typeof startSession; + waitForImageToBuild: typeof waitForImageToBuild; + deleteSession: typeof deleteSession; + pauseSession: typeof pauseSession; + quickstartSession: typeof quickstartSession; + stopAllSessionsForProject: typeof stopAllSessionsForProject; } } }