1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 describe('Process tests', function() {
10 // Only set up common users once. These aren't set up as aliases because
11 // aliases are cleaned up after every test. Also it doesn't make sense
12 // to set the same users on beforeEach() over and over again, so we
13 // separate a little from Cypress' 'Best Practices' here.
14 cy.getUser('admin', 'Admin', 'User', true, true)
15 .as('adminUser').then(function() {
16 adminUser = this.adminUser;
19 cy.getUser('user', 'Active', 'User', false, true)
20 .as('activeUser').then(function() {
21 activeUser = this.activeUser;
26 beforeEach(function() {
28 cy.clearLocalStorage();
31 function setupDockerImage(image_name) {
32 // Create a collection that will be used as a docker image for the tests.
33 cy.createCollection(adminUser.token, {
35 manifest_text: ". d21353cfe035e3e384563ee55eadbb2f+67108864 5c77a43e329b9838cbec18ff42790e57+55605760 0:122714624:sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n"
36 }).as('dockerImage').then(function(dockerImage) {
37 // Give read permissions to the active user on the docker image.
38 cy.createLink(adminUser.token, {
39 link_class: 'permission',
41 tail_uuid: activeUser.user.uuid,
42 head_uuid: dockerImage.uuid
43 }).as('dockerImagePermission').then(function() {
44 // Set-up docker image collection tags
45 cy.createLink(activeUser.token, {
46 link_class: 'docker_image_repo+tag',
48 head_uuid: dockerImage.uuid,
49 }).as('dockerImageRepoTag');
50 cy.createLink(activeUser.token, {
51 link_class: 'docker_image_hash',
52 name: 'sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678',
53 head_uuid: dockerImage.uuid,
54 }).as('dockerImageHash');
57 return cy.getAll('@dockerImage', '@dockerImageRepoTag', '@dockerImageHash',
58 '@dockerImagePermission').then(function([dockerImage]) {
63 function createContainerRequest(user, name, docker_image, command, reuse = false, state = 'Uncommitted') {
64 return setupDockerImage(docker_image).then(function(dockerImage) {
65 return cy.createContainerRequest(user.token, {
68 container_image: dockerImage.portable_data_hash, // for some reason, docker_image doesn't work here
69 output_path: 'stdout.txt',
71 runtime_constraints: {
87 it('shows process logs', function() {
88 const crName = 'test_container_request';
89 createContainerRequest(
93 ['echo', 'hello world'],
95 .then(function(containerRequest) {
96 cy.loginAs(activeUser);
97 cy.goToPath(`/processes/${containerRequest.uuid}`);
98 cy.get('[data-cy=process-details]').should('contain', crName);
99 cy.get('[data-cy=process-logs]')
100 .should('contain', 'No logs yet')
101 .and('not.contain', 'hello world');
102 cy.createLog(activeUser.token, {
103 object_uuid: containerRequest.container_uuid,
108 }).then(function(log) {
109 cy.get('[data-cy=process-logs]', {timeout: 7000})
110 .should('not.contain', 'No logs yet')
111 .and('contain', 'hello world');
116 it('shows process details', function() {
117 createContainerRequest(
119 `test_container_request ${Math.floor(Math.random() * 999999)}`,
121 ['echo', 'hello world'],
123 .then(function(containerRequest) {
124 cy.loginAs(activeUser);
125 cy.goToPath(`/processes/${containerRequest.uuid}`);
126 cy.get('[data-cy=process-details]').should('contain', containerRequest.name);
127 cy.get('[data-cy=process-details-attributes-modifiedby-user]').contains(`Active User (${activeUser.user.uuid})`);
128 cy.get('[data-cy=process-details-attributes-runtime-user]').should('not.exist');
131 // Fake submitted by another user
132 cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
134 res.body.modified_by_user_uuid = 'zzzzz-tpzed-000000000000000';
138 createContainerRequest(
140 `test_container_request ${Math.floor(Math.random() * 999999)}`,
142 ['echo', 'hello world'],
144 .then(function(containerRequest) {
145 cy.loginAs(activeUser);
146 cy.goToPath(`/processes/${containerRequest.uuid}`);
147 cy.get('[data-cy=process-details]').should('contain', containerRequest.name);
148 cy.get('[data-cy=process-details-attributes-modifiedby-user]').contains(`zzzzz-tpzed-000000000000000`);
149 cy.get('[data-cy=process-details-attributes-runtime-user]').contains(`Active User (${activeUser.user.uuid})`);
153 it('filters process logs by event type', function() {
154 const nodeInfoLogs = [
156 'Linux compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p 5.4.0-1059-azure #62~18.04.1-Ubuntu SMP Tue Sep 14 17:53:18 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux',
159 'vendor_id : GenuineIntel',
162 'model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz'
164 const crunchRunLogs = [
165 '2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection',
166 '2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started',
167 '2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)',
168 '2022-03-22T13:56:26.244862836Z Executing container \'zzzzz-dz642-1wokwvcct9s9du3\' using docker runtime',
169 '2022-03-22T13:56:26.245037738Z Executing on host \'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p\'',
172 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.',
173 'Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.',
174 'In hac habitasse platea dictumst.',
175 'Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.',
176 'Interdum et malesuada fames ac ante ipsum primis in faucibus.',
177 'Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.',
178 'Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.',
179 'Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.',
180 'Donec vitae leo id augue gravida bibendum.',
181 'Nam libero libero, pretium ac faucibus elementum, mattis nec ex.',
182 'Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.',
183 'Aliquam viverra nisi nulla, et efficitur dolor mattis in.',
184 'Sed at enim sit amet nulla tincidunt mattis. Aenean eget aliquet ex, non ultrices ex. Nulla ex tortor, vestibulum aliquam tempor ac, aliquam vel est.',
185 'Fusce auctor faucibus libero id venenatis. Etiam sodales, odio eu cursus efficitur, quam sem blandit ex, quis porttitor enim dui quis lectus. In id tincidunt felis.',
186 'Phasellus non ex quis arcu tempus faucibus molestie in sapien.',
187 'Duis tristique semper dolor, vitae pulvinar risus.',
188 'Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.',
189 'Nulla eget mollis ipsum.',
192 createContainerRequest(
194 'test_container_request',
196 ['echo', 'hello world'],
198 .then(function(containerRequest) {
199 cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
200 'node-info', nodeInfoLogs).as('nodeInfoLogs');
201 cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
202 'crunch-run', crunchRunLogs).as('crunchRunLogs');
203 cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
204 'stdout', stdoutLogs).as('stdoutLogs');
205 cy.getAll('@stdoutLogs', '@nodeInfoLogs', '@crunchRunLogs').then(function() {
206 cy.loginAs(activeUser);
207 cy.goToPath(`/processes/${containerRequest.uuid}`);
208 // Should show main logs by default
209 cy.get('[data-cy=process-logs-filter]', {timeout: 7000}).should('contain', 'Main logs');
210 cy.get('[data-cy=process-logs]')
211 .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
212 .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
213 .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
215 cy.get('[data-cy=process-logs-filter]').click();
216 cy.get('body').contains('li', 'All logs').click();
217 cy.get('[data-cy=process-logs]')
218 .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
219 .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
220 .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
221 // Select 'node-info' logs
222 cy.get('[data-cy=process-logs-filter]').click();
223 cy.get('body').contains('li', 'node-info').click();
224 cy.get('[data-cy=process-logs]')
225 .should('not.contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
226 .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
227 .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
228 // Select 'stdout' logs
229 cy.get('[data-cy=process-logs-filter]').click();
230 cy.get('body').contains('li', 'stdout').click();
231 cy.get('[data-cy=process-logs]')
232 .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
233 .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
234 .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
239 it('should show runtime status indicators', function() {
240 // Setup running container with runtime_status error & warning messages
241 createContainerRequest(
243 'test_container_request',
245 ['echo', 'hello world'],
247 .as('containerRequest')
248 .then(function(containerRequest) {
249 expect(containerRequest.state).to.equal('Committed');
250 expect(containerRequest.container_uuid).not.to.be.equal('');
252 cy.getContainer(activeUser.token, containerRequest.container_uuid)
253 .then(function(queuedContainer) {
254 expect(queuedContainer.state).to.be.equal('Queued');
256 cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
258 }).then(function(lockedContainer) {
259 expect(lockedContainer.state).to.be.equal('Locked');
261 cy.updateContainer(adminUser.token, lockedContainer.uuid, {
264 error: 'Something went wrong',
265 errorDetail: 'Process exited with status 1',
266 warning: 'Free disk space is low',
269 .as('runningContainer')
270 .then(function(runningContainer) {
271 expect(runningContainer.state).to.be.equal('Running');
272 expect(runningContainer.runtime_status).to.be.deep.equal({
273 'error': 'Something went wrong',
274 'errorDetail': 'Process exited with status 1',
275 'warning': 'Free disk space is low',
280 // Test that the UI shows the error and warning messages
281 cy.getAll('@containerRequest', '@runningContainer').then(function([containerRequest]) {
282 cy.loginAs(activeUser);
283 cy.goToPath(`/processes/${containerRequest.uuid}`);
284 cy.get('[data-cy=process-runtime-status-error]')
285 .should('contain', 'Something went wrong')
286 .and('contain', 'Process exited with status 1');
287 cy.get('[data-cy=process-runtime-status-warning]')
288 .should('contain', 'Free disk space is low')
289 .and('contain', 'No additional warning details available');
293 // Force container_count for testing
294 let containerCount = 2;
295 cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
297 res.body.container_count = containerCount;
301 cy.getAll('@containerRequest').then(function([containerRequest]) {
302 cy.goToPath(`/processes/${containerRequest.uuid}`);
303 cy.get('[data-cy=process-runtime-status-retry-warning]', {timeout: 7000})
304 .should('contain', 'Process retried 1 time');
307 cy.getAll('@containerRequest').then(function([containerRequest]) {
309 cy.goToPath(`/processes/${containerRequest.uuid}`);
310 cy.get('[data-cy=process-runtime-status-retry-warning]', {timeout: 7000})
311 .should('contain', 'Process retried 2 times');
319 "id": "#main/input_file",
320 "label": "Label Description",
325 "basename": "input1.tar",
327 "location": "keep:00000000000000000000000000000000+01/input1.tar",
330 "basename": "input1-2.txt",
332 "location": "keep:00000000000000000000000000000000+01/input1-2.txt"
335 "basename": "input1-3.txt",
337 "location": "keep:00000000000000000000000000000000+01/input1-3.txt"
340 "basename": "input1-4.txt",
342 "location": "keep:00000000000000000000000000000000+01/input1-4.txt"
350 "id": "#main/input_dir",
351 "doc": "Doc Description",
356 "basename": "11111111111111111111111111111111+01",
357 "class": "Directory",
358 "location": "keep:11111111111111111111111111111111+01"
364 "id": "#main/input_bool",
365 "doc": ["Doc desc 1", "Doc desc 2"],
374 "id": "#main/input_int",
383 "id": "#main/input_long",
392 "id": "#main/input_float",
401 "id": "#main/input_double",
410 "id": "#main/input_string",
414 "input_string": "Hello World",
419 "id": "#main/input_file_array",
426 "input_file_array": [
428 "basename": "input2.tar",
430 "location": "keep:00000000000000000000000000000000+02/input2.tar"
433 "basename": "input3.tar",
435 "location": "keep:00000000000000000000000000000000+03/input3.tar",
438 "basename": "input3-2.txt",
440 "location": "keep:00000000000000000000000000000000+03/input3-2.txt"
445 "$import": "import_path"
452 "id": "#main/input_dir_array",
454 "items": "Directory",
461 "basename": "11111111111111111111111111111111+02",
462 "class": "Directory",
463 "location": "keep:11111111111111111111111111111111+02"
466 "basename": "11111111111111111111111111111111+03",
467 "class": "Directory",
468 "location": "keep:11111111111111111111111111111111+03"
471 "$import": "import_path"
478 "id": "#main/input_int_array",
490 "$import": "import_path"
497 "id": "#main/input_long_array",
504 "input_long_array": [
508 "$import": "import_path"
515 "id": "#main/input_float_array",
522 "input_float_array": [
527 "$import": "import_path"
534 "id": "#main/input_double_array",
541 "input_double_array": [
546 "$import": "import_path"
553 "id": "#main/input_string_array",
560 "input_string_array": [
565 "$import": "import_path"
572 "id": "#main/input_bool_include",
576 "input_bool_include": {
577 "$include": "include_path"
583 "id": "#main/input_int_include",
587 "input_int_include": {
588 "$include": "include_path"
594 "id": "#main/input_float_include",
598 "input_float_include": {
599 "$include": "include_path"
605 "id": "#main/input_string_include",
609 "input_string_include": {
610 "$include": "include_path"
616 "id": "#main/input_file_include",
620 "input_file_include": {
621 "$include": "include_path"
627 "id": "#main/input_directory_include",
631 "input_directory_include": {
632 "$include": "include_path"
638 "id": "#main/input_file_url",
643 "basename": "index.html",
645 "location": "http://example.com/index.html"
651 const testOutputs = [
654 "id": "#main/output_file",
655 "label": "Label Description",
660 "basename": "cat.png",
662 "location": "cat.png"
668 "id": "#main/output_file_with_secondary",
669 "doc": "Doc Description",
673 "output_file_with_secondary": {
674 "basename": "main.dat",
676 "location": "main.dat",
679 "basename": "secondary.dat",
681 "location": "secondary.dat"
684 "basename": "secondary2.dat",
686 "location": "secondary2.dat"
694 "id": "#main/output_dir",
695 "doc": ["Doc desc 1", "Doc desc 2"],
700 "basename": "outdir1",
701 "class": "Directory",
702 "location": "outdir1"
708 "id": "#main/output_bool",
717 "id": "#main/output_int",
726 "id": "#main/output_long",
735 "id": "#main/output_float",
739 "output_float": 100.5
744 "id": "#main/output_double",
748 "output_double": 100.3
753 "id": "#main/output_string",
757 "output_string": "Hello output"
762 "id": "#main/output_file_array",
769 "output_file_array": [
771 "basename": "output2.tar",
773 "location": "output2.tar"
776 "basename": "output3.tar",
778 "location": "output3.tar"
785 "id": "#main/output_dir_array",
787 "items": "Directory",
792 "output_dir_array": [
794 "basename": "outdir2",
795 "class": "Directory",
796 "location": "outdir2"
799 "basename": "outdir3",
800 "class": "Directory",
801 "location": "outdir3"
808 "id": "#main/output_int_array",
815 "output_int_array": [
824 "id": "#main/output_long_array",
831 "output_long_array": [
839 "id": "#main/output_float_array",
846 "output_float_array": [
855 "id": "#main/output_double_array",
862 "output_double_array": [
871 "id": "#main/output_string_array",
878 "output_string_array": [
887 const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
888 cy.get('table tr').contains(name).parents('tr').within(($mainRow) => {
889 label && cy.contains(label);
892 cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as('secondaryRows');
894 if (Array.isArray(val)) {
895 val.forEach(v => cy.get('@secondaryRows').contains(v));
897 cy.get('@secondaryRows').contains(val);
901 cy.get('@secondaryRows').contains(collection);
905 if (Array.isArray(val)) {
906 val.forEach(v => cy.contains(v));
912 cy.contains(collection);
920 const verifyIOParameterImage = (name, url) => {
921 cy.get('table tr').contains(name).parents('tr').within(() => {
922 cy.get('[alt="Inline Preview"]')
923 .should('be.visible')
925 expect($img[0].naturalWidth).to.be.greaterThan(0);
926 expect($img[0].src).contains(url);
931 it('displays IO parameters with keep links and previews', function() {
932 // Create output collection for real files
933 cy.createCollection(adminUser.token, {
934 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
935 owner_uuid: activeUser.user.uuid,
936 }).then((testOutputCollection) => {
937 cy.loginAs(activeUser);
939 cy.goToPath(`/collections/${testOutputCollection.uuid}`);
941 cy.get('[data-cy=upload-button]').click();
943 cy.fixture('files/cat.png', 'base64').then(content => {
944 cy.get('[data-cy=drag-and-drop]').upload(content, 'cat.png');
945 cy.get('[data-cy=form-submit-btn]').click();
946 cy.waitForDom().get('[data-cy=form-submit-btn]').should('not.exist');
947 // Confirm final collection state.
948 cy.get('[data-cy=collection-files-panel]')
949 .contains('cat.png').should('exist');
952 cy.getCollection(activeUser.token, testOutputCollection.uuid).as('testOutputCollection');
955 // Get updated collection pdh
956 cy.getAll('@testOutputCollection').then(([testOutputCollection]) => {
957 // Add output uuid and inputs to container request
958 cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
960 res.body.output_uuid = testOutputCollection.uuid;
961 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
962 content: testInputs.map((param) => (param.input)).reduce((acc, val) => (Object.assign(acc, val)), {})
964 res.body.mounts["/var/lib/cwl/workflow.json"] = {
968 inputs: testInputs.map((input) => (input.definition)),
969 outputs: testOutputs.map((output) => (output.definition))
976 // Stub fake output collection
977 cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${testOutputCollection.uuid}*`}, {
980 uuid: testOutputCollection.uuid,
981 portable_data_hash: testOutputCollection.portable_data_hash,
985 // Stub fake output json
986 cy.intercept({method: 'GET', url: '**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json'}, {
988 body: testOutputs.map((param) => (param.output)).reduce((acc, val) => (Object.assign(acc, val)), {})
991 // Stub webdav response, points to output json
992 cy.intercept({method: 'PROPFIND', url: '*'}, {
993 fixture: 'webdav-propfind-outputs.xml',
997 createContainerRequest(
999 'test_container_request',
1001 ['echo', 'hello world'],
1003 .as('containerRequest');
1005 cy.getAll('@containerRequest', '@testOutputCollection').then(function([containerRequest, testOutputCollection]) {
1006 cy.goToPath(`/processes/${containerRequest.uuid}`);
1007 cy.get('[data-cy=process-io-card] h6').contains('Inputs')
1008 .parents('[data-cy=process-io-card]').within(() => {
1009 verifyIOParameter('input_file', null, "Label Description", 'input1.tar', '00000000000000000000000000000000+01');
1010 verifyIOParameter('input_file', null, "Label Description", 'input1-2.txt', undefined, true);
1011 verifyIOParameter('input_file', null, "Label Description", 'input1-3.txt', undefined, true);
1012 verifyIOParameter('input_file', null, "Label Description", 'input1-4.txt', undefined, true);
1013 verifyIOParameter('input_dir', null, "Doc Description", '/', '11111111111111111111111111111111+01');
1014 verifyIOParameter('input_bool', null, "Doc desc 1, Doc desc 2", 'true');
1015 verifyIOParameter('input_int', null, null, '1');
1016 verifyIOParameter('input_long', null, null, '1');
1017 verifyIOParameter('input_float', null, null, '1.5');
1018 verifyIOParameter('input_double', null, null, '1.3');
1019 verifyIOParameter('input_string', null, null, 'Hello World');
1020 verifyIOParameter('input_file_array', null, null, 'input2.tar', '00000000000000000000000000000000+02');
1021 verifyIOParameter('input_file_array', null, null, 'input3.tar', undefined, true);
1022 verifyIOParameter('input_file_array', null, null, 'input3-2.txt', undefined, true);
1023 verifyIOParameter('input_file_array', null, null, 'Cannot display value', undefined, true);
1024 verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+02');
1025 verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+03', true);
1026 verifyIOParameter('input_dir_array', null, null, 'Cannot display value', undefined, true);
1027 verifyIOParameter('input_int_array', null, null, ["1", "3", "5", "Cannot display value"]);
1028 verifyIOParameter('input_long_array', null, null, ["10", "20", "Cannot display value"]);
1029 verifyIOParameter('input_float_array', null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
1030 verifyIOParameter('input_double_array', null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
1031 verifyIOParameter('input_string_array', null, null, ["Hello", "World", "!", "Cannot display value"]);
1032 verifyIOParameter('input_bool_include', null, null, "Cannot display value");
1033 verifyIOParameter('input_int_include', null, null, "Cannot display value");
1034 verifyIOParameter('input_float_include', null, null, "Cannot display value");
1035 verifyIOParameter('input_string_include', null, null, "Cannot display value");
1036 verifyIOParameter('input_file_include', null, null, "Cannot display value");
1037 verifyIOParameter('input_directory_include', null, null, "Cannot display value");
1038 verifyIOParameter('input_file_url', null, null, "http://example.com/index.html");
1040 cy.get('[data-cy=process-io-card] h6').contains('Outputs')
1041 .parents('[data-cy=process-io-card]').within((ctx) => {
1042 cy.get(ctx).scrollIntoView();
1043 cy.get('[data-cy="io-preview-image-toggle"]').click({waitForAnimations: false});
1044 const outPdh = testOutputCollection.portable_data_hash;
1046 verifyIOParameter('output_file', null, "Label Description", 'cat.png', `${outPdh}`);
1047 verifyIOParameterImage('output_file', `/c=${outPdh}/cat.png`);
1048 verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'main.dat', `${outPdh}`);
1049 verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary.dat', undefined, true);
1050 verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary2.dat', undefined, true);
1051 verifyIOParameter('output_dir', null, "Doc desc 1, Doc desc 2", 'outdir1', `${outPdh}`);
1052 verifyIOParameter('output_bool', null, null, 'true');
1053 verifyIOParameter('output_int', null, null, '1');
1054 verifyIOParameter('output_long', null, null, '1');
1055 verifyIOParameter('output_float', null, null, '100.5');
1056 verifyIOParameter('output_double', null, null, '100.3');
1057 verifyIOParameter('output_string', null, null, 'Hello output');
1058 verifyIOParameter('output_file_array', null, null, 'output2.tar', `${outPdh}`);
1059 verifyIOParameter('output_file_array', null, null, 'output3.tar', undefined, true);
1060 verifyIOParameter('output_dir_array', null, null, 'outdir2', `${outPdh}`);
1061 verifyIOParameter('output_dir_array', null, null, 'outdir3', undefined, true);
1062 verifyIOParameter('output_int_array', null, null, ["10", "11", "12"]);
1063 verifyIOParameter('output_long_array', null, null, ["51", "52"]);
1064 verifyIOParameter('output_float_array', null, null, ["100.2", "100.4", "100.6"]);
1065 verifyIOParameter('output_double_array', null, null, ["100.1", "100.2", "100.3"]);
1066 verifyIOParameter('output_string_array', null, null, ["Hello", "Output", "!"]);
1071 it('displays IO parameters with no value', function() {
1073 const fakeOutputUUID = 'zzzzz-4zz18-abcdefghijklmno';
1074 const fakeOutputPDH = '11111111111111111111111111111111+99/';
1076 cy.loginAs(activeUser);
1078 // Add output uuid and inputs to container request
1079 cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
1080 req.reply((res) => {
1081 res.body.output_uuid = fakeOutputUUID;
1082 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
1085 res.body.mounts["/var/lib/cwl/workflow.json"] = {
1089 inputs: testInputs.map((input) => (input.definition)),
1090 outputs: testOutputs.map((output) => (output.definition))
1097 // Stub fake output collection
1098 cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${fakeOutputUUID}*`}, {
1101 uuid: fakeOutputUUID,
1102 portable_data_hash: fakeOutputPDH,
1106 // Stub fake output json
1107 cy.intercept({method: 'GET', url: `**/c%3D${fakeOutputUUID}/cwl.output.json`}, {
1112 cy.readFile('cypress/fixtures/webdav-propfind-outputs.xml').then((data) => {
1113 // Stub webdav response, points to output json
1114 cy.intercept({method: 'PROPFIND', url: '*'}, {
1116 body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID)
1120 createContainerRequest(
1122 'test_container_request',
1124 ['echo', 'hello world'],
1126 .as('containerRequest');
1128 cy.getAll('@containerRequest').then(function([containerRequest]) {
1129 cy.goToPath(`/processes/${containerRequest.uuid}`);
1130 cy.get('[data-cy=process-io-card] h6').contains('Inputs')
1131 .parents('[data-cy=process-io-card]').within(() => {
1134 cy.get('tbody tr').each((item) => {
1135 cy.wrap(item).contains('No value');
1138 cy.get('[data-cy=process-io-card] h6').contains('Outputs')
1139 .parents('[data-cy=process-io-card]').within(() => {
1140 cy.get('tbody tr').each((item) => {
1141 cy.wrap(item).contains('No value');
1148 it('allows copying processes', function() {
1149 const crName = 'first_container_request';
1150 const copiedCrName = 'copied_container_request';
1151 createContainerRequest(
1155 ['echo', 'hello world'],
1157 .then(function(containerRequest) {
1158 cy.loginAs(activeUser);
1159 cy.goToPath(`/processes/${containerRequest.uuid}`);
1160 cy.get('[data-cy=process-details]').should('contain', crName);
1162 cy.get('[data-cy=process-details]').find('button[title="More options"]').click();
1163 cy.get('ul[data-cy=context-menu]').contains("Copy and re-run process").click();
1166 cy.get('[data-cy=form-dialog]').within(() => {
1167 cy.get('input[name=name]').clear().type(copiedCrName);
1168 cy.get('[data-cy=projects-tree-home-tree-picker]').click();
1169 cy.get('[data-cy=form-submit-btn]').click();
1172 cy.get('[data-cy=process-details]').should('contain', copiedCrName);
1173 cy.get('[data-cy=process-details]').find('button').contains('Run');
1176 const getFakeContainer = (fakeContainerUuid) => ({
1177 href: `/containers/${fakeContainerUuid}`,
1178 kind: "arvados#container",
1179 etag: "ecfosljpnxfari9a8m7e4yv06",
1180 uuid: fakeContainerUuid,
1181 owner_uuid: "zzzzz-tpzed-000000000000000",
1182 created_at: "2023-02-13T15:55:47.308915000Z",
1183 modified_by_client_uuid: "zzzzz-ozdt8-q6dzdi1lcc03155",
1184 modified_by_user_uuid: "zzzzz-tpzed-000000000000000",
1185 modified_at: "2023-02-15T19:12:45.987086000Z",
1187 "arvados-cwl-runner",
1190 "--project-uuid=zzzzz-j7d0g-yr18k784zplfeza",
1191 "/var/lib/cwl/workflow.json#main",
1192 "/var/lib/cwl/cwl.input.json",
1194 container_image: "4ad7d11381df349e464694762db14e04+303",
1195 cwd: "/var/spool/cwl",
1199 locked_by_uuid: null,
1202 output_path: "/var/spool/cwl",
1204 runtime_constraints: {
1209 hardware_capability: "",
1211 keep_cache_disk: 2147483648,
1219 scheduling_parameters: {
1224 runtime_user_uuid: "zzzzz-tpzed-vllbpebicy84rd5",
1225 runtime_auth_scopes: ["all"],
1227 gateway_address: null,
1228 interactive_session_started: false,
1229 output_storage_classes: ["default"],
1230 output_properties: {},
1232 subrequests_cost: 0.0,
1235 it('shows cancel button when appropriate', function() {
1236 // Ignore collection requests
1237 cy.intercept({method: 'GET', url: `**/arvados/v1/collections/*`}, {
1242 // Uncommitted container
1243 const crUncommitted = `Test process ${Math.floor(Math.random() * 999999)}`;
1244 createContainerRequest(
1248 ['echo', 'hello world'],
1249 false, 'Uncommitted')
1250 .then(function(containerRequest) {
1251 // Navigate to process and verify run / cancel button
1252 cy.goToPath(`/processes/${containerRequest.uuid}`);
1254 cy.get('[data-cy=process-details]').should('contain', crUncommitted);
1255 cy.get('[data-cy=process-run-button]').should('exist');
1256 cy.get('[data-cy=process-cancel-button]').should('not.exist');
1260 const crQueued = `Test process ${Math.floor(Math.random() * 999999)}`;
1261 const fakeCrUuid = 'zzzzz-dz642-000000000000001';
1262 createContainerRequest(
1266 ['echo', 'hello world'],
1268 .then(function(containerRequest) {
1269 // Fake container uuid
1270 cy.intercept({method: 'GET', url: `**/arvados/v1/container_requests/${containerRequest.uuid}`}, (req) => {
1271 req.reply((res) => {
1272 res.body.output_uuid = fakeCrUuid;
1273 res.body.priority = 500;
1274 res.body.state = "Committed";
1279 const container = getFakeContainer(fakeCrUuid);
1280 cy.intercept({method: 'GET', url: `**/arvados/v1/container/${fakeCrUuid}`}, {
1282 body: {...container, state: "Queued", priority: 500}
1285 // Navigate to process and verify cancel button
1286 cy.goToPath(`/processes/${containerRequest.uuid}`);
1288 cy.get('[data-cy=process-details]').should('contain', crQueued);
1289 cy.get('[data-cy=process-cancel-button]').contains('Cancel');
1293 const crLocked = `Test process ${Math.floor(Math.random() * 999999)}`;
1294 const fakeCrLockedUuid = 'zzzzz-dz642-000000000000002';
1295 createContainerRequest(
1299 ['echo', 'hello world'],
1301 .then(function(containerRequest) {
1302 // Fake container uuid
1303 cy.intercept({method: 'GET', url: `**/arvados/v1/container_requests/${containerRequest.uuid}`}, (req) => {
1304 req.reply((res) => {
1305 res.body.output_uuid = fakeCrLockedUuid;
1306 res.body.priority = 500;
1307 res.body.state = "Committed";
1312 const container = getFakeContainer(fakeCrLockedUuid);
1313 cy.intercept({method: 'GET', url: `**/arvados/v1/container/${fakeCrLockedUuid}`}, {
1315 body: {...container, state: "Locked", priority: 500}
1318 // Navigate to process and verify cancel button
1319 cy.goToPath(`/processes/${containerRequest.uuid}`);
1321 cy.get('[data-cy=process-details]').should('contain', crLocked);
1322 cy.get('[data-cy=process-cancel-button]').contains('Cancel');
1325 // On Hold container
1326 const crOnHold = `Test process ${Math.floor(Math.random() * 999999)}`;
1327 const fakeCrOnHoldUuid = 'zzzzz-dz642-000000000000003';
1328 createContainerRequest(
1332 ['echo', 'hello world'],
1334 .then(function(containerRequest) {
1335 // Fake container uuid
1336 cy.intercept({method: 'GET', url: `**/arvados/v1/container_requests/${containerRequest.uuid}`}, (req) => {
1337 req.reply((res) => {
1338 res.body.output_uuid = fakeCrOnHoldUuid;
1339 res.body.priority = 0;
1340 res.body.state = "Committed";
1345 const container = getFakeContainer(fakeCrOnHoldUuid);
1346 cy.intercept({method: 'GET', url: `**/arvados/v1/container/${fakeCrOnHoldUuid}`}, {
1348 body: {...container, state: "Queued", priority: 0}
1351 // Navigate to process and verify cancel button
1352 cy.goToPath(`/processes/${containerRequest.uuid}`);
1354 cy.get('[data-cy=process-details]').should('contain', crOnHold);
1355 cy.get('[data-cy=process-run-button]').should('exist');
1356 cy.get('[data-cy=process-cancel-button]').should('not.exist');