Resolve "Implement user journey tests"

Closes #26 (closed)

Merge request reports

Loading

cypress/e2e/auth.cy.ts

0 → 100644
+131 −0
Changes for cypress/e2e/auth.cy.ts: 131 added lines, 0 removed lines.
Original line number Diff line number Diff line
describe("user authentication", () => {
  beforeEach(() => {
    cy.visit(window.location.origin);
  });

  it("should block unauthenticated user from entering a protected route", () => {
    cy.fixture("routes.json").then((data) => {
      cy.log(data.routes);
      for (let route of data.routes) {
        cy.intercept(route);
        cy.log(route);
        cy.visit(route);
        cy.location("pathname").should("equal", "/");
      }
    });
  });
  it("should display login errors when nothing is typed", () => {
    cy.get(":nth-child(1) > .nav-link").click();
    cy.location("pathname").should("equal", "/services");
    cy.get(".btn").should("contain", "Sign In").click();
    cy.origin(Cypress.env("authURL"), () => {
      cy.location("pathname").should("equal", Cypress.env("authRealm"));
      cy.get("#kc-login").click();
      cy.get("#input-error")
        .should("be.visible")
        .and("contain", "Invalid username or password.");
      cy.get('[aria-invalid="true"]').should("have.length", 2); //the 2 input fields should have selector aria-invalid = 'true'
    });
  });
  it("should display login errors when credentials are wrong", () => {
    cy.get(":nth-child(1) > .nav-link").click();
    cy.location("pathname").should("equal", "/services");
    cy.get(".btn").should("contain", "Sign In").click();
    cy.origin(Cypress.env("authURL"), () => {
      cy.location("pathname").should("equal", Cypress.env("authRealm"));

      cy.get("#username").type("test").blur();
      cy.get("#password").type("test").blur();
      cy.get("#kc-login").click();
      cy.get("#input-error")
        .should("be.visible")
        .and("contain", "Invalid username or password.");
      cy.get('[aria-invalid="true"]').should("have.length", 2); //the 2 input fields should have selector aria-invalid = 'true'
    });
  });
  it("should login, logout", () => {
    cy.loginPath(
      ":nth-child(1) > .nav-link",
      "/services"
    );
    cy.get("#navbarDropdown2").click();
    cy.window().then((win) => {
      expect(win.localStorage.getItem("access_token")).to.exist;
    });

    cy.get(
      ":nth-child(2) > .dropdown > .dropdown-menu > :nth-child(8)"
    ).click();
    cy.location("pathname").should("equal", "/");
  });
  it("forget password should work", () => {
    cy.get(":nth-child(1) > .nav-link").click();
    cy.location("pathname").should("equal", "/services");
    cy.get(".btn").should("contain", "Sign In").click();
    cy.origin(Cypress.env("authURL"), () => {
      cy.location("pathname").should("equal", Cypress.env("authRealm"));
      cy.get(":nth-child(2) > span > a").click();
      cy.log(Cypress.env("resetCredentials"));
      cy.location("pathname").should("equal", Cypress.env("resetCredentials"));
      cy.get("#username").should("exist");
    });
  });
  context("register form", () => {
    beforeEach(() => {
      cy.get(":nth-child(1) > .nav-link").click();
      cy.location("pathname").should("equal", "/services");
      cy.get(".btn").should("contain", "Sign In").click();
      cy.origin(Cypress.env("authURL"), () => {
        cy.location("pathname").should("equal", Cypress.env("authRealm"));
        cy.get("#kc-registration > span > a").click();
        cy.location("pathname").should("equal", Cypress.env("registration"));
      });
    });
    it("register form should not be empty when submitted", () => {
      cy.origin(Cypress.env("authURL") + Cypress.env("registration"), () => {
        cy.get(".pf-c-button").click();
        cy.get("#input-error-firstname")
          .should("exist")
          .and("contain", "Please specify first name.");
        cy.get("#input-error-lastname")
          .should("exist")
          .and("contain", "Please specify last name.");
        cy.get("#input-error-email")
          .should("exist")
          .and("contain", "Please specify email.");
        cy.get("#input-error-username")
          .should("exist")
          .and("contain", "Please specify username.");
        cy.get("#input-error-password")
          .should("exist")
          .and("contain", "Please specify password.");
      });
    });
    it("invalid email", () => {
      cy.origin(Cypress.env("authURL") + Cypress.env("registration"), () => {
        cy.get("#email").type("email");
        cy.get(".pf-c-button").click();
        cy.get("#input-error-email")
          .should("exist")
          .and("contain", "Invalid email address.");
        cy.get("#email").type("@example.com");
        cy.get(".pf-c-button").click();
        cy.get("#input-error-email").should("not.exist");
      });
    });
    it("confirm password field should be the same with password", () => {
      cy.origin(Cypress.env("authURL") + Cypress.env("registration"), () => {
        cy.get("#password").type("123");
        cy.get("#password-confirm").type("1234");
        cy.get(".pf-c-button").click();
        cy.get("#input-error-password-confirm")
          .should("exist")
          .and("contain", "Password confirmation doesn't match.");
        cy.get("#password").type("123");
        cy.get("#password-confirm").type("123");
        cy.get(".pf-c-button").click();
        cy.get("#input-error-password-confirm").should("not.exist");
      });
    });
  });
});
+41 −0
Changes for cypress/e2e/user-journeys-logged-in.cy.ts: 41 added lines, 0 removed lines.
Original line number Diff line number Diff line
describe('logged user journeys', () => {
  beforeEach(() => {
      cy.visit(window.location.origin);        
      
  });
  afterEach(() => {
    //log user out after each test
    cy.get('#navbarDropdown2').click();
    cy.get(':nth-child(2) > .dropdown > .dropdown-menu > :nth-child(8)').click();
    cy.location("pathname").should("equal", "/");
  });


  it('logs in through services', () => {
    cy.loginPath('[data-cy="servicesPortal"]', '/services');
    cy.location("pathname").should("equal", "/services/services_marketplace");
    cy.navigateThroughDropdowns("Services Marketplace");
    
  });

  it('logs in through resources', () => {
    cy.loginPath('[data-cy="resourcesPortal"]', '/resources');
    cy.location("pathname").should("equal", "/resources/resource_inventory");
    cy.navigateThroughDropdowns("Resource Inventory");

  });


  it('logs in through testing', () => {
    cy.loginPath('[data-cy="testingPortal"]', '/testing');
    cy.location("pathname").should("equal", "/testing/service_tests");
    cy.navigateThroughDropdowns("Test Instances");
  });

  
  it('logs in through products', () => {
    cy.loginPath('[data-cy="productsPortal"]', '/products');
    cy.location("pathname").should("equal", "/products/marketplace");
    cy.navigateThroughDropdowns("");
  });
})
 No newline at end of file
