diff --git a/cypress/e2e/api/SessionApi.spec.js b/cypress/e2e/api/SessionApi.spec.js index 40665aa75e0..c459dc72f3f 100644 --- a/cypress/e2e/api/SessionApi.spec.js +++ b/cypress/e2e/api/SessionApi.spec.js @@ -34,12 +34,10 @@ describe('The session Api', function() { before(function() { cy.createUser(user) - cy.prepareWindowForSessionApi() }) beforeEach(function() { cy.login(user) - cy.prepareSessionApi() }) describe('open the session', function() { @@ -223,7 +221,6 @@ describe('The session Api', function() { }) .then(() => cy.clearCookies()) .then(() => { - cy.prepareSessionApi() return cy.createTextSession(undefined, { filePath: '', shareToken }) .then(con => { connection = con @@ -247,7 +244,6 @@ describe('The session Api', function() { .should('be.at.least', 1) cy.save(connection, { version: 1, autosaveContent: '# Heading 1', manualSave: true }) cy.login(user) - cy.prepareSessionApi() cy.downloadFile('saves.md') .its('data') .should('eql', '# Heading 1') @@ -290,7 +286,6 @@ describe('The session Api', function() { cy.log(token) shareToken = token cy.clearCookies() - cy.prepareSessionApi() cy.createTextSession(undefined, { filePath: '', shareToken }) .then(con => { connection = con diff --git a/cypress/e2e/api/SyncServiceProvider.spec.js b/cypress/e2e/api/SyncServiceProvider.spec.js index 545aaff47b2..06b63b78e7c 100644 --- a/cypress/e2e/api/SyncServiceProvider.spec.js +++ b/cypress/e2e/api/SyncServiceProvider.spec.js @@ -32,12 +32,10 @@ describe('Sync service provider', function() { before(function() { cy.createUser(user) - cy.prepareWindowForSessionApi() }) beforeEach(function() { cy.login(user) - cy.prepareSessionApi() cy.uploadTestFile('test.md') .then(id => { fileId = id diff --git a/cypress/e2e/api/UsersApi.spec.js b/cypress/e2e/api/UsersApi.spec.js index 9b21899e3fc..c39ebf7d7d6 100644 --- a/cypress/e2e/api/UsersApi.spec.js +++ b/cypress/e2e/api/UsersApi.spec.js @@ -30,14 +30,12 @@ describe('The user mention API', function() { before(function() { cy.createUser(user) - cy.prepareWindowForSessionApi() }) beforeEach(function() { cy.login(user) cy.uploadTestFile('test.md').as('fileId') .then(cy.createTextSession).as('connection') - cy.getRequestToken() }) afterEach(function() { diff --git a/cypress/e2e/directediting.spec.js b/cypress/e2e/directediting.spec.js index e798bf3f999..9915ad8064d 100644 --- a/cypress/e2e/directediting.spec.js +++ b/cypress/e2e/directediting.spec.js @@ -2,148 +2,63 @@ import { initUserAndFiles, randUser } from '../utils/index.js' const user = randUser() -const createDirectEditingLink = (user, file) => { - cy.login(user) - return cy.request({ - method: 'POST', - url: `${Cypress.env('baseUrl')}/ocs/v2.php/apps/files/api/v1/directEditing/open?format=json`, - form: true, - body: { - path: file, - }, - auth: { user: user.userId, pass: user.password }, - headers: { - 'OCS-ApiRequest': 'true', - 'Content-Type': 'application/x-www-form-urlencoded', - }, - }).then(response => { - cy.log(response) - const token = response.body?.ocs?.data?.url - cy.log(`Created direct editing token for ${user.userId}`, token) - return cy.wrap(token) - }) -} - -const createDirectEditingLinkForNewFile = (user, file) => { - cy.login(user) - return cy.request({ - method: 'POST', - url: `${Cypress.env('baseUrl')}/ocs/v2.php/apps/files/api/v1/directEditing/create?format=json`, - form: true, - body: { - path: file, - editorId: 'text', - creatorId: 'textdocument', - }, - auth: { user: user.userId, pass: user.password }, - headers: { - 'OCS-ApiRequest': 'true', - 'Content-Type': 'application/x-www-form-urlencoded', - }, - }).then(response => { - cy.log(response) - const token = response.body?.ocs?.data?.url - cy.log(`Created direct editing token for ${user.userId}`, token) - return cy.wrap(token) - }) +function enterContentAndClose() { + cy.intercept({ method: 'POST', url: '**/session/*/close' }).as('closeRequest') + cy.intercept({ method: 'POST', url: '**/session/*/push' }).as('push') + cy.intercept({ method: 'POST', url: '**/session/*/sync' }).as('sync') + cy.getContent().type('# This is a headline') + cy.getContent().type('{enter}') + cy.getContent().type('Some text') + cy.getContent().type('{enter}') + cy.getContent().type('{ctrl+s}') + cy.wait('@push') + cy.wait('@sync') + cy.get('button.icon-close').click() + cy.wait('@closeRequest') } describe('direct editing', function() { - const visitHooks = { - onBeforeLoad(win) { - win.DirectEditingMobileInterface = { - close() {}, - } - }, - } before(function() { initUserAndFiles(user, 'test.md', 'empty.md', 'empty.txt') }) it('Open an existing file, edit it', () => { - cy.intercept({ method: 'POST', url: '**/session/*/close' }).as('closeRequest') - cy.intercept({ method: 'POST', url: '**/session/*/push' }).as('push') - cy.intercept({ method: 'POST', url: '**/session/*/sync' }).as('sync') - - createDirectEditingLink(user, 'empty.md') + cy.login(user) + cy.createDirectEditingLink('empty.md') .then((token) => { - cy.logout() - cy.visit(token, visitHooks) - }) - cy.getContent().type('# This is a headline') - cy.getContent().type('{enter}') - cy.getContent().type('Some text') - cy.getContent().type('{enter}') - cy.getContent().type('{ctrl+s}') - - cy.wait('@push') - cy.wait('@sync') - - cy.get('button.icon-close').click() - cy.wait('@closeRequest') - .then(() => { - cy.getFileContent('empty.md').then((content) => { - expect(content).to.equal('# This is a headline\n\nSome text') - }) + cy.session('direct-editing', () => { }) + cy.openDirectEditingToken(token) }) + enterContentAndClose() + cy.login(user) + cy.getFileContent('empty.md') + .should('equal', '# This is a headline\n\nSome text') }) it('Create a file, edit it', () => { - cy.intercept({ method: 'POST', url: '**/session/*/close' }).as('closeRequest') - cy.intercept({ method: 'POST', url: '**/session/*/push' }).as('push') - cy.intercept({ method: 'POST', url: '**/session/*/sync' }).as('sync') - - createDirectEditingLinkForNewFile(user, 'newfile.md') + cy.login(user) + cy.createDirectEditingLinkForNewFile('newfile.md') .then((token) => { - cy.logout() - cy.visit(token, visitHooks) - }) - - cy.getContent().type('# This is a headline') - cy.getContent().type('{enter}') - cy.getContent().type('Some text') - cy.getContent().type('{enter}') - cy.getContent().type('{ctrl+s}') - - cy.wait('@push') - cy.wait('@sync') - - cy.get('button.icon-close').click() - cy.wait('@closeRequest') - .then(() => { - cy.getFileContent('newfile.md').then((content) => { - expect(content).to.equal('# This is a headline\n\nSome text') - }) + cy.session('direct-editing', () => { }) + cy.openDirectEditingToken(token) }) + enterContentAndClose() + cy.login(user) + cy.getFileContent('newfile.md') + .should('equal', '# This is a headline\n\nSome text') }) it('Open an existing plain text file, edit it', () => { - cy.intercept({ method: 'POST', url: '**/session/*/close' }).as('closeRequest') - cy.intercept({ method: 'POST', url: '**/session/*/push' }).as('push') - cy.intercept({ method: 'POST', url: '**/session/*/sync' }).as('sync') - - createDirectEditingLink(user, 'empty.txt') + cy.login(user) + cy.createDirectEditingLink('empty.txt') .then((token) => { - cy.logout() - cy.visit(token, visitHooks) - }) - - cy.getContent().type('# This is a headline') - cy.getContent().type('{enter}') - cy.getContent().type('Some text') - cy.getContent().type('{enter}') - cy.getContent().type('{ctrl+s}') - - cy.wait('@push') - cy.wait('@sync') - - cy.get('button.icon-close').click() - cy.wait('@closeRequest') - .then(() => { - cy.getFileContent('empty.txt').then((content) => { - expect(content).to.equal('# This is a headline\nSome text\n') - }) + cy.session('direct-editing', () => { }) + cy.openDirectEditingToken(token) }) + enterContentAndClose() + cy.login(user) + cy.getFileContent('empty.txt') + .should('equal', '# This is a headline\nSome text\n') }) }) diff --git a/cypress/e2e/propfind.spec.js b/cypress/e2e/propfind.spec.js index a40966f7c09..7ca0929b568 100644 --- a/cypress/e2e/propfind.spec.js +++ b/cypress/e2e/propfind.spec.js @@ -51,7 +51,7 @@ describe('Text PROPFIND extension ', function() { cy.uploadFile('test.md', 'text/markdown', '/Readme.md') cy.propfindFolder('/') .should('have.property', richWorkspace, '## Hello world\n') - cy.removeFile('/Readme.md') + cy.deleteFile('/Readme.md') cy.propfindFolder('/') .should('have.property', richWorkspace, '') }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index e0d9a37fb34..bb6e5c39835 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -21,6 +21,7 @@ */ import axios from '@nextcloud/axios' +import { emit } from '@nextcloud/event-bus' import { addCommands } from '@nextcloud/cypress' import { addCompareSnapshotCommand } from 'cypress-visual-regression/dist/command.js' @@ -33,59 +34,36 @@ const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '') Cypress.env('baseUrl', url) const silent = { log: false } +// prepare main cypress window so we can use axios there +// and it will successfully fetch csrf tokens when needed. +window.OC = { + config: { modRewriteWorking: false }, +} +// Prevent @nextcloud/router from reading window.location +window._oc_webroot = url + addCommands() -// Keep track of the current user authentication. -// We need it for various commands to authenticate -// and also to determine paths, urls and the like. -let auth +// Prepare the csrf-token for axios Cypress.Commands.overwrite('login', (login, user) => { cy.window(silent).then((win) => { win.location.href = 'about:blank' }) - auth = { user: user.userId, password: user.password } - cy.wrap(null).as('requesttoken') login(user) + cy.request('/csrftoken', silent) + .then(({ body }) => emit('csrf-token-update', body)) + cy.wrap(user, silent).as('currentUser') }) -Cypress.Commands.overwrite('visit', (originalFn, url, options) => { - // Make sure that each visit call that triggers a page load will update the stored requesttoken - return originalFn(url, options).then((result) => { - cy.window(silent) - .then((win) => { - cy.wrap(win?.OC?.requestToken) - .as('requesttoken') - }) - }) -}) - -Cypress.Commands.add('getRequestToken', () => { - cy.then(function() { - if (this.requesttoken) { - return this.requesttoken - } else { - cy.log('Fetching request token') - return cy.request('/csrftoken') - .its('body.token') - .as('requesttoken') - } - }) -}) - -Cypress.Commands.add('ocsRequest', (options) => { - return cy.getRequestToken() - .then((requesttoken) => { - return cy.request({ - form: true, - auth, - headers: { - 'OCS-ApiRequest': 'true', - 'Content-Type': 'application/x-www-form-urlencoded', - requesttoken, - }, - ...options, - }) - }) +Cypress.Commands.add('openDirectEditingToken', (token) => { + const visitHooks = { + onBeforeLoad(win) { + win.DirectEditingMobileInterface = { + close() {}, + } + }, + } + cy.visit(token, visitHooks) }) Cypress.Commands.add('uploadFile', (fileName, mimeType, target) => { @@ -95,65 +73,35 @@ Cypress.Commands.add('uploadFile', (fileName, mimeType, target) => { if (typeof target !== 'undefined') { fileName = target } - cy.getRequestToken() - .then(async (requesttoken) => { - return cy.request({ - url: `${url}/remote.php/webdav/${fileName}`, - method: 'put', - body: blob.size > 0 ? blob : '', - auth, - headers: { - requesttoken, - 'Content-Type': mimeType, - }, - }) - }).then(response => { - const fileId = Number( - response.headers['oc-fileid']?.split('oc')?.[0], - ) - cy.log(`Uploaded ${fileName}`, - response.status, - { fileId }, - ) - return cy.wrap(fileId) - }) + return axios.put( + `${url}/remote.php/webdav/${fileName}`, + blob.size > 0 ? blob : '', + { headers: { 'Content-Type': mimeType } }, + ).then(response => { + const fileId = Number(response.headers['oc-fileid']?.split('oc')?.[0]) + Cypress.log({ message: `"${target}" (${response.status}): ${fileId}` }) + return cy.wrap(fileId, silent) + }) }) }) Cypress.Commands.add('downloadFile', (fileName) => { - return cy.getRequestToken() - .then(requesttoken => { - return axios.get(`${url}/remote.php/webdav/${fileName}`, { - headers: { - requesttoken, - }, - }) - }) + return axios.get(`${url}/remote.php/webdav/${fileName}`) }) Cypress.Commands.add('createFile', (target, content, mimeType = 'text/markdown', headers = {}) => { const blob = new Blob([content], { type: mimeType }) - - return cy.getRequestToken() - .then(requesttoken => { - return cy.request({ - url: `${url}/remote.php/webdav/${target}`, - method: 'put', - body: blob.size > 0 ? blob : '', - auth, - headers: { - 'Content-Type': mimeType, - requesttoken, - ...headers, - }, - }).then((response) => { - cy.log(`Uploaded ${target}`, response.status) - const fileId = Number( - response.headers['oc-fileid']?.split('oc')?.[0], - ) - return cy.wrap(fileId) - }) - }) + return axios.put( + `${url}/remote.php/webdav/${target}`, + blob.size > 0 ? blob : '', + { + headers: { 'Content-Type': mimeType, ...headers }, + }, + ).then((response) => { + const fileId = Number(response.headers['oc-fileid']?.split('oc')?.[0]) + Cypress.log({ message: `"${target}" (${response.status}): ${fileId}` }) + return fileId + }) }) Cypress.Commands.add('createMarkdown', (fileName, content, reload = true) => { @@ -167,18 +115,16 @@ Cypress.Commands.add('createMarkdown', (fileName, content, reload = true) => { }) Cypress.Commands.add('shareFileToUser', (path, targetUser, shareData = {}) => { - cy.ocsRequest({ - method: 'POST', - url: `${url}/ocs/v2.php/apps/files_sharing/api/v1/shares`, - body: { + Cypress.log() + return axios.post( + `${url}/ocs/v2.php/apps/files_sharing/api/v1/shares`, + { path, shareType: 0, shareWith: targetUser.userId, ...shareData, }, - }).then(response => { - cy.log(`${auth.user} shared ${path} with ${targetUser.userId}`, response.status) - }) + ) }) Cypress.Commands.add('testName', () => { @@ -224,112 +170,65 @@ Cypress.Commands.add('isolateTest', ({ sourceFile = 'empty.md', targetFile = nul }) Cypress.Commands.add('shareFile', (path, options = {}) => { - return cy.getRequestToken() - .then(async requesttoken => { - const headers = { requesttoken } - const shareType = window.OC?.Share?.SHARE_TYPE_LINK ?? 3 - const request = await axios.post( - `${url}/ocs/v2.php/apps/files_sharing/api/v1/shares`, - { path, shareType }, - { headers }, - ) - const token = request.data?.ocs?.data?.token - const id = request.data?.ocs?.data?.id - if (!token || !id || token.length === 0) { - throw request - } - cy.log(`Share link created: ${token}`) - - if (options.edit) { - // Same permissions makeing the share editable in the UI would set - // 1 = read; 2 = write; 16 = share; - const permissions = 19 - await axios.put( - `${url}/ocs/v2.php/apps/files_sharing/api/v1/shares/${id}`, - { permissions }, - { headers }, - ) - cy.log(`Made share ${token} editable.`) + const shareType = window.OC?.Share?.SHARE_TYPE_LINK ?? 3 + return axios.post( + `${url}/ocs/v2.php/apps/files_sharing/api/v1/shares`, + { path, shareType }, + ) + .then(response => response.data.ocs.data) + .then(({ token, id }) => { + Cypress.log({ message: `"${path}" (${id}): ${token}` }) + if (!options.edit) { + return token } - return cy.wrap(token) - }).should('have.length', 15) + // Same permissions makeing the share editable in the UI would set + // 1 = read; 2 = write; 16 = share; + const permissions = 19 + return axios.put( + `${url}/ocs/v2.php/apps/files_sharing/api/v1/shares/${id}`, + { permissions }, + ).then(() => token) + }) }) Cypress.Commands.add('createFolder', (target) => { - const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(auth.user)}` - const dirPath = target.split('/').map(encodeURIComponent).join('/') - - return cy.getRequestToken() - .then(requesttoken => { - cy.request({ - url: `${rootPath}/${dirPath}`, - method: 'MKCOL', - auth, - headers: { - requesttoken, - }, - }).then((response) => { - return parseInt(response.headers['oc-fileid']) - }) - }) + cy.get('@currentUser', silent).then(({ userId }) => { + const rootPath = `${url}/remote.php/dav/files/${encodeURIComponent(userId)}` + const dirPath = target.split('/').map(encodeURIComponent).join('/') + return axios.request( + `${rootPath}/${dirPath}`, + { method: 'MKCOL' }, + ) + }).then((response) => parseInt(response.headers['oc-fileid'])) }) Cypress.Commands.add('moveFile', (path, destinationPath) => { - return cy.getRequestToken() - .then(requesttoken => { - return cy.request({ - method: 'MOVE', - url: `${url}/remote.php/webdav/${path}`, - auth, - headers: { - requesttoken, - Destination: `${url}/remote.php/webdav/${destinationPath}`, - }, - }).then(response => { - cy.wrap(response.body) - }) - }) + return axios.request({ + method: 'MOVE', + url: `${url}/remote.php/webdav/${path}`, + headers: { + Destination: `${url}/remote.php/webdav/${destinationPath}`, + }, + }).then(response => response.body) }) -Cypress.Commands.add('removeFile', (path) => { - return cy.getRequestToken() - .then(requesttoken => { - return cy.request({ - url: `${url}/remote.php/webdav/${path}`, - method: 'DELETE', - auth, - headers: { - requesttoken, - }, - }) - }) +Cypress.Commands.add('deleteFile', (path) => { + return axios.delete(`${url}/remote.php/webdav/${path}`) }) Cypress.Commands.add('copyFile', (path, destinationPath) => { - return cy.getRequestToken() - .then(requesttoken => { - return cy.request({ - method: 'COPY', - url: `${url}/remote.php/webdav/${path}`, - auth, - headers: { - requesttoken, - Destination: `${url}/remote.php/webdav/${destinationPath}`, - }, - }).then(response => { - cy.wrap(response.body) - }) - }) + return axios.request({ + method: 'COPY', + url: `${url}/remote.php/webdav/${path}`, + headers: { + Destination: `${url}/remote.php/webdav/${destinationPath}`, + }, + }).then(response => response.body) }) Cypress.Commands.add('getFileContent', (path) => { - return cy.request({ - method: 'GET', - url: `${url}/remote.php/webdav/${path}`, - auth, - }).then(response => { - cy.wrap(response.body) - }) + return axios.get(`${url}/remote.php/webdav/${path}`) + .then(response => response.data) }) Cypress.Commands.add('propfindFolder', (path, depth = 0) => { @@ -409,18 +308,15 @@ Cypress.Commands.add('interceptCreate', () => { }) Cypress.Commands.add('closeInterceptedSession', (shareToken = undefined) => { - return cy.window().then(win => { - return axios.post( - closeData.url, - { - documentId: closeData.session.documentId, - sessionId: closeData.session.id, - sessionToken: closeData.session.token, - token: shareToken, - }, - { headers: { requesttoken: win.OC.requestToken } }, - ) - }) + return axios.post( + closeData.url, + { + documentId: closeData.session.documentId, + sessionId: closeData.session.id, + sessionToken: closeData.session.token, + token: shareToken, + }, + ) }) Cypress.Commands.add('getFile', fileName => { @@ -428,22 +324,6 @@ Cypress.Commands.add('getFile', fileName => { }) -Cypress.Commands.add('deleteFile', fileName => { - return cy.getRequestToken() - .then(requesttoken => { - return cy.request({ - method: 'DELETE', - url: `${url}/remote.php/webdav/${fileName}`, - auth, - headers: { - requesttoken, - }, - }).then(response => { - cy.wrap(response.body) - }) - }) -}) - Cypress.Commands.add('getModal', () => { return cy.get('#viewer[data-handler="text"]') }) @@ -514,32 +394,18 @@ Cypress.Commands.add('openWorkspace', () => { }) Cypress.Commands.add('configureText', (key, value) => { - return cy.window(silent).then(win => { - return axios.post( - `${url}/index.php/apps/text/settings`, - { key, value }, - { headers: { requesttoken: win.OC.requestToken } }, - ) - }) + return axios.post( + `${url}/index.php/apps/text/settings`, + { key, value }, + ) }) Cypress.Commands.add('showHiddenFiles', (value = true) => { - return cy.getRequestToken() - .then(requesttoken => { - return cy.request({ - url: `${url}/index.php/apps/files/api/v1/config/show_hidden`, - method: 'put', - body: { - value, - }, - auth, - headers: { - requesttoken, - }, - }).then((response) => { - return cy.log(`Set hidden files to ${value}`, response.status) - }) - }) + Cypress.log() + return axios.put( + `${url}/index.php/apps/files/api/v1/config/show_hidden`, + { value }, + ) }) Cypress.Commands.add('createDescription', (buttonLabel = 'Add folder description') => { @@ -564,6 +430,42 @@ Cypress.Commands.add('setCssMedia', (media) => { }) }) +Cypress.Commands.add('createDirectEditingLink', path => { + return axios.request({ + method: 'POST', + url: `${url}/ocs/v2.php/apps/files/api/v1/directEditing/open?format=json`, + data: { path }, + headers: { + 'OCS-ApiRequest': 'true', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }).then(response => { + const token = response.data?.ocs?.data?.url + Cypress.log({ message: token }) + return token + }) +}) + +Cypress.Commands.add('createDirectEditingLinkForNewFile', path => { + return axios.request({ + method: 'POST', + url: `${url}/ocs/v2.php/apps/files/api/v1/directEditing/create?format=json`, + data: { + path, + editorId: 'text', + creatorId: 'textdocument', + }, + headers: { + 'OCS-ApiRequest': 'true', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }).then(response => { + const token = response.data?.ocs?.data?.url + Cypress.log({ message: token }) + return token + }) +}) + Cypress.on( 'uncaught:exception', err => !err.message.includes('ResizeObserver loop limit exceeded'), diff --git a/cypress/support/sessions.js b/cypress/support/sessions.js index fc573c87f00..a86cd2040bd 100644 --- a/cypress/support/sessions.js +++ b/cypress/support/sessions.js @@ -20,24 +20,10 @@ * */ +import axios from '@nextcloud/axios' import SessionApi from '../../src/services/SessionApi.js' -import { emit } from '@nextcloud/event-bus' -Cypress.Commands.add('prepareWindowForSessionApi', () => { - window.OC = { - config: { modRewriteWorking: false }, - } - // Prevent @nextcloud/router from reading window.location - window._oc_webroot = '' -}) - -Cypress.Commands.add('prepareSessionApi', () => { - return cy.request('/csrftoken') - .then(({ body }) => { - emit('csrf-token-update', body) - return body.token - }) -}) +const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '') Cypress.Commands.add('createTextSession', (fileId, options = {}) => { const api = new SessionApi(options) @@ -89,18 +75,18 @@ Cypress.Commands.add('failToSave', (connection, options = { version: 0 }) => { }) Cypress.Commands.add('sessionUsers', function(connection, bodyOptions = {}) { - const body = { + const data = { documentId: connection.document.id, sessionId: connection.session.id, sessionToken: connection.session.token, requesttoken: this.requesttoken, ...bodyOptions, } - cy.request({ + return axios.request({ method: 'POST', - url: '/apps/text/api/v1/users', - body, - failOnStatusCode: false, + url: `${url}/index.php/apps/text/api/v1/users`, + data, + validateStatus: status => status < 500, }) })