Compare commits

...
Author SHA1 Message Date
dc 92ff21b8f5 test(licensing): wait for the app to settle instead of sleeping through it
Build / Build-and-ng-test (pull_request) Successful in 3m51s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m22s
Build / Build-and-test-development (pull_request) Failing after 48m15s
Every one of the spec's 52 cy.wait() calls is gone: they ranged from 1s to
20s, 350 seconds of declared sleeping in a spec that runs in 87. Each stood in
for something the app does asynchronously, and each has been replaced by the
state it was waiting for:

- a key change leaves the app re-bootstrapping its licence state, so
  waitForAppSettled() waits for .app-loading to clear and for the app to land
  on a known screen (nav tree, demo banner, key error or login form);
- the paste form exists only on licensing/update - asking whether the app is
  anywhere under licensing/ also matches the error page a bad key produces,
  which has no paste button, so gotoLicensingUpdate() navigates unless that is
  where it already is;
- a rejected key redirects to the licensing page, so verifyLicensingPage()
  waits for that URL instead of sleeping and then checking once, and fails
  there rather than hanging when the redirect never comes;
- the log-out, approve and paste-button paths already retry, so the sleeps in
  front of them were pure padding.

Measured on the local stack against the same estate: 10 passing in 45s twice,
against 1:27 before (and 1:33 in CI).
2026-11-06 16:11:52 +00:00
+66 -144
View File
@@ -45,20 +45,10 @@ context('licensing tests: ', function () {
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
cy.wait(2000)
isLicensingPage((result: boolean) => {
if (result) {
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
cy.wait(2000)
}
gotoLicensingUpdate()
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
cy.then(() => {
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(10000)
}
visitPage('home')
cy.get('.nav-tree clr-tree > clr-tree-node', {
@@ -86,18 +76,9 @@ context('licensing tests: ', function () {
keys = keysGen
console.log('keys', keys)
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingWarning('This license key will expire in ', () => {
done()
})
@@ -120,19 +101,9 @@ context('licensing tests: ', function () {
generateKeys(keyData, (keysGen: any) => {
keys = keysGen
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
cy.wait(2000)
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingPage(
'Licence key is expired - please contact',
(success: boolean) => {
@@ -166,19 +137,9 @@ context('licensing tests: ', function () {
keys = keysGen
keys.activationKey = 'invalid' + keys.activationKey
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
cy.wait(2000)
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingPage(
'Licence key is invalid - please contact',
(success: boolean) => {
@@ -212,19 +173,9 @@ context('licensing tests: ', function () {
keys = keysGen
keys.activationKey = keys.activationKey
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
cy.wait(2000)
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
verifyLicensingPage(
'SYSSITE (below) is not found',
(success: boolean) => {
@@ -247,7 +198,9 @@ context('licensing tests: ', function () {
it('6 | pasting an HTTP-format key (identical licence/activation text) on this secure connection warns before applying', (done) => {
visitPage('licensing/update')
cy.get('button').contains('Paste licence').click()
cy.contains('button', 'Paste licence', {
timeout: longerCommandTimeout
}).click()
// Defaults to the combined single-field form - switch to the legacy
// two-field layout, since this test targets that path specifically.
@@ -287,25 +240,17 @@ context('licensing tests: ', function () {
}
generateKeys(keyData, (keysGen: any) => {
cy.wait(2000)
// Navigate there explicitly rather than only acting when already on
// the licensing page - earlier tests may have left the app on it (a
// bad key) or already activated (a good one), and this test should
// pass either way rather than silently no-op when it's the latter.
isLicensingPage((result: boolean) => {
cy.then(() => {
const proceed = () =>
generateCombinedKey(keysGen.licenseKey, keysGen.activationKey).then(
(combinedKey) => {
inputCombinedKeyPage(combinedKey)
cy.wait(2000)
acceptTermsIfPresented((termsResult: boolean) => {
if (termsResult) {
cy.wait(10000)
}
visitPage('home')
cy.get('.nav-tree clr-tree > clr-tree-node', {
@@ -317,10 +262,7 @@ context('licensing tests: ', function () {
}
)
if (!result) {
visitPage('licensing/update')
cy.wait(2000)
}
gotoLicensingUpdate()
// proceed() generates the combined key asynchronously, then
// enqueues the actual page commands only once its promise
@@ -344,15 +286,15 @@ context('licensing tests: ', function () {
}
generateKeys(keyData, (keysGen: any) => {
cy.wait(2000)
// Navigate there explicitly rather than only acting when already on
// the licensing page - see test 7's own comment for why.
isLicensingPage((result: boolean) => {
cy.then(() => {
const proceed = () =>
generateCombinedKey(keysGen.licenseKey, keysGen.activationKey).then(
(combinedKey) => {
cy.get('button').contains('Paste licence').click()
cy.contains('button', 'Paste licence', {
timeout: longerCommandTimeout
}).click()
cy.get('button')
.contains('Paste as two separate keys instead')
.click()
@@ -375,13 +317,7 @@ context('licensing tests: ', function () {
cy.get('button.apply-keys').click()
cy.wait(2000)
acceptTermsIfPresented((termsResult: boolean) => {
if (termsResult) {
cy.wait(10000)
}
visitPage('home')
cy.get('.nav-tree clr-tree > clr-tree-node', {
@@ -395,10 +331,7 @@ context('licensing tests: ', function () {
// See test 7's own comment for why proceed() is bridged through
// cy.then() rather than called as a plain statement.
if (!result) {
visitPage('licensing/update')
cy.wait(2000)
}
gotoLicensingUpdate()
cy.then(() => proceed())
})
})
@@ -414,11 +347,9 @@ context('licensing tests: ', function () {
}
generateKeys(keyData, (keysGen: any) => {
cy.wait(2000)
// Navigate there explicitly rather than only acting when already on
// the licensing page - see test 7's own comment for why.
isLicensingPage((result: boolean) => {
cy.then(() => {
const proceed = () =>
generateCombinedKey(keysGen.licenseKey, keysGen.activationKey).then(
(combinedKey) => {
@@ -432,13 +363,7 @@ context('licensing tests: ', function () {
timeout: longerCommandTimeout
}).click()
cy.wait(2000)
acceptTermsIfPresented((termsResult: boolean) => {
if (termsResult) {
cy.wait(10000)
}
visitPage('home')
cy.get('.nav-tree clr-tree > clr-tree-node', {
@@ -452,10 +377,7 @@ context('licensing tests: ', function () {
// See test 7's own comment for why proceed() is bridged through
// cy.then() rather than called as a plain statement.
if (!result) {
visitPage('licensing/update')
cy.wait(2000)
}
gotoLicensingUpdate()
cy.then(() => proceed())
})
})
@@ -476,15 +398,15 @@ context('licensing tests: ', function () {
}
generateKeys(keyData, (keysGen: any) => {
cy.wait(2000)
// Navigate there explicitly rather than only acting when already on
// the licensing page - see test 7's own comment for why.
isLicensingPage((result: boolean) => {
cy.then(() => {
const proceed = () =>
generateCombinedKey(keysGen.licenseKey, keysGen.activationKey).then(
(combinedKey) => {
cy.get('button').contains('Paste licence').click()
cy.contains('button', 'Paste licence', {
timeout: longerCommandTimeout
}).click()
cy.get('.combined-key-form textarea', {
timeout: longerCommandTimeout
@@ -522,12 +444,8 @@ context('licensing tests: ', function () {
// See test 7's own comment for why this is chained via .then()
// rather than called as the next plain statement.
if (!result) {
visitPage('licensing/update')
cy.wait(2000).then(() => proceed())
} else {
proceed()
}
gotoLicensingUpdate()
cy.then(() => proceed())
})
})
})
@@ -550,22 +468,10 @@ context('licensing tests: ', function () {
}
generateKeys(keyData, (keysGen: any) => {
generateKeys(keyData2, (keysGen2: any) => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen2, () => {
cy.wait(2000)
const random = Cypress._.random(0, 1000)
const newUser = {
username: `randomusername${random}notregistered`,
@@ -583,8 +489,6 @@ context('licensing tests: ', function () {
visitPage('home')
cy.wait(2000)
verifyLicensingPage(
'The registered number of users reached the limit specified for your licence.',
(success: boolean) => {
@@ -620,17 +524,9 @@ context('licensing tests: ', function () {
generateKeys(keyData, (keysGen: any) => {
generateKeys(keyData2, (keysGen2: any) => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
updateLicenseKeyQuick(keysGen, () => {
cy.wait(2000)
acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
const random = Cypress._.random(0, 1000)
const newUser = {
username: `randomusername${random}`,
@@ -641,7 +537,6 @@ context('licensing tests: ', function () {
{ deleteAll: true, keep: username, newUsers: [newUser] },
() => {
updateLicenseKeyQuick(keysGen2, () => {
cy.wait(2000)
verifyLicensingWarning(
'The registered number of users exceeds the limit specified for your license.',
() => {
@@ -660,6 +555,27 @@ context('licensing tests: ', function () {
}
})
// A key change makes the app re-bootstrap its licence state, rendering
// .app-loading and then one of these states. Waiting for it to settle replaces
// the 2s/10s/20s sleeps that used to stand in for it, and costs nothing when
// the app is already there.
const waitForAppSettled = () => {
cy.get('.app-loading', { timeout: longerCommandTimeout }).should('not.exist')
cy.get('.nav-tree, #demo-banner, .key-error, input.username', {
timeout: longerCommandTimeout
}).should('exist')
}
// The paste form exists only on licensing/update. Asking "are we anywhere
// under licensing/" also matches the error page the app shows when the current
// key is bad, which has no paste button at all.
const gotoLicensingUpdate = () => {
waitForAppSettled()
cy.location('hash').then((hash: string) => {
if (!hash.includes('licensing/update')) visitPage('licensing/update')
})
}
const logout = (callback?: any) => {
cy.get('.header-actions .dropdown-toggle')
.click()
@@ -681,6 +597,9 @@ const acceptTermsIfPresented = (callback?: any) => {
.then(() => {
cy.get('#checkbox1')
.click()
.then(() => {
waitForAppSettled()
})
.then(() => {
if (callback) callback(true)
})
@@ -700,25 +619,26 @@ const isLicensingPage = (callback: any) => {
}
const verifyLicensingPage = (text: string, callback: any) => {
// visitPage('home')
// The app redirects to the licensing page when the key is bad; waiting for
// that URL is what the sleep-then-check-once version was guessing at, and a
// redirect that never comes fails here instead of hanging the test.
cy.location('hash', { timeout: longerCommandTimeout }).should(
'match',
/licensing\/(?!register)/
)
cy.wait(1000)
isLicensingPage((result: boolean) => {
if (result) {
cy.get('.key-error')
.should('contain', text)
.then((treeNodes: any) => {
callback(true)
})
}
})
cy.get('.key-error', { timeout: longerCommandTimeout })
.should('contain', text)
.then(() => {
callback(true)
})
}
const verifyLicensingWarning = (text: string, callback: any) => {
visitPage('home')
cy.wait(1000)
cy.get("div[role='alert'] .alert-text")
waitForAppSettled()
cy.get("div[role='alert'] .alert-text", { timeout: longerCommandTimeout })
.invoke('text')
.should('contain', text)
.then(() => {
@@ -727,7 +647,9 @@ const verifyLicensingWarning = (text: string, callback: any) => {
}
const inputLicenseKeyPage = (licenseKey: string, activationKey: string) => {
cy.get('button').contains('Paste licence').click()
cy.contains('button', 'Paste licence', {
timeout: longerCommandTimeout
}).click()
// Defaults to the combined single-field form - this helper exercises the
// two-part path specifically, so switch to it first.
@@ -742,10 +664,13 @@ const inputLicenseKeyPage = (licenseKey: string, activationKey: string) => {
.trigger('input')
.should('not.be.undefined')
cy.get('button.apply-keys').click()
waitForAppSettled()
}
const inputCombinedKeyPage = (combinedKey: string) => {
cy.get('button').contains('Paste licence').click()
cy.contains('button', 'Paste licence', {
timeout: longerCommandTimeout
}).click()
// Combined is the default paste format - no toggle click needed here,
// unlike inputLicenseKeyPage's legacy two-field path.
@@ -815,7 +740,6 @@ const updateUsersTable = (options: any, callback?: any) => {
// .type('{enter}')
// })
submitTable(() => {
cy.wait(2000)
approveTable(callback)
})
})
@@ -838,7 +762,6 @@ const changeLicenseKeyTable = (keys: any, callback?: any) => {
],
() => {
submitTable(() => {
cy.wait(2000)
approveTable(() => {
cy.reload()
if (callback) callback()
@@ -853,7 +776,6 @@ const updateLicenseKeyQuick = (keys: any, callback: any) => {
isLicensingPage((result: boolean) => {
if (!result) {
visitPage('licensing/update')
cy.wait(2000)
}
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)