+133 −0
Changes for cypress/e2e/user-journeys.cy.ts: 133 added lines, 0 removed lines.
Original line number Diff line number Diff line
describe('user journey', () => {
  
  beforeEach(() => {
    cy.visit(window.location.origin);
  })
  
  context('user-not-logged-in', () => {

    it('checks metrics portal', () => {
      cy.navigateToMetrics('[data-cy="metricsPortal"]');
    });

    it('checks services portal', () => {
      cy.navigateToServices('[data-cy="servicesPortal"]');
    });
    it('checks services portal via nav-bar anchor', () => {
      cy.navigateToServices(':nth-child(1) > .nav-link');
    });
    it('checks services through metrics portal', () => {
      cy.navigateToMetrics('[data-cy="metricsPortal"]');
      cy.navigateToServices('[data-cy="servicesPortal"]');
    });

    it('checks resources portal', () => {
      //cy.get(':nth-child(2) > .nav-link').click();
      cy.navigateToResources('[data-cy="resourcesPortal"]');
    });
    it('checks resources portal via nav-bar anchor', () => {
      cy.navigateToResources(':nth-child(2) > .nav-link');
    });
    it('checks resources through metrics portal', () => {
      cy.navigateToMetrics('[data-cy="metricsPortal"]');
      cy.navigateToResources('[data-cy="resourcesPortal"]');
    });

    it('checks testing portal', () => {
      cy.navigateToTesting('[data-cy="testingPortal"]');
    });
    it('checks testing portal via nav-bar anchor', () => {
      cy.navigateToTesting(':nth-child(4) > .nav-link')
    });

    it('checks products portal', () => {
      cy.navigateToProducts('[data-cy="productsPortal"]');
    });
    it('checks products portal via nav-bar anchor', () => {
      cy.navigateToProducts(':nth-child(5) > .nav-link');
    });
  });


  context('<who we are> links', () => {

    beforeEach(() => {
      cy.get('[id="contact"]').scrollIntoView().should('be.visible');
    });

    it('checks OpenSlice version', () => {
      cy.get('[data-cy="osl"]').click();
      cy.origin("https://labs.etsi.org/", () => {
        cy.location("pathname").should("contain", "releases");
      });
    });

    it('checks OpenSlice wiki via portal card', () => {
      cy.get('[data-cy="wiki"]').click();
      cy.origin("https://osl.etsi.org/", () => {
        cy.location("pathname").should("equal", "/documentation/latest/");
      });
    });

    it('checks OpenSlice by ETSI Portal Link', () => {
      cy.get(':nth-child(1) > .osl-anchor').click();
      cy.location("pathname").should("equal", "/");
      
    });
    it("checks OpenSlice by ETSI wiki", () => {
      cy.get(':nth-child(2) > .osl-anchor').click();
      cy.origin('https://osl.etsi.org/', () => {
        cy.location("pathname").should("equal", "/documentation/latest/");

      });
    });

    it('checks ETSI SDG for OpenSlice link', () => {
      cy.get(':nth-child(3) > .osl-anchor').click();
      cy.origin('https://osl.etsi.org/', () => {
        cy.location("pathname").should("equal", "/");

      });
    });

    it('checks linkedin link', () => {
      cy.get(':nth-child(1) > a > .button-outline').click();
      cy.origin('https://www.linkedin.com/company/openslice', ()=> {
        cy.location("pathname").should("equal", "/company/openslice");
      });
    });

    it('checks slack link', () => {
      cy.get(':nth-child(2) > a > .button-outline').click();
      cy.origin('https://openslice.slack.com/', () => {
        cy.location("pathname").should("contain", "/join");

      });
    });

    it('checks twitter link', () => {
      cy.get(':nth-child(3) > a > .button-outline').click();
      cy.origin('https://x.com/OpensliceOSS', () => {
        cy.location("pathname").should("satisfy", (url) => {
          return url === '/OpensliceOSS' || url === '/i/flow/login';
        })
      });
    });

    it('checks ecosystem link', () => {
      cy.get('.col-md-10 > :nth-child(1) > a').click();
      cy.origin('https://osl.etsi.org/ecosystem/', () => {
        cy.location("pathname").should("equal", "/ecosystem/");
      });
    });
    
    it('checks osl etsi link', () => {
      cy.get('[target="_blank"]').click();
      cy.location("pathname").should("equal", "/")
      
    })
    
  });
  
  
})
 No newline at end of file
