Add cypress framework (#271)

* Add cypress framework

* Add PR change requests

* Add initial .yml file for cypress tests

* Modify Run Keycloak line

* Modify Run Keycloak lines

* Modify Cypress test run

* Modify wait times

* Add more time

* Modify yarn start to npx http-server

* Try Cypress separate step

* Add test to set new admin console

* Modify uses to run

* Change yarn to npx http-server

* Add parameter to npx http-server

* Trigger GitHub actions

* Trigger GitHub actions

* Modify client_scope test messages

* Set headless mode

* Add steps to store artifacts

* Modify .yml

* Rebase onto realm fix and update real role message

* Update yaml file for artifact upload
This commit is contained in:
Aboullos 2021-01-12 18:04:52 +01:00 committed by GitHub
parent ce0ce6d59e
commit daec4957f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 6601 additions and 3 deletions

53
.github/workflows/cypress.yml vendored Normal file
View file

@ -0,0 +1,53 @@
name: Cypress run
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
cypress-run:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install -g yarn
- run: yarn install
- run: yarn build
- name: Run Keycloak
run: ./start.js & sleep 40
- name: Run Admin Console
run: npx http-server ./build -P http://localhost:8180/auth/ & sleep 30
- name: Create admin user and add Admin Console client
run: cd tests && npx cypress run --headless --browser chrome --spec cypress/integration/00_set_new_admin_console.spec.js && cd ..
- name: Cypress run
run: cd tests && npx cypress run --headless --browser chrome --config ignoreTestFiles=00_set_new_admin_console.spec.js && cd ..
- name: Add Cypress videos artifacts
uses: actions/upload-artifact@v2
if: failure()
with:
name: cypress-videos
path: tests/assets/videos
- name: Add Cypress screenshots artifacts
uses: actions/upload-artifact@v2
if: failure()
with:
name: cypress-screenshots
path: tests/assets/screenshots

9
.gitignore vendored
View file

@ -114,7 +114,7 @@ temp/
**/*.backup.*
**/*.back.*
node_modules
**/node_modules
*.sublime*
@ -135,4 +135,9 @@ server/
# VS Code #
###########
*.code-workspace
**/*.code-workspace
# Cypress #
###########
**/assets
**/cypress.env.json

View file

@ -13,7 +13,9 @@
"postinstall": "grunt",
"start": "snowpack dev",
"storybook": "start-storybook -p 6006 -s public",
"test": "jest"
"test": "jest",
"start:cypress": "cd tests && npx cypress open",
"start:cypress-tests": "cd tests && npx cypress run --config ignoreTestFiles=00_set_new_admin_console.spec.js"
},
"dependencies": {
"@patternfly/patternfly": "^4.65.6",

72
tests/README.md Normal file
View file

@ -0,0 +1,72 @@
# Keycloak UI Test Suite in Cypress
This repository contains the UI tests for Keycloak developed with Cypress framework
## Run the test suite
### Prerequisites
* `Keycloak distribution` has to be [downloaded](https://www.keycloak.org/downloads) and started on 8081 port.
**note**: the port in at the test suite side in [cypress.json](cypress.json) or at the Keycloak side, see [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/getting_started/#starting-the-keycloak-server),
* `npm package manager` has to be [downloaded](https://nodejs.org/en/download/) and installed.
### via Cypress Test Runner
**By using `npx`:**
**note**: [npx](https://www.npmjs.com/package/npx) is included with `npm > v5.2` or can be installed separately.
```shell
npx cypress open
```
After a moment, the Cypress Test Runner will launch:
![image](https://drive.google.com/uc?export=view&id=1i4_VABpM29VwrrAcvEY31w7EuymifcwV)
### via terminal
**By executing:**
```shell
$(npm bin)/cypress run
```
...or...
```shell
./node_modules/.bin/cypress run
```
...or... (requires npm@5.2.0 or greater)
```shell
npx cypress run
```
**To execute a specific test on a specific browser run:**
```shell
cypress run --spec "cypress/integration/example-test.spec.js" --browser chrome
```
**note**: the complete list of parameters can be found in the [official Cypress documentation](https://docs.cypress.io/guides/guides/command-line.html#Commands).
## Project Structure
```text
/assets (added to .gitignore)
/videos - if test fails, the video is stored here
/screenshots - if test fails, the screenshot is stored here
/cypress
/fixtures - external pieces of static data that can be used by your tests
/integration - used for test files (supported filetypes are .js, .jsx, .coffee and .cjsx)
/plugins
- index.js - extends Cypress behaviour, custom plugins are imported before every single spec file run
/support - reusable behaviour
- commands.js - custom commands
- index.js - runs before each test file
/cypress.json - Cypress configuration file
/jsconfig.json - Cypress code autocompletion is enabled here
```
**note**: More about the project structure in the [official Cypress documentation](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Folder-Structure).
## License
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)

8
tests/cypress.json Normal file
View file

@ -0,0 +1,8 @@
{
"screenshotsFolder": "assets/screenshots",
"videosFolder": "assets/videos",
"baseUrl": "http://localhost:8080",
"chromeWebSecurity": false,
"viewportWidth": 1360,
"viewportHeight": 768
}

View file

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View file

@ -0,0 +1,28 @@
import LoginPage from '../support/pages/LoginPage.js'
import WelcomePage from '../support/pages/WelcomePage.js'
import OldClientPage from '../support/pages/admin_console/manage/clients/OldClientPage.js'
describe('Set up test', function () {
const loginPage = new LoginPage();
const welcomePage = new WelcomePage();
const oldClientPage = new OldClientPage();
describe('Set up test', function () {
beforeEach(function () {
cy.visit('http://localhost:8180/auth')
})
it('Create admin user and adds admin console client', function () {
welcomePage
.createAdminUser()
.goToAdminConsole();
loginPage.logIn();
oldClientPage
.goToClients()
.addNewAdminConsole();
});
})
})

View file

@ -0,0 +1,85 @@
import LoginPage from '../support/pages/LoginPage.js'
import HeaderPage from '../support/pages/admin_console/HeaderPage.js'
import ListingPage from '../support/pages/admin_console/ListingPage.js'
import SidebarPage from '../support/pages/admin_console/SidebarPage.js'
import CreateClientScopePage from '../support/pages/admin_console/manage/client_scopes/CreateClientScopePage.js'
describe('Client Scopes test', function () {
const itemId = 'client_scope_1';
const loginPage = new LoginPage();
const headerPage = new HeaderPage();
const sidebarPage = new SidebarPage();
const listingPage = new ListingPage();
const createClientScopePage = new CreateClientScopePage();
describe('Client Scope creation', function () {
beforeEach(function () {
cy.visit('')
})
it('should fail creating client scope', function () {
loginPage.logIn();
sidebarPage.goToClientScopes();
listingPage.goToCreateItem();
createClientScopePage
.save()
.checkClientNameRequiredMessage();
createClientScopePage
.fillClientScopeData('address')
.save()
.checkClientNameRequiredMessage(false);
// The error should inform about duplicated name/id
headerPage.checkNotificationMessage('Could not create client scope: \'Error: Request failed with status code 409\'');
});
it('should create client scope', function () {
loginPage.logIn();
sidebarPage.goToClientScopes();
listingPage
.itemExist(itemId, false)
.goToCreateItem();
createClientScopePage
.fillClientScopeData(itemId)
.save();
headerPage.checkNotificationMessage('Client scope created');
sidebarPage.goToClientScopes();
listingPage
.itemExist(itemId)
.searchItem(itemId)
.itemExist(itemId);
});
})
describe('Client scope elimination', function () {
beforeEach(function () {
cy.visit('')
})
it('should delete client scope', function () {
loginPage.logIn();
sidebarPage.goToClientScopes();
listingPage
.itemExist(itemId)
.deleteItem(itemId); // There should be a confirmation pop-up
headerPage.checkNotificationMessage('The client scope has been deleted');
listingPage
.itemExist(itemId, false);
});
})
})

View file

@ -0,0 +1,101 @@
import LoginPage from '../support/pages/LoginPage.js'
import HeaderPage from '../support/pages/admin_console/HeaderPage.js'
import ListingPage from '../support/pages/admin_console/ListingPage.js'
import SidebarPage from '../support/pages/admin_console/SidebarPage.js'
import CreateClientPage from '../support/pages/admin_console/manage/clients/CreateClientPage.js'
describe('Clients test', function () {
const itemId = 'client_1';
const loginPage = new LoginPage();
const headerPage = new HeaderPage();
const sidebarPage = new SidebarPage();
const listingPage = new ListingPage();
const createClientPage = new CreateClientPage();
describe('Client creation', function () {
beforeEach(function () {
cy.visit('')
})
it('should fail creating client', function () {
loginPage.logIn();
sidebarPage.goToClients();
listingPage.goToCreateItem();
createClientPage
.continue()
.checkClientTypeRequiredMessage()
.checkClientIdRequiredMessage();
createClientPage
.fillClientData(itemId)
.continue()
.checkClientTypeRequiredMessage()
.checkClientIdRequiredMessage(false);
createClientPage
.fillClientData('')
.selectClientType('openid-connect')
.continue()
.checkClientTypeRequiredMessage(false)
.checkClientIdRequiredMessage();
createClientPage
.fillClientData('account')
.continue()
.continue();
// The error should inform about duplicated name/id
headerPage.checkNotificationMessage('Could not create client: \'Error: Request failed with status code 409\'');
});
it('should create client', function () {
loginPage.logIn();
sidebarPage.goToClients();
listingPage
.itemExist(itemId, false)
.goToCreateItem();
createClientPage
.selectClientType('openid-connect')
.fillClientData(itemId)
.continue()
.continue();
headerPage.checkNotificationMessage('Client created successfully');
sidebarPage.goToClients();
listingPage
.itemExist(itemId)
.searchItem(itemId)
.itemExist(itemId);
});
})
describe('Client elimination', function () {
beforeEach(function () {
cy.visit('')
})
it('should delete client', function () {
loginPage.logIn();
sidebarPage.goToClients();
listingPage
.itemExist(itemId)
.deleteItem(itemId); // There should be a confirmation pop-up
headerPage.checkNotificationMessage('The client has been deleted');
listingPage
.itemExist(itemId, false);
});
})
})

View file

@ -0,0 +1,31 @@
import LoginPage from './../support/pages/LoginPage.js'
import HeaderPage from './../support/pages/admin_console/HeaderPage.js'
describe('Logging In', function () {
const username = 'admin'
const password = 'admin'
const loginPage = new LoginPage();
const headerPage = new HeaderPage();
describe('Login form submission', function () {
beforeEach(function () {
cy.visit('')
})
it('displays errors on login', function () {
loginPage.logIn('wrong', 'user{enter}')
.checkErrorMessage('Invalid username or password.')
.isLogInPage();
})
it('redirects to admin console on success', function () {
loginPage.logIn(username, password);
headerPage.isAdminConsole();
cy.getCookie('KEYCLOAK_SESSION_LEGACY').should('exist')
})
})
})

View file

@ -0,0 +1,88 @@
import LoginPage from '../support/pages/LoginPage.js'
import HeaderPage from '../support/pages/admin_console/HeaderPage.js'
import ListingPage from '../support/pages/admin_console/ListingPage.js'
import SidebarPage from '../support/pages/admin_console/SidebarPage.js'
import CreateRealmRolePage from '../support/pages/admin_console/manage/realm_roles/CreateRealmRolePage.js'
describe('Realm roles test', function () {
const itemId = 'realm_role_1';
const loginPage = new LoginPage();
const headerPage = new HeaderPage();
const sidebarPage = new SidebarPage();
const listingPage = new ListingPage();
const createRealmRolePage = new CreateRealmRolePage();
describe('Realm roles creation', function () {
beforeEach(function () {
cy.visit('')
})
it('should fail creating realm role', function () {
loginPage.logIn();
sidebarPage.goToRealmRoles();
listingPage.goToCreateItem();
createRealmRolePage
.save()
.checkRealmRoleNameRequiredMessage();
createRealmRolePage
.fillRealmRoleData('admin')
.save();
// The error should inform about duplicated name/id (THIS MESSAGE DOES NOT HAVE QUOTES AS THE OTHERS)
headerPage.checkNotificationMessage('Could not create role: Role with name admin already exists');
});
it('should create realm role', function () {
loginPage.logIn();
sidebarPage.goToRealmRoles();
listingPage
.itemExist(itemId, false)
.goToCreateItem();
createRealmRolePage
.fillRealmRoleData(itemId)
.save();
headerPage.checkNotificationMessage('Role created');
sidebarPage.goToRealmRoles();
listingPage
.itemExist(itemId)
.searchItem(itemId)
.itemExist(itemId);
});
})
describe('Realm roles elimination', function () {
beforeEach(function () {
cy.visit('')
})
it('should delete realm role', function () {
loginPage.logIn();
sidebarPage.goToRealmRoles();
listingPage
.itemExist(itemId)
.deleteItem(itemId);
headerPage
.checkModalTitle('Delete role?')
.confirmModal();
headerPage.checkNotificationMessage('The role has been deleted');
listingPage
.itemExist(itemId, false);
});
})
})

View file

@ -0,0 +1,50 @@
import LoginPage from '../support/pages/LoginPage.js'
import SidebarPage from '../support/pages/admin_console/SidebarPage.js'
import CreateRealmPage from '../support/pages/admin_console/CreateRealmPage.js'
import HeaderPage from '../support/pages/admin_console/HeaderPage.js'
describe('Realms test', function () {
const loginPage = new LoginPage();
const sidebarPage = new SidebarPage();
const createRealmPage = new CreateRealmPage();
const headerPage = new HeaderPage();
describe('Realm creation', function () {
beforeEach(function () {
cy.visit('')
})
it('should fail creating Master realm', function () {
loginPage.logIn();
sidebarPage.goToCreateRealm();
createRealmPage
.fillRealmName('master')
.createRealm();
headerPage.checkNotificationMessage('Error: Request failed with status code 409');
});
it('should create Test realm', function () {
loginPage.logIn();
sidebarPage.goToCreateRealm();
createRealmPage
.fillRealmName('Test')
.createRealm();
headerPage.checkNotificationMessage('Realm created');
});
it('should change to Test realm', function () {
loginPage.logIn();
sidebarPage.getCurrentRealm().should('eq', 'Master');
sidebarPage
.goToRealm('Test')
.getCurrentRealm().should('eq', 'Test');
});
})
})

View file

@ -0,0 +1,20 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View file

@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View file

@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js 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'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View file

@ -0,0 +1,38 @@
export default class LoginPage {
constructor() {
this.userNameInput = "#username";
this.passwordInput = "#password";
this.submitBtn = "#kc-login";
this.errorText = ".kc-feedback-text";
}
isLogInPage() {
cy.get(this.userNameInput).should('exist');
cy.url().should('include', '/auth');
return this;
}
logIn(userName = "admin", password = "admin") {
cy.get(this.userNameInput).type(userName);
cy.get(this.passwordInput).type(password);
cy.get(this.submitBtn).click();
return this;
}
checkErrorIsDisplayed() {
cy.get(this.userDrpDwn).should('exist');
return this;
}
checkErrorMessage(message) {
cy.get(this.errorText).invoke('text').should('contain', message);
return this;
}
}

View file

@ -0,0 +1,27 @@
export default class WelcomePage {
constructor() {
this.userNameInput = "#username";
this.passwordInput = "#password";
this.confirmPasswordInput = "#passwordConfirmation";
this.createBtn = "#create-button";
this.adminConsoleBtn = ".welcome-primary-link a";
}
createAdminUser(userName = "admin", password = "admin") {
cy.get(this.userNameInput).type(userName);
cy.get(this.passwordInput).type(password);
cy.get(this.confirmPasswordInput).type(password);
cy.get(this.createBtn).click();
return this;
}
goToAdminConsole() {
cy.get(this.adminConsoleBtn).click();
return this;
}
}

View file

@ -0,0 +1,25 @@
export default class CreateRealmPage {
constructor() {
this.browseBtn = '#kc-realm-filename-browse-button';
this.clearBtn = '.pf-c-file-upload__file-select button:last-child';
this.realmFileNameInput = '#kc-realm-filename';
this.realmNameInput = '#kc-realm-name';
this.enabledSwitch = '[for="kc-realm-enabled-switch"] span.pf-c-switch__toggle';
this.createBtn = '.pf-c-form__group:last-child button[type="submit"]';
this.cancelBtn = '.pf-c-form__group:last-child button[type="button"]';
}
fillRealmName(realmName) {
cy.get(this.realmNameInput).type(realmName);
return this;
}
createRealm() {
cy.get(this.createBtn).click();
return this;
}
}

View file

@ -0,0 +1,92 @@
export default class HeaderPage {
constructor() {
this.menuBtn = '#nav-toggle';
this.logoBtn = 'img[alt="Logo"]';
this.helpBtn = '#help';
this.userDrpDwn = '[id*="pf-dropdown-toggle-id"]';
this.manageAccountBtn = '.pf-c-page__header-tools-item [role*="menu"] li:nth-child(1)';
this.serverInfoBtn = '.pf-c-page__header-tools-item [role*="menu"] li:nth-child(2)';
this.signOutBtn = '.pf-c-page__header-tools-item [role*="menu"] li:nth-child(4)';
this.notificationList = '.pf-c-alert-group.pf-m-toast';
this.modalTitle = '.pf-c-modal-box .pf-c-modal-box__title-text';
this.modalMessage = '.pf-c-modal-box .pf-c-modal-box__body';
this.confirmModalBtn = '#modal-confirm';
this.cancelModalBtn = '#modal-cancel';
this.closeModalBtn = '.pf-c-modal-box .pf-m-plain';
}
goToAdminConsole() {
cy.visit('');
return this;
}
goToManageAccount() {
cy.get(this.userDrpDwn).click();
cy.get(this.manageAccountBtn).click();
return this;
}
goToServerInfo() {
cy.get(this.userDrpDwn).click();
cy.get(this.serverInfoBtn).click();
return this;
}
signOut() {
cy.get(this.userDrpDwn).click();
cy.get(this.signOutBtn).click();
return this;
}
isAdminConsole() {
cy.get(this.logoBtn).should('exist');
cy.get(this.userDrpDwn).should('exist');
return this;
}
checkNotificationMessage(message) {
cy.contains(message).should('exist');
return this;
}
confirmModal() {
cy.get(this.confirmModalBtn).click();
return this;
}
cancelModal() {
cy.get(this.cancelModalBtn).click();
return this;
}
closeModal() {
cy.get(this.closeModalBtn).click();
return this;
}
checkModalTitle(title) {
cy.get(this.modalTitle).invoke('text').should('eq', title);
return this;
}
checkModalMessage(message) {
cy.get(this.modalMessage).invoke('text').should('eq', message);
return this;
}
}

View file

@ -0,0 +1,58 @@
export default class ListingPage {
constructor() {
this.searchInput = '.pf-c-toolbar__item [type="search"]';
this.itemsRows = '.pf-c-page__main-section tbody > tr';
this.itemRowDrpDwn = '.pf-c-dropdown > button';
this.exportBtn = '[role="menuitem"]:nth-child(1)';
this.deleteBtn = '[role="menuitem"]:nth-child(2)';
this.searchBtn = '.pf-c-page__main .pf-c-toolbar__content-section button.pf-m-control';
this.createBtn = '.pf-c-page__main .pf-c-toolbar__content-section button.pf-m-primary';
this.importBtn = '.pf-c-page__main .pf-c-toolbar__content-section button.pf-m-link';
}
goToCreateItem() {
cy.get(this.createBtn).click();
return this;
}
goToImportItem() {
cy.get(this.importBtn).click();
return this;
}
searchItem(searchValue) {
cy.get(this.searchInput).type(searchValue);
cy.get(this.searchBtn).click();
return this;
}
itemExist(itemName, exist = true) {
cy.get(this.itemsRows).contains(itemName).should((!exist ? 'not.': '') + 'exist')
return this;
}
goToItemDetails(itemName) {
cy.get(this.itemsRows).contains(itemName).click();
return this;
}
deleteItem(itemName) {
cy.get(this.itemsRows).contains(itemName).parentsUntil('tbody').find(this.itemRowDrpDwn).click();
cy.get(this.itemsRows).contains('Delete').click();
return this;
}
exportItem(itemName) {
cy.get(this.itemsRows).contains(itemName).parentsUntil('tbody').find(this.itemRowDrpDwn).click();
cy.get(this.itemsRows).contains('Export').click();
return this;
}
}

View file

@ -0,0 +1,105 @@
export default class SidebarPage {
constructor() {
this.realmsDrpDwn = '#realm-select-toggle';
this.realmsList = '#realm-select ul';
this.createRealmBtn = '#realm-select li:last-child a';
this.clientsBtn = '#nav-item-clients';
this.clientScopesBtn = '#nav-item-client-scopes';
this.realmRolesBtn = '#nav-item-roles';
this.usersBtn = '#nav-item-users';
this.groupsBtn = '#nav-item-groups';
this.sessionsBtn = '#nav-item-sessions';
this.eventsBtn = '#nav-item-events';
this.realmSettingsBtn = '#nav-item-realm-settings';
this.authenticationBtn = '#nav-item-authentication';
this.identityProvidersBtn = '#nav-item-identity-providers';
this.userFederationBtn = '#nav-item-user-federation';
}
getCurrentRealm() {
return cy.get(this.realmsDrpDwn).invoke('text');
}
goToRealm(realmName) {
cy.get(this.realmsDrpDwn).click();
cy.get(this.realmsList).contains(realmName).click();
return this;
}
goToCreateRealm() {
cy.get(this.realmsDrpDwn).click();
cy.get(this.createRealmBtn).click();
return this;
}
goToClients() {
cy.get(this.clientsBtn).click();
return this;
}
goToClientScopes() {
cy.get(this.clientScopesBtn).click();
return this;
}
goToRealmRoles() {
cy.get(this.realmRolesBtn).click();
return this;
}
goToUsers() {
cy.get(this.usersBtn).click();
return this;
}
goToGroups() {
cy.get(this.groupsBtn).click();
return this;
}
goToSessions() {
cy.get(this.sessionsBtn).click();
return this;
}
goToEvents() {
cy.get(this.eventsBtn).click();
return this;
}
goToRealmSettings() {
cy.get(this.realmSettingsBtn).click();
return this;
}
goToAuthentication() {
cy.get(this.authenticationBtn).click();
return this;
}
goToIdentityProviders() {
cy.get(this.identityProvidersBtn).click();
return this;
}
goToUserFederation() {
cy.get(this.userFederationBtn).click();
return this;
}
}

View file

@ -0,0 +1,81 @@
export default class CreateClientScopePage {
constructor() {
this.settingsTab = '.pf-c-tabs__item:nth-child(1)';
this.mappersTab = '.pf-c-tabs__item:nth-child(2)';
this.clientScopeNameInput = '#kc-name';
this.clientScopeNameError = '#kc-name-helper';
this.clientScopeDescriptionInput = '#kc-description';
this.clientScopeTypeDrpDwn = '#kc-protocol';
this.clientScopeTypeList = '#kc-protocol + ul';
this.displayOnConsentSwitch = '[id="kc-display.on.consent.screen"] + .pf-c-switch__toggle';
this.consentScreenTextInput = '#kc-consent-screen-text';
this.includeInTokenSwitch = '[id="includeInTokenScope"] + .pf-c-switch__toggle';
this.displayOrderInput = '#kc-gui-order';
this.saveBtn = '[type="submit"]';
this.cancelBtn = '[type="button"]';
}
//#region General Settings
fillClientScopeData(name, description = '', consentScreenText = '', displayOrder = '') {
cy.get(this.clientScopeNameInput).clear();
if(name) {
cy.get(this.clientScopeNameInput).type(name);
}
if(description) {
cy.get(this.clientScopeDescriptionInput).type(description);
}
if(consentScreenText) {
cy.get(this.consentScreenTextInput).type(consentScreenText);
}
if(displayOrder) {
cy.get(this.displayOrderInput).type(displayOrder);
}
return this;
}
selectClientScopeType(clientScopeType) {
cy.get(this.clientScopeTypeDrpDwn).click();
cy.get(this.clientScopeTypeList).contains(clientScopeType).click();
return this;
}
checkClientNameRequiredMessage(exist = true) {
cy.get(this.clientScopeNameError).should((!exist ? 'not.': '') + 'exist');
return this;
}
switchDisplayOnConsentScreen() {
cy.get(this.displayOnConsentSwitch).click();
return this;
}
switchIncludeInTokenScope() {
cy.get(this.includeInTokenSwitch).click();
return this;
}
//#endregion
save() {
cy.get(this.saveBtn).click();
return this;
}
cancel() {
cy.get(this.cancelBtn).click();
return this;
}
}

View file

@ -0,0 +1,118 @@
export default class CreateClientPage {
constructor() {
this.clientTypeDrpDwn = '.pf-c-select__toggle';
this.clientTypeError = '.pf-c-select + div';
this.clientTypeList = '.pf-c-select__toggle + ul';
this.clientIdInput = '#kc-client-id';
this.clientIdError = '#kc-client-id + div';
this.clientNameInput = '#kc-name';
this.clientDescriptionInput = '#kc-description';
this.clientAuthenticationSwitch = '[for="kc-authentication"] .pf-c-switch__toggle';
this.clientAuthorizationSwitch = '[for="kc-authorization"] .pf-c-switch__toggle';
this.standardFlowChkBx = '#kc-flow-standard';
this.directAccessChkBx = '#kc-flow-direct';
this.implicitFlowChkBx = '#kc-flow-implicit';
this.serviceAccountRolesChkBx = '#kc-flow-service-account';
this.continueBtn = '.pf-c-wizard__footer .pf-m-primary';
this.backBtn = '.pf-c-wizard__footer .pf-m-secondary';
this.cancelBtn = '.pf-c-wizard__footer .pf-m-link';
}
//#region General Settings
selectClientType(clientType) {
cy.get(this.clientTypeDrpDwn).click();
cy.get(this.clientTypeList).contains(clientType).click();
return this;
}
fillClientData(id, name = '', description = '') {
cy.get(this.clientIdInput).clear();
if(id) {
cy.get(this.clientIdInput).type(id);
}
if(name) {
cy.get(this.clientNameInput).type(name);
}
if(description) {
cy.get(this.clientDescriptionInput).type(description);
}
return this;
}
checkClientTypeRequiredMessage(exist = true) {
cy.get(this.clientTypeError).should((!exist ? 'not.': '') + 'exist');
return this;
}
checkClientIdRequiredMessage(exist = true) {
cy.get(this.clientIdError).should((!exist ? 'not.': '') + 'exist');
return this;
}
//#endregion
//#region Capability config
switchClientAuthentication() {
cy.get(this.clientAuthenticationSwitch).click();
return this;
}
switchClientAuthorization() {
cy.get(this.clientAuthorizationSwitch).click();
return this;
}
clickStandardFlow() {
cy.get(this.standardFlowChkBx).click();
return this;
}
clickDirectAccess() {
cy.get(this.directAccessChkBx).click();
return this;
}
clickImplicitFlow() {
cy.get(this.implicitFlowChkBx).click();
return this;
}
clickServiceAccountRoles() {
cy.get(this.serviceAccountRolesChkBx).click();
return this;
}
//#endregion
continue() {
cy.get(this.continueBtn).click();
return this;
}
back() {
cy.get(this.backBtn).click();
return this;
}
cancel() {
cy.get(this.cancelBtn).click();
return this;
}
}

View file

@ -0,0 +1,29 @@
export default class CreateClientPage {
constructor() {
this.goToClientsBtn = '[href="#/realms/master/clients"]';
this.createClientBtn = '#createClient';
this.clientIdInput = '#clientId';
this.rootUrlInput = '#rootUrl';
this.saveBtn = '[kc-save]';
}
goToClients() {
cy.get(this.goToClientsBtn).click();
return this;
}
addNewAdminConsole() {
cy.get(this.createClientBtn).click();
cy.get(this.clientIdInput).type('security-admin-console-v2');
cy.get(this.rootUrlInput).type('http://localhost:8080/');
cy.get(this.saveBtn).click();
return this;
}
}

View file

@ -0,0 +1,45 @@
export default class CreateRealmRolePage {
constructor() {
this.realmRoleNameInput = '#kc-name';
this.realmRoleNameError = '#kc-name-helper';
this.realmRoleDescriptionInput = '#kc-role-description';
this.saveBtn = '[type="submit"]';
this.cancelBtn = '[type="button"]';
}
//#region General Settings
fillRealmRoleData(name, description = '') {
cy.get(this.realmRoleNameInput).clear();
if(name) {
cy.get(this.realmRoleNameInput).type(name);
}
if(description) {
cy.get(this.realmRoleDescriptionInput).type(description);
}
return this;
}
checkRealmRoleNameRequiredMessage(exist = true) {
cy.get(this.realmRoleNameError).should((!exist ? 'not.': '') + 'exist');
return this;
}
//#endregion
save() {
cy.get(this.saveBtn).click();
return this;
}
cancel() {
cy.get(this.cancelBtn).click();
return this;
}
}

6
tests/jsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"include": [
"./node_modules/cypress",
"cypress/**/*.js"
]
}

3964
tests/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

14
tests/package.json Normal file
View file

@ -0,0 +1,14 @@
{
"name": "cypress-keycloak-ui-testsuite",
"version": "1.0.0",
"description": "UI tests for Keycloak using Cypress.",
"main": "index.js",
"scripts": {
"test": "cypress open"
},
"author": "",
"license": "ISC",
"devDependencies": {
"cypress": "^5.6.0"
}
}

1403
tests/yarn.lock Normal file

File diff suppressed because it is too large Load diff