Skip to content

Commit

Permalink
cypress POC with github actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Yibaebi committed Feb 4, 2024
1 parent 9275f2e commit be21fdf
Show file tree
Hide file tree
Showing 32 changed files with 1,976 additions and 138 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/e2e-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: E2E Test
on:
push:
branches:
- cypress-experiment
- develop
- main
- master

jobs:
e2e-tests:
runs-on: [ubuntu-latest]
concurrency:
group: ${{ github.workflow }}-ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
deployments: write
name: Run E2E Tests
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'npm'
- run: npm ci
- name: Cypress run
uses: cypress-io/github-action@v5
env:
CYPRESS_TEST_USERNAME: ${{ secrets.CYPRESS_TEST_USERNAME}}
CYPRESS_TEST_PASSWORD: ${{ secrets.CYPRESS_TEST_PASSWORD }}
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_TEST_RECORD_ID}}
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_TEST_PROJECT_ID }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
start: npm run startprod
install: false
config: pageLoadTimeout=100000
wait-on: 'http://localhost:4200'
record: true
2 changes: 1 addition & 1 deletion app/components/auth-assets/index.hbs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<ImgLogo></ImgLogo>
<ImgLogo id='login-logo-img' />
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<LinkTo @route='authenticated.index'>
<img
data-test-img-logo
id='org-img-logo'
src={{if @isCollapsed this.faviconImage.src this.appLogoImage.src}}
alt={{this.whitelabel.name}}
/>
Expand Down
1 change: 1 addition & 0 deletions app/components/img-logo/index.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<div class='has-text-centered'>
<LinkTo @route='authenticated.index'>
<img
...attributes
data-test-img-logo
local-class='img-logo__img'
src={{this.whitelabel.logo}}
Expand Down
1 change: 1 addition & 0 deletions app/components/login-component/check/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
/>
<AkIconButton
data-test-login-check-button
id='login-username-check-icon'
class='button is-primary highlighted-button margin-l-h'
@type='submit'
{{on 'click' @check}}
Expand Down
45 changes: 24 additions & 21 deletions app/components/login-component/index.hbs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<div class="bg-color-container" local-class="container">
<div class="bg-color-container" local-class="inner-container">
<AuthAssets></AuthAssets>
<section local-class="login-container">
<h1 local-class="login-title">{{t "login"}}</h1>
<div local-class="action-container">
<div class='bg-color-container' local-class='container'>
<div class='bg-color-container' local-class='inner-container'>
<AuthAssets />
<section local-class='login-container'>
<h1 local-class='login-title' id='login-title' data-test-login-title>{{t
'login'
}}</h1>
<div local-class='action-container'>
{{#if this.isCheckDone}}
{{#if this.MFAEnabled}}
<LoginComponent::Mfa
Expand All @@ -13,8 +15,7 @@
@showSpinner={{this.loginTask.isRunning}}
@login={{this.login}}
data-test-login-mfa
>
</LoginComponent::Mfa>
/>
{{else}}

{{#if this.isSSOEnabled}}
Expand All @@ -28,8 +29,7 @@
@ssologin={{this.ssologin}}
@isEnforced={{this.isSSOEnforced}}
data-test-login-sso
>
</LoginComponent::Sso>
/>
{{else}}
<LoginComponent::Login
@username={{this.username}}
Expand All @@ -38,8 +38,7 @@
@showSpinner={{this.loginTask.isRunning}}
@login={{this.login}}
data-test-login-login
>
</LoginComponent::Login>
/>
{{/if}}

{{/if}}
Expand All @@ -49,18 +48,22 @@
@check={{this.verifySSO}}
@username={{this.username}}
data-test-login-check
>
</LoginComponent::Check>
/>
{{/if}}
</div>
{{#if this.showRegistrationLink}}
<div local-class="login-footer">
<p>{{t "dontHaveAccount"}}</p>
<a data-test-login-registration-link href="{{this.registrationLink}}" local-class="register-link">
{{t "register"}}
</a>
</div>
<div local-class='login-footer'>
<p>{{t 'dontHaveAccount'}}</p>
<a
data-test-login-registration-link
id='login-registration-link'
href='{{this.registrationLink}}'
local-class='register-link'
>
{{t 'register'}}
</a>
</div>
{{/if}}
</section>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions app/components/login-component/login/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
</div>
<AkButton
data-test-login-login-button
id='login-submit-button'
class='button is-primary is-fullwidth highlighted-button
{{local-class
"login-button"
Expand Down
17 changes: 17 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
projectId: 'fkj9f2',
baseUrl: 'http://localhost:4200/',
specPattern: ['cypress/e2e/**/*.spec.{js,ts}'],
viewportHeight: 1070,
viewportWidth: 1480,
env: {
hideCredentials: true,
TEST_USERNAME: '***',
TEST_PASSWORD: '***',
},
setupNodeEvents(on, config) {},
},
});
76 changes: 76 additions & 0 deletions cypress.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Add Cypress types
declare namespace Cypress {
/**
* ==================================
* IRENE SPECIFIC INTERFACES
* ==================================
*/

export interface FrontendConfig {
name: string;
hide_poweredby_logo: boolean;
url: string;
registration_enabled: boolean;
registration_link: string;
integrations: Integrations;
images: Images;
theme: Theme;
}

export interface Images {
logo_on_darkbg: string;
logo_on_lightbg: string;
favicon: string;
}

export interface Integrations {
crisp_key: string;
hotjar_key: string;
pendo_key: string;
csb_key: string;
rollbar_key: string;
freshdesk_configuration: FreshdeskConfiguration;
}

export interface FreshdeskConfiguration {
widget_id: string;
}

export interface Theme {
scheme: string;
primary_color: string;
primary_alt_color: string;
secondary_color: string;
secondary_alt_color: string;
}

interface ServerConfig {
websocket: string;
enterprise: boolean;
url_upload_allowed: boolean;
}

/**
* ==================================
* CUSTOM CYPRESS COMMANDS
* ==================================
*/
type modelFactoriesName = 'vulnerabilities' | 'projects';

interface Chainable {
loginByCredentials(username: string, password: string): Chainable;

generateModelFixture: (
name: modelFactoriesName,
size?: number
) => Chainable;

loadAppConfig(data?: {
frontendConfig?: FrontendConfig;
serverConfig?: ServerConfig;
}): Chainable;

loadMockAppConfig(): Chainable;
loadMockHudsonProjects(): Chainable;
}
}
145 changes: 145 additions & 0 deletions cypress/e2e/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import APP_TRANSLATIONS from '../support/translations';
import { mirageServer } from '../support/Mirage';

import LoginActions from '../support/Actions/auth/LoginActions';
import NetworkActions from '../support/Actions/common/NetworkActions';
import NotificationRepository from '../support/Repositories/NotificationRepository';

import LOGIN_PAGE_LOCATORS from '../locators/pages/LoginPage';
import NAVBAR_LOCATORS from '../locators/common/Navbar';

import { API_ROUTES } from '../support/api.routes';
import { APPLICATION_ROUTES } from '../support/application.routes';

// Grouped test Actions
const loginActions = new LoginActions();
const networkActions = new NetworkActions();

// Common application repositories
const notifications = new NotificationRepository();

// User credentials
const username = Cypress.env('TEST_USERNAME');
const password = Cypress.env('TEST_PASSWORD');

describe('User Login', () => {
beforeEach(() => {
networkActions.hideNetworkLogsFor({ ...API_ROUTES.websockets });
});

it('should redirect unauthenticated user to login page', function () {
cy.visit(APPLICATION_ROUTES.projects);

cy.url().should('contain', APPLICATION_ROUTES.login);
cy.get(LOGIN_PAGE_LOCATORS.loginTitle).should(
'contain.text',
APP_TRANSLATIONS.login
);

cy.get(LOGIN_PAGE_LOCATORS.orgImageLogo).should('be.visible');
cy.get(LOGIN_PAGE_LOCATORS.loginTitle)
.should('be.visible')
.contains(APP_TRANSLATIONS.login);

cy.get(LOGIN_PAGE_LOCATORS.usernameField).should('be.visible');
cy.get(LOGIN_PAGE_LOCATORS.userCheckIconBtn).should('be.visible');
// cy.get(LOGIN_PAGE_LOCATORS.regLink).should('be.visible');
});

it('should throw an error if user credentials are invalid', () => {
const errorMessage = 'Unable to log in with provided credentials.';

// Mock Login request to fail
networkActions.mockNetworkReq({
method: 'POST',
route: API_ROUTES.login.route,
alias: 'failedLoginAttempt',
dataOverride: {
statusCode: 401,
body: {
message: errorMessage,
attempt_left: '4',
failure_limit: '5',
},
},
});

// Logs user to dashboard via UI
// CASE: Invalid Password
loginActions.doLoginWithUI({ username, password: 'password' });
notifications.assertErrorMessage(errorMessage);

// CASE: Invalid Username
loginActions.doLoginWithUI({ username: 'username', password });
notifications.assertErrorMessage(errorMessage);
});

it('should redirect authenticated user to dashboard after logging in', () => {
// Intercept vulnerabilities route
networkActions.mockNetworkReq({
...API_ROUTES.vulnerabilityList,
dataOverride: {
data: mirageServer.createRecordList('vulnerability', 1).map((_) => ({
id: _['id'],
type: 'vulnerabilities',
attributes: _,
relationships: {},
})),
},
});

// Intercept unknown analysis request
const unknownAnalysisStatus = mirageServer.createRecord(
'unknown-analysis-status'
);
const file = mirageServer.createRecord('file');

networkActions.mockNetworkReq({
...API_ROUTES.unknownAnalysisStatus,
dataOverride: unknownAnalysisStatus,
});

// Intercept file request
networkActions.mockNetworkReq({
...API_ROUTES.file,
dataOverride: file,
});

// Intercept projects route
networkActions.mockPaginatedNetworkReq({
...API_ROUTES.projectList,
resDataOverride: {
results: mirageServer
.createRecordList('project', 1)
.map((_) => ({ ..._, file: file['id'] })),
},
});

// Return empty submissions data
networkActions.mockPaginatedNetworkReq({
...API_ROUTES.submissionList,
resDataOverride: {
results: [],
},
});

networkActions.loadAppConfigWithMockData();

cy.visit('/');

// Logs user to dashboard via API
loginActions.doLoginWithUI({ username, password });

// Navigates to project listing route
cy.url({ timeout: 15000 }).should('include', APPLICATION_ROUTES.projects);

// Assertion for different dashboard elements
cy.get(NAVBAR_LOCATORS.container);

cy.get(NAVBAR_LOCATORS.startScanBtn).contains(
APP_TRANSLATIONS.startNewScan
);

cy.get(NAVBAR_LOCATORS.profileBtn).contains(username);
});
});
Loading

0 comments on commit be21fdf

Please sign in to comment.