+276 −0
Changes for cypress/support/commands.ts: 276 added lines, 0 removed lines.
Original line number Diff line number Diff line
declare namespace Cypress {
  interface Chainable {
    navigateToMetrics(selector): Chainable<any>;
    navigateToServices(selector): Chainable<any>;
    navigateToResources(selector): Chainable<any>;
    navigateToTesting(selector): Chainable<any>;
    navigateToProducts(selector): Chainable<any>;
    authUser(): Chainable<any>;
    loginPath(selector, path): Chainable<any>;
    navigateThroughDropdowns(headerTitle): Chainable<any>;
    // manageEntities(selector, numOfItems): Chainable<any>;
    checkProtectedRoutes(): Chainable<any>;
  }
}

Cypress.Commands.add("navigateToMetrics", (selector) => {
  cy.get(selector).click();
  cy.location("pathname").should("equal", "/metrics");
  
})

Cypress.Commands.add("navigateToServices", (selector) => {
  cy.get(selector).click();
  cy.location("pathname").should("equal", "/services");
  cy.get(".btn").should("contain", "Sign In");
  cy.get(":nth-child(2) > .nav-item > .nav-link").should("exist");
  //service marketplace button should exist and should redirect to /services_marketplace
  cy.get(".mr-auto > .nav-item > .nav-link").click();
  cy.get(".catalog-tree-header").should("exist");
  //sign in button should exist
  cy.get(":nth-child(2) > .nav-item > .nav-link").click();
  //sign in button should redirect to auth form
  cy.origin(Cypress.env("authURL"), () => {
    cy.location("pathname").should("equal", Cypress.env("authRealm"));
  });
});

