Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ REACT_APP_ENABLE_MESSAGING=true
REACT_APP_ENABLE_REGIONS=true
REACT_APP_ENABLE_TOOLTIPS=true
REACT_APP_ENABLE_STAKEHOLDERS=true
REACT_APP_KEYCLOAK_URL=https://dev-k8s.treetracker.org/keycloak
REACT_APP_KEYCLOAK_REALM=treetracker
REACT_APP_KEYCLOAK_CLIENT_ID=treetracker-admin-client
3 changes: 2 additions & 1 deletion features/login.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ Feature: Login
And I click the login button
Then I should see an error message


Scenario: Login with valid credentials succeeds
Given I am on the login page
When I enter username "test-dev" and password "5Z5971uQXV"
When I enter username "user-test-treetracker-admin-client" and password "LjyxVk4t5^yx&!Gl"
And I click the login button
Then I should be redirected away from the login page
74 changes: 69 additions & 5 deletions features/page-objects/LoginPage.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,89 @@
class LoginPage {
get pageTitle() {
return $('#kc-page-title');
}

get loginForm() {
return $('#kc-form-login');
}

get usernameInput() {
return $('#userName');
return $('#username');
}

get passwordInput() {
return $('#password');
}

get submitButton() {
return $('button[type="submit"]');
return $('#kc-login');
}

get errorMessage() {
return $('h6');
get invalidCredentialsError() {
return $('#input-error');
}

async login(username, password) {
async isOpen() {
return this.loginForm.isExisting();
}

async waitForPage() {
await this.loginForm.waitForExist({
timeout: 60000,
timeoutMsg: 'Expected to be redirected to the Keycloak sign-in page',
});

await this.pageTitle.waitForDisplayed({ timeout: 10000 });
await this.usernameInput.waitForDisplayed({ timeout: 10000 });
await this.passwordInput.waitForDisplayed({ timeout: 10000 });
}

async enterCredentials(username, password) {
await this.waitForPage();
await this.usernameInput.setValue(username);
await this.passwordInput.setValue(password);
}

async submit() {
await this.submitButton.waitForDisplayed({ timeout: 10000 });
await this.submitButton.click();
}

async login(username, password) {
await this.enterCredentials(username, password);
await this.submit();
}

async waitForInvalidCredentials() {
await this.waitForPage();

await this.invalidCredentialsError.waitForDisplayed({
timeout: 10000,
timeoutMsg: 'Expected Keycloak to show invalid credential feedback',
});
}

async waitForSuccessfulRedirect() {
const baseUrl = browser.options.baseUrl;

await browser.waitUntil(
async () => {
const currentUrl = await browser.getUrl();

if (!baseUrl || !currentUrl.startsWith(baseUrl)) {
return false;
}

const currentPath = new URL(currentUrl).pathname;
return currentPath !== '/login' && currentPath !== '/auth/callback';
},
{
timeout: 60000,
interval: 500,
timeoutMsg: 'Expected successful login to redirect back to the app',
}
);
}
}

module.exports = new LoginPage();
90 changes: 79 additions & 11 deletions features/step-definitions/login.steps.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,97 @@
const { Given, When, Then } = require('@cucumber/cucumber');
const loginPage = require('../page-objects/LoginPage');
const { Before, Given, When, Then } = require('@cucumber/cucumber');
const LoginPage = require('../page-objects/LoginPage');

const LOG_OUT_BUTTON = '//button[normalize-space(.)="LOG OUT"]';

async function navigate(path) {
await browser.url(path, { wait: 'none' });
}

async function clearAppStorage() {
await navigate('/');

await browser.execute(() => {
window.localStorage.clear();
window.sessionStorage.clear();
});

await browser.deleteCookies();
}

async function isExisting(selector) {
return $(selector)
.isExisting()
.catch(() => false);
}

async function openKeycloakLoginPage() {
await navigate('/login');

if (await LoginPage.isOpen()) {
await LoginPage.waitForPage();
return;
}

await navigate('/account');

try {
await browser.waitUntil(
async () =>
(await LoginPage.isOpen()) || (await isExisting(LOG_OUT_BUTTON)),
{ timeout: 15000, interval: 250 }
);
} catch {
await navigate('/login');
await LoginPage.waitForPage();
return;
}

if (await LoginPage.isOpen()) {
await LoginPage.waitForPage();
return;
}

const logoutButton = $(LOG_OUT_BUTTON);
await logoutButton.waitForDisplayed({ timeout: 10000 });
await logoutButton.scrollIntoView();
await logoutButton.click();
await LoginPage.waitForPage();
}

async function ensureLoggedOut() {
await clearAppStorage();

await openKeycloakLoginPage();
}

async function assertInvalidCredentialFeedback() {
await LoginPage.waitForInvalidCredentials();
}

Before(async () => {
await ensureLoggedOut();
});

Given('I am on the login page', async () => {
await browser.url('/');
await openKeycloakLoginPage();
await LoginPage.waitForPage();
});

When(
'I enter username {string} and password {string}',
async (username, password) => {
await loginPage.usernameInput.setValue(username);
await loginPage.passwordInput.setValue(password);
await LoginPage.enterCredentials(username, password);
}
);

When('I click the login button', async () => {
await loginPage.submitButton.click();
await LoginPage.submit();
});

Then('I should see an error message', async () => {
await expect(loginPage.errorMessage).toBeDisplayed();
await assertInvalidCredentialFeedback();
});

Then('I should be redirected away from the login page', async () => {
await browser.waitUntil(
async () => !(await browser.getUrl()).includes('/login'),
{ timeout: 10000, timeoutMsg: 'Expected URL to leave /login after login' }
);
await LoginPage.waitForSuccessfulRedirect();
});
Loading
Loading