X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/39f2ecd2cfa22d7dd6e1dc94f9952bd5c5c13ef2..5afc1f6ee27b2135827d0f2976ed01ad0534ba62:/cypress/support/commands.js diff --git a/cypress/support/commands.js b/cypress/support/commands.js index cfdfa9ec..67ddf45d 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -28,16 +28,22 @@ // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) +import { extractFilesData } from "services/collection-service/collection-service-files-response"; + const controllerURL = Cypress.env('controller_url'); const systemToken = Cypress.env('system_token'); let createdResources = []; -// Clean up on a 'before' hook to allow post-mortem analysis on individual tests. -beforeEach(function () { +const containerLogFolderPrefix = 'log for container '; + +// Clean up anything that was created. You can temporarily add +// 'return' to the top if you need the resources to hang around to +// debug a specific test. +afterEach(function () { if (createdResources.length === 0) { return; } - cy.log(`Cleaning ${createdResources.length} previously created resource(s)`); + cy.log(`Cleaning ${createdResources.length} previously created resource(s).`); createdResources.forEach(function({suffix, uuid}) { // Don't fail when a resource isn't already there, some objects may have // been removed, directly or indirectly, from the test that created them. @@ -60,6 +66,22 @@ Cypress.Commands.add( }); }); +Cypress.Commands.add( + "doWebDAVRequest", (method = 'GET', path = '', data = null, qs = null, + token = systemToken, auth = false, followRedirect = true, failOnStatusCode = true) => { + return cy.doRequest('GET', '/arvados/v1/config', null, null).then(({body: config}) => { + return cy.request({ + method: method, + url: `${config.Services.WebDAVDownload.ExternalURL.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`, + body: data, + qs: auth ? qs : Object.assign({ api_token: token }, qs), + auth: auth ? { bearer: `${token}` } : undefined, + followRedirect: followRedirect, + failOnStatusCode: failOnStatusCode + }); + }); +}); + Cypress.Commands.add( "getUser", (username, first_name = '', last_name = '', is_admin = false, is_active = true) => { // Create user if not already created @@ -73,30 +95,48 @@ Cypress.Commands.add( }), return_to: ',https://example.local' }, null, systemToken, true, false) // Don't follow redirects so we can catch the token - .its('headers.location').as('location') - // Get its token and set the account up as admin and/or active + .its('headers.location').as('location') + // Get its token and set the account up as admin and/or active + .then(function () { + this.userToken = this.location.split("=")[1] + assert.isString(this.userToken) + return cy.doRequest('GET', '/arvados/v1/users', null, { + filters: `[["username", "=", "${username}"]]` + }) + .its('body.items.0').as('aUser') .then(function () { - this.userToken = this.location.split("=")[1] - assert.isString(this.userToken) - return cy.doRequest('GET', '/arvados/v1/users', null, { - filters: `[["username", "=", "${username}"]]` + cy.doRequest('PUT', `/arvados/v1/users/${this.aUser.uuid}`, { + user: { + is_admin: is_admin, + is_active: is_active + } }) - .its('body.items.0') - .as('aUser') + .its('body').as('theUser') + .then(function () { + cy.doRequest('GET', '/arvados/v1/api_clients', null, { + filters: `[["is_trusted", "=", false]]`, + order: `["created_at desc"]` + }) + .its('body.items').as('apiClients') .then(function () { - cy.doRequest('PUT', `/arvados/v1/users/${this.aUser.uuid}`, { - user: { - is_admin: is_admin, - is_active: is_active - } - }) - .its('body') - .as('theUser') - .then(function () { - return { user: this.theUser, token: this.userToken }; + if (this.apiClients.length > 0) { + cy.doRequest('PUT', `/arvados/v1/api_clients/${this.apiClients[0].uuid}`, { + api_client: { + is_trusted: true + } }) + .its('body').as('updatedApiClient') + .then(function() { + assert(this.updatedApiClient.is_trusted); + }) + } + }) + .then(function () { + return { user: this.theUser, token: this.userToken }; }) + }) }) + }) } ) @@ -142,6 +182,12 @@ Cypress.Commands.add( } ) +Cypress.Commands.add( + "getCollection", (token, uuid) => { + return cy.getResource(token, 'collections', uuid) + } +) + Cypress.Commands.add( "updateCollection", (token, uuid, data) => { return cy.updateResource(token, 'collections', uuid, { @@ -150,6 +196,132 @@ Cypress.Commands.add( } ) +Cypress.Commands.add( + "collectionReplaceFiles", (token, uuid, data) => { + return cy.updateResource(token, 'collections', uuid, { + collection: { + preserve_version: true, + }, + replace_files: JSON.stringify(data) + }) + } +) + +Cypress.Commands.add( + "getContainer", (token, uuid) => { + return cy.getResource(token, 'containers', uuid) + } +) + +Cypress.Commands.add( + "updateContainer", (token, uuid, data) => { + return cy.updateResource(token, 'containers', uuid, { + container: JSON.stringify(data) + }) + } +) + +Cypress.Commands.add( + "getContainerRequest", (token, uuid) => { + return cy.getResource(token, 'container_requests', uuid) + } +) + +Cypress.Commands.add( + 'createContainerRequest', (token, data) => { + return cy.createResource(token, 'container_requests', { + container_request: JSON.stringify(data), + ensure_unique_name: true + }) + } +) + +Cypress.Commands.add( + "updateContainerRequest", (token, uuid, data) => { + return cy.updateResource(token, 'container_requests', uuid, { + container_request: JSON.stringify(data) + }) + } +) + +/** + * Requires an admin token for log_uuid modification to succeed + */ +Cypress.Commands.add( + "appendLog", (token, crUuid, fileName, lines = []) => ( + cy.getContainerRequest(token, crUuid).then((containerRequest) => { + if (containerRequest.log_uuid) { + cy.listContainerRequestLogs(token, crUuid).then((logFiles) => { + const filePath = `${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`; + if (logFiles.find((file) => (file.name === fileName))) { + // File exists, fetch and append + return cy.doWebDAVRequest( + "GET", + `c=${filePath}`, + null, + null, + token + ) + .then(({ body: contents }) => cy.doWebDAVRequest( + "PUT", + `c=${filePath}`, + contents.split("\n").concat(lines).join("\n"), + null, + token + )); + } else { + // File not exists, put new file + cy.doWebDAVRequest( + "PUT", + `c=${filePath}`, + lines.join("\n"), + null, + token + ) + } + }); + } else { + // Create log collection + return cy.createCollection(token, { + name: `Test log collection ${Math.floor(Math.random() * 999999)}`, + owner_uuid: containerRequest.owner_uuid, + manifest_text: "" + }).then((collection) => { + // Update CR log_uuid to fake log collection + cy.updateContainerRequest(token, containerRequest.uuid, { + log_uuid: collection.uuid, + }).then(() => ( + // Create empty directory for container uuid + cy.collectionReplaceFiles(token, collection.uuid, { + [`/${containerLogFolderPrefix}${containerRequest.container_uuid}`]: "d41d8cd98f00b204e9800998ecf8427e+0" + }).then(() => ( + // Put new log file with contents into fake log collection + cy.doWebDAVRequest( + 'PUT', + `c=${collection.uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`, + lines.join('\n'), + null, + token + ) + )) + )); + }); + } + }) + ) +) + +Cypress.Commands.add( + "listContainerRequestLogs", (token, crUuid) => ( + cy.getContainerRequest(token, crUuid).then((containerRequest) => ( + cy.doWebDAVRequest('PROPFIND', `c=${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}`, null, null, token) + .then(({body: data}) => { + return extractFilesData(new DOMParser().parseFromString(data, "text/xml")); + }) + )) + ) +); + Cypress.Commands.add( "createVirtualMachine", (token, data) => { return cy.createResource(token, 'virtual_machines', { @@ -159,13 +331,23 @@ Cypress.Commands.add( } ) +Cypress.Commands.add( + "getResource", (token, suffix, uuid) => { + return cy.doRequest('GET', `/arvados/v1/${suffix}/${uuid}`, null, {}, token) + .its('body') + .then(function (resource) { + return resource; + }) + } +) + Cypress.Commands.add( "createResource", (token, suffix, data) => { return cy.doRequest('POST', '/arvados/v1/' + suffix, data, null, token, true) - .its('body').as('resource') - .then(function () { - createdResources.push({suffix, uuid: this.resource.uuid}); - return this.resource; + .its('body') + .then(function (resource) { + createdResources.push({suffix, uuid: resource.uuid}); + return resource; }) } ) @@ -173,19 +355,19 @@ Cypress.Commands.add( Cypress.Commands.add( "deleteResource", (token, suffix, uuid, failOnStatusCode = true) => { return cy.doRequest('DELETE', '/arvados/v1/' + suffix + '/' + uuid, null, null, token, false, true, failOnStatusCode) - .its('body').as('resource') - .then(function () { - return this.resource; + .its('body') + .then(function (resource) { + return resource; }) } ) Cypress.Commands.add( "updateResource", (token, suffix, uuid, data) => { - return cy.doRequest('PUT', '/arvados/v1/' + suffix + '/' + uuid, data, null, token, true) - .its('body').as('resource') - .then(function () { - return this.resource; + return cy.doRequest('PATCH', '/arvados/v1/' + suffix + '/' + uuid, data, null, token, true) + .its('body') + .then(function (resource) { + return resource; }) } ) @@ -296,9 +478,11 @@ Cypress.Commands.add( { prevSubject: 'element', }, - (subject, file, fileName) => { + (subject, file, fileName, binaryMode = true) => { cy.window().then(window => { - const blob = b64toBlob(file, '', 512); + const blob = binaryMode + ? b64toBlob(file, '', 512) + : new Blob([file], {type: 'text/plain'}); const testFile = new window.File([blob], fileName); cy.wrap(subject).trigger('drop', { @@ -328,3 +512,76 @@ function b64toBlob(b64Data, contentType = '', sliceSize = 512) { const blob = new Blob(byteArrays, { type: contentType }); return blob } + +// From https://github.com/cypress-io/cypress/issues/7306#issuecomment-1076451070= +// This command requires the async package (https://www.npmjs.com/package/async) +Cypress.Commands.add('waitForDom', () => { + cy.window().then({ + // Don't timeout before waitForDom finishes + timeout: 10000 + }, win => { + let timeElapsed = 0; + + cy.log("Waiting for DOM mutations to complete"); + + return new Cypress.Promise((resolve) => { + // set the required variables + let async = require("async"); + let observerConfig = { attributes: true, childList: true, subtree: true }; + let items = Array.apply(null, { length: 50 }).map(Number.call, Number); + win.mutationCount = 0; + win.previousMutationCount = null; + + // create an observer instance + let observer = new win.MutationObserver((mutations) => { + mutations.forEach((mutation) => { + // Only record "attributes" type mutations that are not a "class" mutation. + // If the mutation is not an "attributes" type, then we always record it. + if (mutation.type === 'attributes' && mutation.attributeName !== 'class') { + win.mutationCount += 1; + } else if (mutation.type !== 'attributes') { + win.mutationCount += 1; + } + }); + + // initialize the previousMutationCount + if (win.previousMutationCount == null) win.previousMutationCount = 0; + }); + + // watch the document body for the specified mutations + observer.observe(win.document.body, observerConfig); + + // check the DOM for mutations up to 50 times for a maximum time of 5 seconds + async.eachSeries(items, function iteratee(item, callback) { + // keep track of the elapsed time so we can log it at the end of the command + timeElapsed = timeElapsed + 100; + + // make each iteration of the loop 100ms apart + setTimeout(() => { + if (win.mutationCount === win.previousMutationCount) { + // pass an argument to the async callback to exit the loop + return callback('Resolved - DOM changes complete.'); + } else if (win.previousMutationCount != null) { + // only set the previous count if the observer has checked the DOM at least once + win.previousMutationCount = win.mutationCount; + return callback(); + } else if (win.mutationCount === 0 && win.previousMutationCount == null && item === 4) { + // this is an early exit in case nothing is changing in the DOM. That way we only + // wait 500ms instead of the full 5 seconds when no DOM changes are occurring. + return callback('Resolved - Exiting early since no DOM changes were detected.'); + } else { + // proceed to the next iteration + return callback(); + } + }, 100); + }, function done() { + // Log the total wait time so users can see it + cy.log(`DOM mutations ${timeElapsed >= 5000 ? "did not complete" : "completed"} in ${timeElapsed} ms`); + + // disconnect the observer and resolve the promise + observer.disconnect(); + resolve(); + }); + }); + }); + });