loginAndUpdateValidKey decided whether a key was already working by looking for the nav tree, and looked for it in the body captured before login. A free tier estate renders the tree as well, so on an unlicensed estate the command took the 'already licensed' branch every time and never applied a key. Every upload spec then lost its feature set: onShowUploadModal shows the locked feature dialog instead of the panel, so input#file-upload never renders. The loaded page is read again after login, the licence banner decides whether a key is needed, and an estate with no key is left untouched unless the caller asked for one - the free tier specs are written against that state. Measured on an unlicensed mock estate: excel.cy.ts 24 failing in 14m 1s before, 24 passing in 5m 44s after; csv.cy.ts 1 failing before, 1 passing after.
288 lines
8.0 KiB
TypeScript
288 lines
8.0 KiB
TypeScript
// ***********************************************
|
|
// 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) => { ... })
|
|
import 'cypress-file-upload'
|
|
|
|
import { arrayBufferToBase64 } from './../util/helper-functions'
|
|
import moment from 'moment'
|
|
|
|
// These custom commands were added via Cypress.Commands.add() below but never
|
|
// had a type augmentation, so every spec calling cy.loginAndUpdateValidKey()/
|
|
// cy.isLoggedIn() had an unresolved-property error.
|
|
declare global {
|
|
namespace Cypress {
|
|
interface Chainable {
|
|
isLoggedIn(callback: (exist: boolean) => void): Chainable<void>
|
|
loginAndUpdateValidKey(forceLicenceKey?: boolean): Chainable<void>
|
|
}
|
|
}
|
|
}
|
|
|
|
const username = Cypress.env('username')
|
|
const password = Cypress.env('password')
|
|
const hostUrl = Cypress.env('hosturl')
|
|
const appLocation = Cypress.env('appLocation')
|
|
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
|
const site_id_SASJS = Cypress.env('site_id_SASJS')
|
|
|
|
Cypress.Commands.add('isLoggedIn', (callback: (exist: boolean) => void) => {
|
|
cy.get('body').then(($body) => {
|
|
if ($body.find('.nav-tree').length > 0) {
|
|
if (callback) callback(true)
|
|
} else {
|
|
if (callback) callback(false)
|
|
}
|
|
})
|
|
})
|
|
|
|
Cypress.Commands.add(
|
|
'loginAndUpdateValidKey',
|
|
(forceLicenceKey: boolean = false) => {
|
|
cy.visit(hostUrl + appLocation)
|
|
|
|
cy.wait(2000)
|
|
|
|
cy.get('body').then(($body) => {
|
|
const usernameInput = $body.find('input.username')[0]
|
|
|
|
if (usernameInput && !Cypress.dom.isHidden(usernameInput)) {
|
|
cy.get('input.username').type(username)
|
|
cy.get('input.password').type(password)
|
|
|
|
cy.get('.login-group button').click()
|
|
}
|
|
|
|
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
|
.should('not.exist')
|
|
.then(() => {
|
|
cy.wait(2000)
|
|
|
|
/**
|
|
* A key that is already working needs no update. Ask the loaded
|
|
* page, not the tree: a free tier estate renders the nav tree as
|
|
* well, so the tree cannot tell a licensed estate from an
|
|
* unlicensed one - the licence banner is what does. Reading the
|
|
* body again here also matters on an estate that shows a login
|
|
* form, where the body captured above has no tree yet.
|
|
*/
|
|
cy.get('body').then(($loaded) => {
|
|
const licenceProblem = $loaded.find('#demo-banner').length > 0
|
|
|
|
if (!licenceProblem && $loaded.find('.nav-tree').length > 0) {
|
|
return logout(() => {
|
|
return
|
|
})
|
|
}
|
|
|
|
/**
|
|
* An estate with no key is left as it is unless the caller asked
|
|
* for a licensed one - the free tier specs are written against
|
|
* that state and must not have a key planted under them.
|
|
*/
|
|
if (!forceLicenceKey) {
|
|
return logout(() => {
|
|
return
|
|
})
|
|
}
|
|
|
|
const keyData = {
|
|
valid_until: moment().add(20, 'day').format('YYYY-MM-DD'),
|
|
number_of_users: 10,
|
|
hot_license_key: '',
|
|
site_id: '',
|
|
demo: false
|
|
}
|
|
|
|
return generateKeys(
|
|
keyData.valid_until,
|
|
keyData.number_of_users,
|
|
keyData.hot_license_key,
|
|
keyData.demo,
|
|
(keysGen: any) => {
|
|
return acceptTermsIfPresented((result: boolean) => {
|
|
if (result) {
|
|
cy.wait(20000)
|
|
}
|
|
|
|
return updateLicenseKeyQuick(keysGen, () => {
|
|
cy.wait(25000)
|
|
return acceptTermsIfPresented((result: boolean) => {
|
|
if (result) {
|
|
cy.wait(20000)
|
|
}
|
|
return logout(() => {
|
|
return
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
)
|
|
|
|
const logout = (callback?: any) => {
|
|
cy.get('.header-actions .dropdown-toggle')
|
|
.click()
|
|
.then(() => {
|
|
cy.get('.header-actions .dropdown-menu > .separator')
|
|
.next()
|
|
.click()
|
|
.then(() => {
|
|
if (callback) callback()
|
|
})
|
|
})
|
|
}
|
|
|
|
const updateLicenseKeyQuick = (keys: any, callback: any) => {
|
|
isLicensingPage((result: boolean) => {
|
|
if (!result) {
|
|
visitPage('licensing/update')
|
|
cy.wait(2000)
|
|
}
|
|
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
|
|
|
|
callback()
|
|
})
|
|
}
|
|
|
|
const acceptTermsIfPresented = (callback?: any) => {
|
|
cy.url().then((url: string) => {
|
|
if (url.includes('licensing/register')) {
|
|
cy.get('.card-block')
|
|
.scrollTo('bottom')
|
|
.then(() => {
|
|
cy.get('#checkbox1')
|
|
.click()
|
|
.then(() => {
|
|
if (callback) callback(true)
|
|
})
|
|
})
|
|
} else {
|
|
if (callback) callback(false)
|
|
}
|
|
})
|
|
}
|
|
|
|
const isLicensingPage = (callback: any) => {
|
|
cy.url().then((url: string) => {
|
|
callback(url.includes('licensing') && !url.includes('licensing/register'))
|
|
})
|
|
}
|
|
|
|
const inputLicenseKeyPage = (licenseKey: string, activationKey: string) => {
|
|
cy.get('button').contains('Paste licence').click()
|
|
|
|
// Combined single-string paste is the default format now - switch to
|
|
// the legacy two-field layout, since this helper fills licenseKey and
|
|
// activationKey as two separate values.
|
|
cy.get('button').contains('Paste as two separate keys instead').click()
|
|
|
|
cy.get('.license-key-form textarea', { timeout: longerCommandTimeout })
|
|
.invoke('val', licenseKey)
|
|
.trigger('input')
|
|
.should('not.be.undefined')
|
|
cy.get('.activation-key-form textarea', { timeout: longerCommandTimeout })
|
|
.invoke('val', activationKey)
|
|
.trigger('input')
|
|
.should('not.be.undefined')
|
|
cy.get('button.apply-keys').click()
|
|
}
|
|
|
|
const visitPage = (url: string) => {
|
|
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
|
}
|
|
const generateKeys = async (
|
|
valid_until: string,
|
|
users_allowed: number,
|
|
hot_license_key: string,
|
|
demo: boolean,
|
|
resultCallback?: any
|
|
) => {
|
|
let keyPair = await window.crypto.subtle.generateKey(
|
|
{
|
|
name: 'RSA-OAEP',
|
|
modulusLength: 2024,
|
|
publicExponent: new Uint8Array([1, 0, 1]),
|
|
hash: 'SHA-256'
|
|
},
|
|
true,
|
|
['encrypt', 'decrypt']
|
|
)
|
|
|
|
let licenseData = {
|
|
valid_until: valid_until,
|
|
users_allowed: users_allowed,
|
|
hot_license_key: hot_license_key,
|
|
site_id: site_id_SASJS,
|
|
demo: demo
|
|
}
|
|
|
|
console.log('License data', licenseData)
|
|
|
|
let encoded = new TextEncoder().encode(JSON.stringify(licenseData))
|
|
|
|
console.log(encoded)
|
|
|
|
let cipher = await window.crypto.subtle
|
|
.encrypt(
|
|
{
|
|
name: 'RSA-OAEP'
|
|
},
|
|
keyPair.publicKey,
|
|
encoded
|
|
)
|
|
.then(
|
|
(value) => {
|
|
return value
|
|
},
|
|
(err) => {
|
|
console.log('Encrpyt error', err)
|
|
}
|
|
)
|
|
|
|
if (!cipher) {
|
|
alert('Encryptin keys failed')
|
|
throw new Error('Encryptin keys failed')
|
|
}
|
|
|
|
let privateKeyBytes = await window.crypto.subtle.exportKey(
|
|
'pkcs8',
|
|
keyPair.privateKey
|
|
)
|
|
|
|
let activationKey = await arrayBufferToBase64(privateKeyBytes)
|
|
let licenseKey = await arrayBufferToBase64(cipher)
|
|
|
|
if (resultCallback)
|
|
resultCallback({
|
|
activationKey,
|
|
licenseKey
|
|
})
|
|
}
|