Cypress.Commands.add("navigateToResources", (selector) => {
  cy.get(selector).click();
  cy.location("pathname").should("equal", "/resources");
  //sign in button should exist
  cy.get(".btn").should("exist").click();
  //sign in button should redirect to auth form
  cy.origin(Cypress.env("authURL"), () => {
    cy.location("pathname").should("equal", Cypress.env("authRealm"));
  });
});

Cypress.Commands.add("navigateToTesting", (selector) => {
  cy.get(selector).click();
  cy.location("pathname").should("equal", "/testing");
  //sign in button should exist
  cy.get(".btn").should("exist").click();
  //sign in button should redirect to auth form
  cy.origin(Cypress.env("authURL"), () => {
    cy.location("pathname").should("equal", Cypress.env("authRealm"));
  });
});

Cypress.Commands.add("navigateToProducts", (selector) => {
  cy.get(selector).click();
  cy.location("pathname").should("equal", "/products");
  cy.get(".btn").should("contain", "Sign In");
  cy.get(":nth-child(2) > .nav-item > .nav-link").should("exist");
  //marketplace button should exist and should redirect to products/marketplace
  cy.get(".mr-auto > .nav-item > .nav-link").click();
  cy.get(".catalog-tree-header").should("exist");
  cy.get(".mb-0").should("contain", "Product Catalog Explorer");
  //sign in button should exist
  cy.get(":nth-child(2) > .nav-item > .nav-link").click();
  //sign in button should redirect to auth form
  cy.origin(Cypress.env("authURL"), () => {
    cy.location("pathname").should("equal", Cypress.env("authRealm"));
  });
});

Cypress.Commands.add("authUser", () => {
  cy.get(".btn").should("contain", "Sign In").click();
  cy.origin(Cypress.env("authURL"), () => {
    cy.location("pathname").should("equal", Cypress.env("authRealm"));
    cy.get("#username").type(Cypress.env("username"));
    cy.get("#password").type(Cypress.env("password"));
    cy.get("#kc-login").click();
  });
  cy.location("pathname").should("equal", "/redirect");
});

Cypress.Commands.add("loginPath", (selector, path) => {
  cy.get(selector).click();
  cy.intercept(path);
  cy.location("pathname").should("equal", path);
  cy.authUser();
});

