Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cypress): improve cypress performances #47379

Merged
merged 6 commits into from
Aug 22, 2024
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/cypress.yml
skjnldsv marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
npmVersion: ${{ steps.versions.outputs.npmVersion }}

env:
# We'll install cypress in the cypress job
CYPRESS_INSTALL_BINARY: 0
PUPPETEER_SKIP_DOWNLOAD: true

steps:
Expand Down Expand Up @@ -115,9 +117,14 @@ jobs:
- name: Set up npm ${{ needs.init.outputs.npmVersion }}
run: npm i -g 'npm@${{ needs.init.outputs.npmVersion }}'

- name: Install cypress
run: ./node_modules/cypress/bin/cypress install

- name: Run ${{ matrix.containers == 'component' && 'component' || 'E2E' }} cypress tests
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
with:
# We already installed the dependencies in the init job
install: false
component: ${{ matrix.containers == 'component' }}
group: ${{ matrix.use-cypress-cloud && matrix.containers == 'component' && 'Run component' || matrix.use-cypress-cloud && 'Run E2E' || '' }}
# cypress env
Expand Down
53 changes: 44 additions & 9 deletions cypress/dockerNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc
// https://github.com/apocas/dockerode/issues/357
docker.modem.followProgress(stream, onFinished)

/**
*
* @param err
*/
function onFinished(err) {
if (!err) {
resolve(true)
Expand Down Expand Up @@ -82,14 +78,27 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc
Image: SERVER_IMAGE,
name: CONTAINER_NAME,
HostConfig: {
Binds: [],
Mounts: [{
Target: '/var/www/html/data',
Source: '',
Type: 'tmpfs',
ReadOnly: false,
}],
},
Env: [
`BRANCH=${branch}`,
'APCU=1',
],
})
await container.start()

// Set proper permissions for the data folder
await runExec(container, ['chown', '-R', 'www-data:www-data', '/var/www/html/data'], false, 'root')
await runExec(container, ['chmod', '0770', '/var/www/html/data'], false, 'root')

// Init Nextcloud
// await runExec(container, ['initnc.sh'], true, 'root')

// Get container's IP
const ip = await getContainerIP(container)

Expand Down Expand Up @@ -117,9 +126,28 @@ export const configureNextcloud = async function() {
await runExec(container, ['php', 'occ', 'config:system:set', 'default_locale', '--value', 'en_US'], true)
await runExec(container, ['php', 'occ', 'config:system:set', 'force_locale', '--value', 'en_US'], true)
await runExec(container, ['php', 'occ', 'config:system:set', 'enforce_theme', '--value', 'light'], true)

// Speed up test and make them less flaky. If a cron execution is needed, it can be triggered manually.
await runExec(container, ['php', 'occ', 'background:cron'], true)

// Checking apcu
const distributed = await runExec(container, ['php', 'occ', 'config:system:get', 'memcache.distributed'])
const local = await runExec(container, ['php', 'occ', 'config:system:get', 'memcache.local'])
const hashing = await runExec(container, ['php', 'occ', 'config:system:get', 'hashing_default_password'])

console.log('├─ Checking APCu configuration... 👀')
if (!distributed.trim().includes('Memcache\\APCu')
|| !local.trim().includes('Memcache\\APCu')
|| !hashing.trim().includes('true')) {
console.log('└─ APCu is not properly configured 🛑')
throw new Error('APCu is not properly configured')
}
console.log('│ └─ OK !')

// Saving DB state
console.log('├─ Creating init DB snapshot...')
await runExec(container, ['cp', '/var/www/html/data/owncloud.db', '/var/www/html/data/owncloud.db-init'], true)

console.log('└─ Nextcloud is now ready to use 🎉')
}

