From a44c46ef41adf4bb86554027d958cc85790d9d72 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 25 Apr 2024 07:52:20 +0200 Subject: [PATCH 1/3] test(cy): rely on @nextcloud/axios for requests This greatly simplifies the handling of csrf-tokens and auth. Signed-off-by: Max --- cypress/e2e/api/SessionApi.spec.js | 5 - cypress/e2e/api/SyncServiceProvider.spec.js | 2 - cypress/e2e/api/UsersApi.spec.js | 2 - cypress/e2e/directediting.spec.js | 55 +-- cypress/e2e/propfind.spec.js | 2 +- cypress/support/commands.js | 383 +++++++------------- cypress/support/sessions.js | 28 +- 7 files changed, 151 insertions(+), 326 deletions(-) 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..e42ee2d41e1 100644 --- a/cypress/e2e/directediting.spec.js +++ b/cypress/e2e/directediting.spec.js @@ -2,52 +2,6 @@ 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) - }) -} - describe('direct editing', function() { const visitHooks = { @@ -66,7 +20,8 @@ describe('direct editing', function() { 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) @@ -94,7 +49,8 @@ describe('direct editing', function() { 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) @@ -123,7 +79,8 @@ describe('direct editing', function() { 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) 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..783c3120329 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,25 @@ 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) -}) - -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, - }) - }) + cy.request('/csrftoken', silent) + .then(({ body }) => emit('csrf-token-update', body)) + cy.wrap(user, silent).as('currentUser') }) Cypress.Commands.add('uploadFile', (fileName, mimeType, target) => { @@ -95,65 +62,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 +104,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 +159,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 +297,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 +313,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 +383,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 +419,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, }) }) From 62ecdf67b686e915f8ee3dc302c63a9ba3a4d5f4 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 1 May 2024 11:10:01 +0200 Subject: [PATCH 2/3] test(cy): use a new session for direct editing Signed-off-by: Max --- cypress/e2e/directediting.spec.js | 30 ++++++++++++------------------ cypress/support/commands.js | 11 +++++++++++ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/cypress/e2e/directediting.spec.js b/cypress/e2e/directediting.spec.js index e42ee2d41e1..82c0f9d3643 100644 --- a/cypress/e2e/directediting.spec.js +++ b/cypress/e2e/directediting.spec.js @@ -4,13 +4,6 @@ const user = randUser() describe('direct editing', function() { - const visitHooks = { - onBeforeLoad(win) { - win.DirectEditingMobileInterface = { - close() {}, - } - }, - } before(function() { initUserAndFiles(user, 'test.md', 'empty.md', 'empty.txt') }) @@ -23,8 +16,8 @@ describe('direct editing', function() { cy.login(user) cy.createDirectEditingLink('empty.md') .then((token) => { - cy.logout() - cy.visit(token, visitHooks) + cy.session('direct-editing', () => { }) + cy.openDirectEditingToken(token) }) cy.getContent().type('# This is a headline') cy.getContent().type('{enter}') @@ -37,11 +30,10 @@ describe('direct editing', function() { 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.login(user) + cy.getFileContent('empty.md').then((content) => { + expect(content).to.equal('# This is a headline\n\nSome text') + }) }) it('Create a file, edit it', () => { @@ -52,8 +44,8 @@ describe('direct editing', function() { cy.login(user) cy.createDirectEditingLinkForNewFile('newfile.md') .then((token) => { - cy.logout() - cy.visit(token, visitHooks) + cy.session('direct-editing', () => { }) + cy.openDirectEditingToken(token) }) cy.getContent().type('# This is a headline') @@ -68,6 +60,7 @@ describe('direct editing', function() { cy.get('button.icon-close').click() cy.wait('@closeRequest') .then(() => { + cy.login(user) cy.getFileContent('newfile.md').then((content) => { expect(content).to.equal('# This is a headline\n\nSome text') }) @@ -82,8 +75,8 @@ describe('direct editing', function() { cy.login(user) cy.createDirectEditingLink('empty.txt') .then((token) => { - cy.logout() - cy.visit(token, visitHooks) + cy.session('direct-editing', () => { }) + cy.openDirectEditingToken(token) }) cy.getContent().type('# This is a headline') @@ -98,6 +91,7 @@ describe('direct editing', function() { cy.get('button.icon-close').click() cy.wait('@closeRequest') .then(() => { + cy.login(user) cy.getFileContent('empty.txt').then((content) => { expect(content).to.equal('# This is a headline\nSome text\n') }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 783c3120329..bb6e5c39835 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -55,6 +55,17 @@ Cypress.Commands.overwrite('login', (login, user) => { cy.wrap(user, silent).as('currentUser') }) +Cypress.Commands.add('openDirectEditingToken', (token) => { + const visitHooks = { + onBeforeLoad(win) { + win.DirectEditingMobileInterface = { + close() {}, + } + }, + } + cy.visit(token, visitHooks) +}) + Cypress.Commands.add('uploadFile', (fileName, mimeType, target) => { return cy.fixture(fileName, 'binary') .then(Cypress.Blob.binaryStringToBlob) From ab794e4e0abcef97b09bb8c2b91029612e7d27ce Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 2 May 2024 06:45:02 +0200 Subject: [PATCH 3/3] test(cy): simplify direct editing spec Extract function `enterContentAndClose()`. Signed-off-by: Max --- cypress/e2e/directediting.spec.js | 88 +++++++++---------------------- 1 file changed, 26 insertions(+), 62 deletions(-) diff --git a/cypress/e2e/directediting.spec.js b/cypress/e2e/directediting.spec.js index 82c0f9d3643..9915ad8064d 100644 --- a/cypress/e2e/directediting.spec.js +++ b/cypress/e2e/directediting.spec.js @@ -2,6 +2,21 @@ import { initUserAndFiles, randUser } from '../utils/index.js' const user = randUser() +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() { before(function() { @@ -9,92 +24,41 @@ describe('direct editing', function() { }) 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') - cy.login(user) cy.createDirectEditingLink('empty.md') .then((token) => { cy.session('direct-editing', () => { }) cy.openDirectEditingToken(token) }) - 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') + enterContentAndClose() cy.login(user) - cy.getFileContent('empty.md').then((content) => { - expect(content).to.equal('# This is a headline\n\nSome text') - }) + 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') - cy.login(user) cy.createDirectEditingLinkForNewFile('newfile.md') .then((token) => { cy.session('direct-editing', () => { }) cy.openDirectEditingToken(token) }) - - 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.login(user) - cy.getFileContent('newfile.md').then((content) => { - expect(content).to.equal('# This is a headline\n\nSome text') - }) - }) + 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') - cy.login(user) cy.createDirectEditingLink('empty.txt') .then((token) => { cy.session('direct-editing', () => { }) cy.openDirectEditingToken(token) }) - - 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.login(user) - cy.getFileContent('empty.txt').then((content) => { - expect(content).to.equal('# This is a headline\nSome text\n') - }) - }) + enterContentAndClose() + cy.login(user) + cy.getFileContent('empty.txt') + .should('equal', '# This is a headline\nSome text\n') }) })