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 const testOutputs = [
641 "id": "#main/output_file",
642 "label": "Label Description",
647 "basename": "cat.png",
649 "location": "cat.png"
655 "id": "#main/output_file_with_secondary",
656 "doc": "Doc Description",
660 "output_file_with_secondary": {
661 "basename": "main.dat",
663 "location": "main.dat",
666 "basename": "secondary.dat",
668 "location": "secondary.dat"
671 "basename": "secondary2.dat",
673 "location": "secondary2.dat"
681 "id": "#main/output_dir",
682 "doc": ["Doc desc 1", "Doc desc 2"],
687 "basename": "outdir1",
688 "class": "Directory",
689 "location": "outdir1"
695 "id": "#main/output_bool",
704 "id": "#main/output_int",
713 "id": "#main/output_long",
722 "id": "#main/output_float",
726 "output_float": 100.5
731 "id": "#main/output_double",
735 "output_double": 100.3
740 "id": "#main/output_string",
744 "output_string": "Hello output"
749 "id": "#main/output_file_array",
756 "output_file_array": [
758 "basename": "output2.tar",
760 "location": "output2.tar"
763 "basename": "output3.tar",
765 "location": "output3.tar"
772 "id": "#main/output_dir_array",
774 "items": "Directory",
779 "output_dir_array": [
781 "basename": "outdir2",
782 "class": "Directory",
783 "location": "outdir2"
786 "basename": "outdir3",
787 "class": "Directory",
788 "location": "outdir3"
795 "id": "#main/output_int_array",
802 "output_int_array": [
811 "id": "#main/output_long_array",
818 "output_long_array": [
826 "id": "#main/output_float_array",
833 "output_float_array": [
842 "id": "#main/output_double_array",
849 "output_double_array": [
858 "id": "#main/output_string_array",
865 "output_string_array": [
874 const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
875 cy.get('table tr').contains(name).parents('tr').within(($mainRow) => {
876 label && cy.contains(label);
879 cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as('secondaryRows');
881 if (Array.isArray(val)) {
882 val.forEach(v => cy.get('@secondaryRows').contains(v));
884 cy.get('@secondaryRows').contains(val);
888 cy.get('@secondaryRows').contains(collection);
892 if (Array.isArray(val)) {
893 val.forEach(v => cy.contains(v));
899 cy.contains(collection);
907 const verifyIOParameterImage = (name, url) => {
908 cy.get('table tr').contains(name).parents('tr').within(() => {
909 cy.get('[alt="Inline Preview"]')
910 .should('be.visible')
912 expect($img[0].naturalWidth).to.be.greaterThan(0);
913 expect($img[0].src).contains(url);
918 it('displays IO parameters with keep links and previews', function() {
919 // Create output collection for real files
920 cy.createCollection(adminUser.token, {
921 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
922 owner_uuid: activeUser.user.uuid,
923 }).then((testOutputCollection) => {
924 cy.loginAs(activeUser);
926 cy.goToPath(`/collections/${testOutputCollection.uuid}`);
928 cy.get('[data-cy=upload-button]').click();
930 cy.fixture('files/cat.png', 'base64').then(content => {
931 cy.get('[data-cy=drag-and-drop]').upload(content, 'cat.png');
932 cy.get('[data-cy=form-submit-btn]').click();
933 cy.waitForDom().get('[data-cy=form-submit-btn]').should('not.exist');
934 // Confirm final collection state.
935 cy.get('[data-cy=collection-files-panel]')
936 .contains('cat.png').should('exist');
939 cy.getCollection(activeUser.token, testOutputCollection.uuid).as('testOutputCollection');
942 // Get updated collection pdh
943 cy.getAll('@testOutputCollection').then(([testOutputCollection]) => {
944 // Add output uuid and inputs to container request
945 cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
947 res.body.output_uuid = testOutputCollection.uuid;
948 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
949 content: testInputs.map((param) => (param.input)).reduce((acc, val) => (Object.assign(acc, val)), {})
951 res.body.mounts["/var/lib/cwl/workflow.json"] = {
955 inputs: testInputs.map((input) => (input.definition)),
956 outputs: testOutputs.map((output) => (output.definition))
963 // Stub fake output collection
964 cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${testOutputCollection.uuid}*`}, {
967 uuid: testOutputCollection.uuid,
968 portable_data_hash: testOutputCollection.portable_data_hash,
972 // Stub fake output json
973 cy.intercept({method: 'GET', url: '**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json'}, {
975 body: testOutputs.map((param) => (param.output)).reduce((acc, val) => (Object.assign(acc, val)), {})
978 // Stub webdav response, points to output json
979 cy.intercept({method: 'PROPFIND', url: '*'}, {
980 fixture: 'webdav-propfind-outputs.xml',
984 createContainerRequest(
986 'test_container_request',
988 ['echo', 'hello world'],
990 .as('containerRequest');
992 cy.getAll('@containerRequest', '@testOutputCollection').then(function([containerRequest, testOutputCollection]) {
993 cy.goToPath(`/processes/${containerRequest.uuid}`);
994 cy.get('[data-cy=process-io-card] h6').contains('Inputs')
995 .parents('[data-cy=process-io-card]').within(() => {
996 verifyIOParameter('input_file', null, "Label Description", 'input1.tar', '00000000000000000000000000000000+01');
997 verifyIOParameter('input_file', null, "Label Description", 'input1-2.txt', undefined, true);
998 verifyIOParameter('input_file', null, "Label Description", 'input1-3.txt', undefined, true);
999 verifyIOParameter('input_file', null, "Label Description", 'input1-4.txt', undefined, true);
1000 verifyIOParameter('input_dir', null, "Doc Description", '/', '11111111111111111111111111111111+01');
1001 verifyIOParameter('input_bool', null, "Doc desc 1, Doc desc 2", 'true');
1002 verifyIOParameter('input_int', null, null, '1');
1003 verifyIOParameter('input_long', null, null, '1');
1004 verifyIOParameter('input_float', null, null, '1.5');
1005 verifyIOParameter('input_double', null, null, '1.3');
1006 verifyIOParameter('input_string', null, null, 'Hello World');
1007 verifyIOParameter('input_file_array', null, null, 'input2.tar', '00000000000000000000000000000000+02');
1008 verifyIOParameter('input_file_array', null, null, 'input3.tar', undefined, true);
1009 verifyIOParameter('input_file_array', null, null, 'input3-2.txt', undefined, true);
1010 verifyIOParameter('input_file_array', null, null, 'Cannot display value', undefined, true);
1011 verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+02');
1012 verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+03', true);
1013 verifyIOParameter('input_dir_array', null, null, 'Cannot display value', undefined, true);
1014 verifyIOParameter('input_int_array', null, null, ["1", "3", "5", "Cannot display value"]);
1015 verifyIOParameter('input_long_array', null, null, ["10", "20", "Cannot display value"]);
1016 verifyIOParameter('input_float_array', null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
1017 verifyIOParameter('input_double_array', null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
1018 verifyIOParameter('input_string_array', null, null, ["Hello", "World", "!", "Cannot display value"]);
1019 verifyIOParameter('input_bool_include', null, null, "Cannot display value");
1020 verifyIOParameter('input_int_include', null, null, "Cannot display value");
1021 verifyIOParameter('input_float_include', null, null, "Cannot display value");
1022 verifyIOParameter('input_string_include', null, null, "Cannot display value");
1023 verifyIOParameter('input_file_include', null, null, "Cannot display value");
1024 verifyIOParameter('input_directory_include', null, null, "Cannot display value");
1026 cy.get('[data-cy=process-io-card] h6').contains('Outputs')
1027 .parents('[data-cy=process-io-card]').within((ctx) => {
1028 cy.get(ctx).scrollIntoView();
1029 cy.get('[data-cy="io-preview-image-toggle"]').click();
1030 const outPdh = testOutputCollection.portable_data_hash;
1032 verifyIOParameter('output_file', null, "Label Description", 'cat.png', `${outPdh}`);
1033 verifyIOParameterImage('output_file', `/c=${outPdh}/cat.png`);
1034 verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'main.dat', `${outPdh}`);
1035 verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary.dat', undefined, true);
1036 verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary2.dat', undefined, true);
1037 verifyIOParameter('output_dir', null, "Doc desc 1, Doc desc 2", 'outdir1', `${outPdh}`);
1038 verifyIOParameter('output_bool', null, null, 'true');
1039 verifyIOParameter('output_int', null, null, '1');
1040 verifyIOParameter('output_long', null, null, '1');
1041 verifyIOParameter('output_float', null, null, '100.5');
1042 verifyIOParameter('output_double', null, null, '100.3');
1043 verifyIOParameter('output_string', null, null, 'Hello output');
1044 verifyIOParameter('output_file_array', null, null, 'output2.tar', `${outPdh}`);
1045 verifyIOParameter('output_file_array', null, null, 'output3.tar', undefined, true);
1046 verifyIOParameter('output_dir_array', null, null, 'outdir2', `${outPdh}`);
1047 verifyIOParameter('output_dir_array', null, null, 'outdir3', undefined, true);
1048 verifyIOParameter('output_int_array', null, null, ["10", "11", "12"]);
1049 verifyIOParameter('output_long_array', null, null, ["51", "52"]);
1050 verifyIOParameter('output_float_array', null, null, ["100.2", "100.4", "100.6"]);
1051 verifyIOParameter('output_double_array', null, null, ["100.1", "100.2", "100.3"]);
1052 verifyIOParameter('output_string_array', null, null, ["Hello", "Output", "!"]);
1057 it('displays IO parameters with no value', function() {
1059 const fakeOutputUUID = 'zzzzz-4zz18-abcdefghijklmno';
1060 const fakeOutputPDH = '11111111111111111111111111111111+99/';
1062 cy.loginAs(activeUser);
1064 // Add output uuid and inputs to container request
1065 cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
1066 req.reply((res) => {
1067 res.body.output_uuid = fakeOutputUUID;
1068 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
1071 res.body.mounts["/var/lib/cwl/workflow.json"] = {
1075 inputs: testInputs.map((input) => (input.definition)),
1076 outputs: testOutputs.map((output) => (output.definition))
1083 // Stub fake output collection
1084 cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${fakeOutputUUID}*`}, {
1087 uuid: fakeOutputUUID,
1088 portable_data_hash: fakeOutputPDH,
1092 // Stub fake output json
1093 cy.intercept({method: 'GET', url: `**/c%3D${fakeOutputUUID}/cwl.output.json`}, {
1098 cy.readFile('cypress/fixtures/webdav-propfind-outputs.xml').then((data) => {
1099 // Stub webdav response, points to output json
1100 cy.intercept({method: 'PROPFIND', url: '*'}, {
1102 body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID)
1106 createContainerRequest(
1108 'test_container_request',
1110 ['echo', 'hello world'],
1112 .as('containerRequest');
1114 cy.getAll('@containerRequest').then(function([containerRequest]) {
1115 cy.goToPath(`/processes/${containerRequest.uuid}`);
1116 cy.get('[data-cy=process-io-card] h6').contains('Inputs')
1117 .parents('[data-cy=process-io-card]').within(() => {
1120 cy.get('tbody tr').each((item) => {
1121 cy.wrap(item).contains('No value');
1124 cy.get('[data-cy=process-io-card] h6').contains('Outputs')
1125 .parents('[data-cy=process-io-card]').within(() => {
1126 cy.get('tbody tr').each((item) => {
1127 cy.wrap(item).contains('No value');