Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,24 +89,55 @@ function login(email: string, password: string): void {
Cypress.Commands.add('login', login);

/**
* Login user via displayed login form
* Login the given user via the REST API and inject the resulting auth token
* into the UI session cookie. Despite the historical "ViaForm" name this is
* now a fully programmatic login — clicking through the UI form is unreliable
* on the CI runners (the browser-side POST /api/authn/login routinely fails
* to receive a response within Cypress' default 30s wait, which previously
* caused every login-protected spec to fail with "No response ever
* occurred"). Going through cy.request() instead bypasses the SSR layer and
* any browser-side CORS/XSRF timing problems, then a single cy.visit('/')
* forces Angular to rehydrate as an authenticated user.
*
* @param email email to login as
* @param password password to login as
*/
// Cypress custom command for form-based login with intercept and redirect assertion
function loginViaForm(
email: string,
password: string
): void {
cy.wait(500);

// Fill in credentials
cy.get('[data-test="email"]').should('be.visible').type(email);
cy.get('[data-test="password"]').type(password);
function loginViaForm(email: string, password: string): void {
// Each invocation needs a fresh CSRF cookie/token pair, since prior tests
// (or this test's own beforeEach) explicitly clear the XSRF cookie.
cy.createCSRFCookie().then((csrfToken: string) => {
cy.task('getRestBaseURL').then((baseRestUrl: string) => {
cy.request({
method: 'POST',
url: baseRestUrl + '/api/authn/login',
headers: { [XSRF_REQUEST_HEADER]: csrfToken },
// form-urlencoded body, matching what the Angular login form sends
form: true,
body: { user: email, password: password },
// Be generous: the very first login on a freshly-started DSpace
// backend in CI can take well over 30s while Hibernate warms up.
timeout: 120000,
}).then((resp) => {
expect(resp.status, 'login POST status').to.eq(200);
expect(resp.headers, 'login response headers').to.have.property('authorization');

// Submit the form
cy.get('[data-test="login-button"]').click();
// Persist the auth token into the UI cookie that Angular reads on
// bootstrap so the subsequent navigation is already authenticated.
const authHeader = resp.headers.authorization as string;
const authInfo: AuthTokenInfo = new AuthTokenInfo(authHeader);
cy.setCookie(TOKENITEM, JSON.stringify(authInfo));
});
});
});

// Force Angular to re-bootstrap with the new auth cookie. cy.reload()
// preserves the current URL, so specs that visit a restricted page first
// (e.g. /mydspace -> redirected to /login?returnUrl=/mydspace) still end up
// back at the original destination after login. For specs that visit
// /login directly, the login page sees the authenticated user on bootstrap
// and redirects to /home.
cy.reload();
cy.location('pathname', { timeout: 30000 }).should('not.match', /\/login$/);
}
// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
Cypress.Commands.add('loginViaForm', loginViaForm);
Expand Down
36 changes: 35 additions & 1 deletion cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,50 @@ before(() => {
});
});

// Pre-agreed Klaro consent payload. Keep this list in sync with the services
// declared in src/app/shared/cookies/klaro-configuration.ts — otherwise Klaro
// detects a configuration change and re-shows the consent banner during tests.
const KLARO_CONSENT_PAYLOAD = encodeURIComponent(JSON.stringify({
authentication: true,
preferences: true,
acknowledgement: true,
'google-analytics': true,
'google-recaptcha': true,
accessibility: true,
}));

// Runs once before the first test in each "block"
beforeEach(() => {
// Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie
// This just ensures it doesn't get in the way of matching other objects in the page.
cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true}');
cy.setCookie('klaro-anonymous', KLARO_CONSENT_PAYLOAD);

// Remove any CSRF cookies saved from prior tests
cy.clearCookie(DSPACE_XSRF_COOKIE);
});

// Hide the Klaro cookie-consent banner in every test window. Even with a pre-set
// klaro-anonymous cookie, Klaro may still render the notice (e.g. when its
// internal consent version changes after a config update), and that notice
// overlaps interactive elements such as the admin sidebar toggle. Injecting a
// `display: none` rule for the `.klaro` container at every page load keeps the
// banner from intercepting clicks during e2e tests.
Cypress.on('window:before:load', (win) => {
const injectKlaroHider = () => {
if (!win.document.getElementById('cypress-hide-klaro')) {
const style = win.document.createElement('style');
style.id = 'cypress-hide-klaro';
style.textContent = '.klaro { display: none !important; }';
(win.document.head || win.document.documentElement).appendChild(style);
}
};
if (win.document && win.document.head) {
injectKlaroHider();
} else {
win.addEventListener('DOMContentLoaded', injectKlaroHider, { once: true });
}
});

// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL
// from the Angular UI's config.json. See 'before()' above.
const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server';
Expand Down
Loading