//navigate through all the dropdowns, all the dropdown items and nested items
Cypress.Commands.add("navigateThroughDropdowns", (headerTitle: string) => {
  cy.get('[data-cy^="dropdown-"]').its('length').then((dropdownCount: number) => {

    const ensureDropdownOpen = (dropdownSelector: string, itemPattern: string) => {
      cy.get(dropdownSelector).then(($dropdown) => {
        const isOpen = $dropdown.attr('aria-expanded') === 'true' || $dropdown.hasClass('show');
        if (!isOpen) {
          cy.wrap($dropdown).should('exist').click();
        }
      });

      //wait until items exist & are visible
      cy.get(itemPattern, { timeout: 10000 })
        .should('have.length.greaterThan', 0)
        .filter(':visible')
        .should('have.length.greaterThan', 0);
    };

    const processDropdown = (dropdownIndex: number) => {
      if (dropdownIndex >= dropdownCount) return;

      const dropdownSelector = `[data-cy="dropdown-${dropdownIndex}"]`;
      const itemPattern = `[data-cy^="dropdown-${dropdownIndex}-item-"]`;

      ensureDropdownOpen(dropdownSelector, itemPattern);

      cy.get(itemPattern).its('length').then((itemCount: number) => {
        if (itemCount === 0) {
          cy.log(`Dropdown ${dropdownIndex} had no items, skipping.`);
          processDropdown(dropdownIndex + 1);
          return;
        }

        const processItem = (itemIndex: number) => {
          if (itemIndex >= itemCount) {
            cy.log(`Finished dropdown ${dropdownIndex}`);
            processDropdown(dropdownIndex + 1);
            return;
          }

          const itemSelector = `[data-cy="dropdown-${dropdownIndex}-item-${itemIndex}"]`;

          ensureDropdownOpen(dropdownSelector, itemPattern);

          cy.get(itemSelector).filter(':visible').invoke('text').then((rawText) => {
            const itemText = rawText.trim();

            cy.location('pathname').then((initialPath) => {
              cy.log(`Clicking ${itemSelector}`);
              cy.get(itemSelector).filter(':visible').should('exist').click();

              cy.get('body').then(() => {
                cy.location('pathname').then((newPath) => {
                  // if a redirect doesn't happen when an item is clicked, it has nested subitems
                  if (newPath === initialPath && itemText !== headerTitle) {
                    const nestedPattern = `[data-cy^="dropdown-${dropdownIndex}-item-${itemIndex}-subitem-"]`;
                    cy.get(nestedPattern).its('length').then((nestedCount: number) => {
                      if (nestedCount === 0) {
                        processItem(itemIndex + 1);
                        return;
                      }

                      const processNested = (nestedIndex: number) => {
                        if (nestedIndex >= nestedCount) {
                          processItem(itemIndex + 1);
                          return;
                        }

                        const nestedSelector = `[data-cy="dropdown-${dropdownIndex}-item-${itemIndex}-subitem-${nestedIndex}"]`;
                        cy.get(nestedSelector).filter(':visible').should('exist').click();

                        // a dialog may exist in place of a nested subitem
                        cy.get('body').then(($bodyAfterNested) => {
                          const dialogExists =
                            $bodyAfterNested.find('mat-dialog').length > 0 ||
                            $bodyAfterNested.find('#mat-dialog-0').length > 0;

                          if (dialogExists) {
                            cy.get('#mat-dialog-0').should('be.visible');
                            cy.go('back');
                            cy.wait(200);
                            ensureDropdownOpen(dropdownSelector, itemPattern);
                            cy.get(itemSelector).filter(':visible').click();
                            processNested(nestedIndex + 1);
                          } else {
                            cy.location('pathname').then((afterNestedPath) => {
                              if (afterNestedPath !== initialPath) {
                                cy.location("pathname").should("not.equal", initialPath);
                                cy.go('back');
                                cy.wait(200);
                              }
                              ensureDropdownOpen(dropdownSelector, itemPattern);
                              cy.get(itemSelector).filter(':visible').click();
                              processNested(nestedIndex + 1);
                            });
                          }
                        });
                      };

                      processNested(0);
                    });
                  } else if (itemText === headerTitle) {
                    // if the redirect doesn't happen, then we are at the item of the initial route
                    cy.location("pathname").should("equal", initialPath);
                    processItem(itemIndex + 1);
                  } else {
                    cy.location("pathname").should("not.equal", initialPath);
                    cy.go('back');
                    cy.wait(200);
                    processItem(itemIndex + 1);
                  }
                });
              });
            });
          });
        };

        processItem(0);
      });
    };

    processDropdown(0);
  });
});

Cypress.Commands.add("checkProtectedRoutes", () => {
  for (let route of Cypress.env("protectedRoutes")) {
    cy.intercept(route);
    cy.log(route);
    cy.visit(route);
    cy.location("pathname").should("equal", "/");
  }
});


// Cypress.Commands.add("manageEntities", (selector, numOfItems) => {
//   function clickItem(index) {
//     if (index > numOfItems+1) return;

//     cy.get(selector).click(); // open navbar dropdown

//     cy.get('body').then(($body) => {
//       const menuItem = $body.find(
//         `.nav-item.open > #navbarDropdown > .dropdown-menu > :nth-child(${index}) > .dropdown-item`
//       );

//       if (menuItem.length === 0) {
//         cy.log(`Skipping missing item at index ${index}`);
//         cy.get('body').click();
//         clickItem(index + 1);
//         return;
//       }

//       cy.wrap(menuItem).click();

//       cy.get('body').then(($page) => {
//         const hasButton = $page.find('.text-md-right > .btn, .jumbotron-heading > .btn').length > 0;
//         const hasHeader = $page.find('h2.shadowed').length > 0;

//         expect(
//           hasButton || hasHeader,
//           `Expected either a button or a header on page for item ${index}`
//         ).to.be.true;

//         if (hasButton) {
//           cy.get('.text-md-right > .btn, .jumbotron-heading > .btn')
//             .first()
//             .click();
//         } else {
//           cy.log(`Found header instead of button for item ${index}`);
//         }

//         cy.go('back').then(() => clickItem(index + 1));
//       });
//     });
//   }

//   clickItem(1);
// });

cypress/support/e2e.ts

0 → 100644
+17 −0
Changes for cypress/support/e2e.ts: 17 added lines, 0 removed lines.
Original line number Diff line number Diff line
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'
Loading
Loading