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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ jobs:

- name: Extract NC logs
if: failure() && matrix.containers != 'component'
run: docker logs nextcloud-cypress-tests-${{ env.APP_NAME }} > nextcloud.log
run: docker logs nextcloud-cypress-tests_${{ env.APP_NAME }} > nextcloud.log

- name: Upload NC logs
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
Expand All @@ -142,7 +142,7 @@ jobs:

- name: Create data dir archive
if: failure() && matrix.containers != 'component'
run: docker exec nextcloud-cypress-tests-server tar -cvjf - data > data.tar
run: docker exec nextcloud-cypress-tests_${{ env.APP_NAME }} tar -cvjf - data > data.tar

- name: Upload data dir archive
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<NcActionButton v-for="action in enabledActions"
:key="action.id"
:class="'files-list__row-actions-batch-' + action.id"
:data-cy-files-list-selection-action="action.id"
@click="onActionClick(action)">
<template #icon>
<NcLoadingIcon v-if="loading === action.id" :size="18" />
Expand Down
13 changes: 13 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ export default defineConfig({

on('task', { removeDirectory })

// This allows to store global data (e.g. the name of a snapshot)
// because Cypress.env() and other options are local to the current spec file.
const data = {}
on('task', {
setVariable({ key, value }) {
data[key] = value
return null
},
getVariable({ key }) {
return data[key] ?? null
},
})

// Disable spell checking to prevent rendering differences
on('before:browser:launch', (browser, launchOptions) => {
if (browser.family === 'chromium' && browser.name !== 'electron') {
Expand Down
3 changes: 2 additions & 1 deletion cypress/dockerNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@
import Docker from 'dockerode'
import waitOn from 'wait-on'
import tar from 'tar'
import { basename } from 'path'
import { execSync } from 'child_process'

export const docker = new Docker()

const CONTAINER_NAME = 'nextcloud-cypress-tests-server'
const CONTAINER_NAME = `nextcloud-cypress-tests_${basename(process.cwd()).replace(' ', '')}`
const SERVER_IMAGE = 'ghcr.io/nextcloud/continuous-integration-shallow-server'

/**
Expand Down
107 changes: 107 additions & 0 deletions cypress/e2e/files/LivePhotosUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { User } from '@nextcloud/cypress'

type SetupInfo = {
snapshot: string
jpgFileId: number
movFileId: number
fileName: string
user: User
}

/**
*
* @param user
* @param fileName
* @param domain
* @param requesttoken
* @param metadata
*/
function setMetadata(user: User, fileName: string, requesttoken: string, metadata: object) {
cy.url().then(url => {
const hostname = new URL(url).hostname
cy.request({
method: 'PROPPATCH',
url: `http://${hostname}/remote.php/dav/files/${user.userId}/${fileName}`,
auth: { user: user.userId, pass: user.password },
headers: {
requesttoken,
},
body: `<?xml version="1.0"?>
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns">
<d:set>
<d:prop>
${Object.entries(metadata).map(([key, value]) => `<${key}>${value}</${key}>`).join('\n')}
</d:prop>
</d:set>
</d:propertyupdate>`,
})
})

}

/**
*
* @param enable
*/
export function setShowHiddenFiles(enable: boolean) {
cy.get('[data-cy-files-navigation-settings-button]').click()
// Force:true because the checkbox is hidden by the pretty UI.
if (enable) {
cy.get('[data-cy-files-settings-setting="show_hidden"] input').check({ force: true })
} else {
cy.get('[data-cy-files-settings-setting="show_hidden"] input').uncheck({ force: true })
}
cy.get('[data-cy-files-navigation-settings]').type('{esc}')
}

/**
*
*/
export function setupLivePhotos(): Cypress.Chainable<SetupInfo> {
return cy.task('getVariable', { key: 'live-photos-data' })
.then((_setupInfo) => {
const setupInfo = _setupInfo as SetupInfo || {}
if (setupInfo.snapshot) {
cy.restoreState(setupInfo.snapshot)
} else {
let requesttoken: string

setupInfo.fileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10)

cy.createRandomUser().then(_user => { setupInfo.user = _user })

cy.then(() => {
cy.uploadContent(setupInfo.user, new Blob(['jpg file'], { type: 'image/jpg' }), 'image/jpg', `/${setupInfo.fileName}.jpg`)
.then(response => { setupInfo.jpgFileId = parseInt(response.headers['oc-fileid']) })
cy.uploadContent(setupInfo.user, new Blob(['mov file'], { type: 'video/mov' }), 'video/mov', `/${setupInfo.fileName}.mov`)
.then(response => { setupInfo.movFileId = parseInt(response.headers['oc-fileid']) })

cy.login(setupInfo.user)
})

cy.visit('/apps/files')

cy.get('head').invoke('attr', 'data-requesttoken').then(_requesttoken => { requesttoken = _requesttoken as string })

cy.then(() => {
setMetadata(setupInfo.user, `${setupInfo.fileName}.jpg`, requesttoken, { 'nc:metadata-files-live-photo': setupInfo.movFileId })
setMetadata(setupInfo.user, `${setupInfo.fileName}.mov`, requesttoken, { 'nc:metadata-files-live-photo': setupInfo.jpgFileId })
})

cy.then(() => {
cy.saveState().then((value) => { setupInfo.snapshot = value })
cy.task('setVariable', { key: 'live-photos-data', value: setupInfo })
})
}
return cy.then(() => {
cy.login(setupInfo.user)
cy.visit('/apps/files')
return cy.wrap(setupInfo)
})
})
}
91 changes: 23 additions & 68 deletions cypress/e2e/files/live_photos.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,75 +21,34 @@
*/

import type { User } from '@nextcloud/cypress'
import { clickOnBreadcrumbs, closeSidebar, copyFile, getRowForFile, getRowForFileId, renameFile, triggerActionForFile, triggerInlineActionForFileId } from './FilesUtils'

/**
*
* @param user
* @param fileName
* @param domain
* @param requesttoken
* @param metadata
*/
function setMetadata(user: User, fileName: string, domain: string, requesttoken: string, metadata: object) {
cy.request({
method: 'PROPPATCH',
url: `http://${domain}/remote.php/dav/files/${user.userId}/${fileName}`,
auth: { user: user.userId, pass: user.password },
headers: {
requesttoken,
},
body: `<?xml version="1.0"?>
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns">
<d:set>
<d:prop>
${Object.entries(metadata).map(([key, value]) => `<${key}>${value}</${key}>`).join('\n')}
</d:prop>
</d:set>
</d:propertyupdate>`,
})
}
import {
clickOnBreadcrumbs,
copyFile,
createFolder,
getRowForFile,
getRowForFileId,
moveFile,
navigateToFolder,
renameFile,
triggerActionForFile,
triggerInlineActionForFileId,
} from './FilesUtils'
import { setShowHiddenFiles, setupLivePhotos } from './LivePhotosUtils'

describe('Files: Live photos', { testIsolation: true }, () => {
let currentUser: User
let user: User
let randomFileName: string
let jpgFileId: number
let movFileId: number
let hostname: string
let requesttoken: string

before(() => {
cy.createRandomUser().then((user) => {
currentUser = user
cy.login(currentUser)
cy.visit('/apps/files')
})

cy.url().then(url => { hostname = new URL(url).hostname })
})

beforeEach(() => {
randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10)

cy.uploadContent(currentUser, new Blob(['jpg file'], { type: 'image/jpg' }), 'image/jpg', `/${randomFileName}.jpg`)
.then(response => { jpgFileId = parseInt(response.headers['oc-fileid']) })
cy.uploadContent(currentUser, new Blob(['mov file'], { type: 'video/mov' }), 'video/mov', `/${randomFileName}.mov`)
.then(response => { movFileId = parseInt(response.headers['oc-fileid']) })

cy.login(currentUser)
cy.visit('/apps/files')

cy.get('head').invoke('attr', 'data-requesttoken').then(_requesttoken => { requesttoken = _requesttoken as string })

cy.then(() => {
setMetadata(currentUser, `${randomFileName}.jpg`, hostname, requesttoken, { 'nc:metadata-files-live-photo': movFileId })
setMetadata(currentUser, `${randomFileName}.mov`, hostname, requesttoken, { 'nc:metadata-files-live-photo': jpgFileId })
})

cy.then(() => {
cy.visit(`/apps/files/files/${jpgFileId}`) // Refresh and scroll to the .jpg file.
closeSidebar()
})
setupLivePhotos()
.then((setupInfo) => {
user = setupInfo.user
randomFileName = setupInfo.fileName
jpgFileId = setupInfo.jpgFileId
movFileId = setupInfo.movFileId
})
})

it('Only renders the .jpg file', () => {
Expand All @@ -98,12 +57,8 @@ describe('Files: Live photos', { testIsolation: true }, () => {
})

context("'Show hidden files' is enabled", () => {
before(() => {
cy.login(currentUser)
cy.visit('/apps/files')
cy.get('[data-cy-files-navigation-settings-button]').click()
// Force:true because the checkbox is hidden by the pretty UI.
cy.get('[data-cy-files-settings-setting="show_hidden"] input').check({ force: true })
beforeEach(() => {
setShowHiddenFiles(true)
})

it("Shows both files when 'Show hidden files' is enabled", () => {
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/files_versions/version_expiration.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('Versions expiration', () => {
})

it('Expire all versions', () => {
cy.runOccCommand('config:system:set versions_retention_obligation --value "0, 0"')
cy.runOccCommand("config:system:set versions_retention_obligation --value '0, 0'")
cy.runOccCommand('versions:expire')
cy.runOccCommand('config:system:set versions_retention_obligation --value auto')
cy.visit('/apps/files')
Expand All @@ -55,7 +55,7 @@ describe('Versions expiration', () => {
it('Expire versions v2', () => {
nameVersion(2, 'v1')

cy.runOccCommand('config:system:set versions_retention_obligation --value "0, 0"')
cy.runOccCommand("config:system:set versions_retention_obligation --value '0, 0'")
cy.runOccCommand('versions:expire')
cy.runOccCommand('config:system:set versions_retention_obligation --value auto')
cy.visit('/apps/files')
Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/theming/a11y-color-contrast.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe('Accessibility of Nextcloud theming colors', () => {
before(() => {
cy.createRandomUser().then(($user) => {
// set user theme
cy.runOccCommand(`user:setting -- '${$user.userId}' theming enabled-themes '["${theme}"]'`)
cy.runOccCommand(`user:setting -- '${$user.userId}' theming enabled-themes '[\\"${theme}\\"]'`)
cy.login($user)
cy.visit('/')
cy.injectAxe({ axeCorePath: 'node_modules/axe-core/axe.min.js' })
Expand Down
10 changes: 0 additions & 10 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,6 @@ declare global {
* **Warning**: Providing a user will reset the previous session.
*/
resetUserTheming(user?: User): Cypress.Chainable<void>,

/**
* Run an occ command in the docker container.
*/
runOccCommand(command: string, options?: Partial<Cypress.ExecOptions>): Cypress.Chainable<Cypress.Exec>,
}
}
}
Expand Down Expand Up @@ -292,8 +287,3 @@ Cypress.Commands.add('resetUserTheming', (user?: User) => {
cy.clearCookies()
}
})

Cypress.Commands.add('runOccCommand', (command: string, options?: Partial<Cypress.ExecOptions>) => {
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)
})
27 changes: 24 additions & 3 deletions cypress/support/commonUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { basename } from 'path'

/**
* Get the header navigation bar
*/
Expand Down Expand Up @@ -44,8 +51,12 @@ export function installTestApp() {
cy.runOccCommand('-V').then((output) => {
const version = output.stdout.match(/(\d\d+)\.\d+\.\d+/)?.[1]
cy.wrap(version).should('not.be.undefined')
cy.exec(`docker cp '${testAppPath}' nextcloud-cypress-tests-server:/var/www/html/apps`, { log: true })
cy.exec(`docker exec nextcloud-cypress-tests-server sed -i -e 's|-version="[0-9]\\+|-version="${version}|g' apps/testapp/appinfo/info.xml`)
getContainerName()
.then(containerName => {
cy.exec(`docker cp '${testAppPath}' ${containerName}:/var/www/html/apps`, { log: true })
cy.exec(`docker exec --workdir /var/www/html ${containerName} chown -R www-data:www-data /var/www/html/apps/testapp`)
})
cy.runCommand(`sed -i -e 's|-version=\\"[0-9]\\+|-version=\\"${version}|g' apps/testapp/appinfo/info.xml`)
cy.runOccCommand('app:enable --force testapp')
})
}
Expand All @@ -55,5 +66,15 @@ export function installTestApp() {
*/
export function uninstallTestApp() {
cy.runOccCommand('app:remove testapp', { failOnNonZeroExit: false })
cy.exec('docker exec nextcloud-cypress-tests-server rm -fr apps/testapp/appinfo/info.xml')
cy.runCommand('rm -fr apps/testapp/appinfo/info.xml')
}

/**
*
*/
export function getContainerName(): Cypress.Chainable<string> {
return cy.exec('pwd')
.then(({ stdout }) => {
return cy.wrap(`nextcloud-cypress-tests_${basename(stdout).replace(' ', '')}`)
})
}
4 changes: 2 additions & 2 deletions dist/files-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/files-main.js.map

Large diffs are not rendered by default.

Loading