Expand Down Expand Up @@ -249,7 +277,7 @@ const runExec = async function(
command: string[],
verbose = false,
user = 'www-data',
) {
): Promise<string> {
const exec = await container.exec({
Cmd: command,
AttachStdout: true,
Expand All @@ -258,18 +286,25 @@ const runExec = async function(
})

return new Promise((resolve, reject) => {
let output = ''
exec.start({}, (err, stream) => {
if (err) {
reject(err)
}
if (stream) {
stream.setEncoding('utf-8')
stream.on('data', str => {
if (verbose && str.trim() !== '') {
console.log(`├─ ${str.trim().replace(/\n/gi, '\n├─ ')}`)
str = str.trim()
// Remove non printable characters
.replace(/[^\x20-\x7E]+/g, '')
// Remove non alphanumeric leading characters
.replace(/^[^a-z]/gi, '')
output += str
if (verbose && str !== '') {
console.log(`├─ ${str.replace(/\n/gi, '\n├─ ')}`)
}
})
stream.on('end', resolve)
stream.on('end', () => resolve(output))
}
})
})
Expand Down
27 changes: 20 additions & 7 deletions cypress/e2e/settings/personal-info.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,23 @@ const genericProperties = ['Location', 'X (formerly Twitter)', 'Fediverse']
const nonfederatedProperties = ['Organisation', 'Role', 'Headline', 'About']

describe('Settings: Change personal information', { testIsolation: true }, () => {
let snapshot: string = ''

before(() => {
// ensure we can set locale and language
cy.runOccCommand('config:system:delete force_language')
cy.runOccCommand('config:system:delete force_locale')
cy.createRandomUser().then(($user) => {
user = $user
cy.modifyUser(user, 'language', 'en')
cy.modifyUser(user, 'locale', 'en_US')
})

cy.wait(500)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seem arbitrary. Can't we wait for something more specific?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we'll standardise this a bit more in a followup.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?
As soon as this part is reached the create user request has terminated so all database actions are already finished as the PHP process is done.
So normally this should not be needed I guess?


cy.backupDB().then(($snapshot) => {
snapshot = $snapshot
})
})

after(() => {
Expand All @@ -115,30 +127,31 @@ describe('Settings: Change personal information', { testIsolation: true }, () =>
})

beforeEach(() => {
cy.createRandomUser().then(($user) => {
user = $user
cy.modifyUser(user, 'language', 'en')
cy.modifyUser(user, 'locale', 'en_US')
cy.login($user)
cy.visit('/settings/user')
})
cy.login(user)
cy.visit('/settings/user')
cy.intercept('PUT', /ocs\/v2.php\/cloud\/users\//).as('submitSetting')
})

afterEach(() => {
cy.restoreDB(snapshot)
})

it('Can dis- and enable the profile', () => {
cy.visit(`/u/${user.userId}`)
cy.contains('h2', user.userId).should('be.visible')

cy.visit('/settings/user')
cy.contains('Enable profile').click()
handlePasswordConfirmation(user.password)
cy.wait('@submitSetting')

cy.visit(`/u/${user.userId}`, { failOnStatusCode: false })
cy.contains('h2', 'Profile not found').should('be.visible')

cy.visit('/settings/user')
cy.contains('Enable profile').click()
handlePasswordConfirmation(user.password)
cy.wait('@submitSetting')

cy.visit(`/u/${user.userId}`, { failOnStatusCode: false })
cy.contains('h2', user.userId).should('be.visible')
Expand Down
23 changes: 23 additions & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ declare global {
* Run an occ command in the docker container.
*/
runOccCommand(command: string, options?: Partial<Cypress.ExecOptions>): Cypress.Chainable<Cypress.Exec>,

/**
* Create a snapshot of the current database
*/
backupDB(): Cypress.Chainable<string>,

/**
* Restore a snapshot of the database
* Default is the post-setup state
*/
restoreDB(snapshot?: string): Cypress.Chainable,
}
}
}
Expand Down Expand Up @@ -276,3 +287,15 @@ Cypress.Commands.add('runOccCommand', (command: string, options?: Partial<Cypres
const env = Object.entries(options?.env ?? {}).map(([name, value]) => `-e '${name}=${value}'`).join(' ')
return cy.exec(`docker exec --user www-data ${env} nextcloud-cypress-tests-server php ./occ ${command}`, options)
})

Cypress.Commands.add('backupDB', (): Cypress.Chainable<string> => {
const randomString = Math.random().toString(36).substring(7)
cy.exec(`docker exec --user www-data nextcloud-cypress-tests-server cp /var/www/html/data/owncloud.db /var/www/html/data/owncloud.db-${randomString}`)
cy.log(`Created snapshot ${randomString}`)
return cy.wrap(randomString)
})

Cypress.Commands.add('restoreDB', (snapshot: string = 'init') => {
cy.exec(`docker exec --user www-data nextcloud-cypress-tests-server cp /var/www/html/data/owncloud.db-${snapshot} /var/www/html/data/owncloud.db`)
cy.log(`Restored snapshot ${snapshot}`)
})
Loading