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]')
110 .should('not.contain', 'No logs yet')
111 .and('contain', 'hello world');
116 it('filters process logs by event type', function() {
117 const nodeInfoLogs = [
119 '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',
122 'vendor_id : GenuineIntel',
125 'model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz'
127 const crunchRunLogs = [
128 '2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection',
129 '2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started',
130 '2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)',
131 '2022-03-22T13:56:26.244862836Z Executing container \'zzzzz-dz642-1wokwvcct9s9du3\' using docker runtime',
132 '2022-03-22T13:56:26.245037738Z Executing on host \'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p\'',
135 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.',
136 'Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.',
137 'In hac habitasse platea dictumst.',
138 'Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.',
139 'Interdum et malesuada fames ac ante ipsum primis in faucibus.',
140 'Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.',
141 'Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.',
142 'Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.',
143 'Donec vitae leo id augue gravida bibendum.',
144 'Nam libero libero, pretium ac faucibus elementum, mattis nec ex.',
145 'Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.',
146 'Aliquam viverra nisi nulla, et efficitur dolor mattis in.',
147 '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.',
148 '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.',
149 'Phasellus non ex quis arcu tempus faucibus molestie in sapien.',
150 'Duis tristique semper dolor, vitae pulvinar risus.',
151 'Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.',
152 'Nulla eget mollis ipsum.',
155 createContainerRequest(
157 'test_container_request',
159 ['echo', 'hello world'],
161 .then(function(containerRequest) {
162 cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
163 'node-info', nodeInfoLogs).as('nodeInfoLogs');
164 cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
165 'crunch-run', crunchRunLogs).as('crunchRunLogs');
166 cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
167 'stdout', stdoutLogs).as('stdoutLogs');
168 cy.getAll('@stdoutLogs', '@nodeInfoLogs', '@crunchRunLogs').then(function() {
169 cy.loginAs(activeUser);
170 cy.goToPath(`/processes/${containerRequest.uuid}`);
171 // Should show main logs by default
172 cy.get('[data-cy=process-logs-filter]').should('contain', 'Main logs');
173 cy.get('[data-cy=process-logs]')
174 .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
175 .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
176 .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
178 cy.get('[data-cy=process-logs-filter]').click();
179 cy.get('body').contains('li', 'All logs').click();
180 cy.get('[data-cy=process-logs]')
181 .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
182 .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
183 .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
184 // Select 'node-info' logs
185 cy.get('[data-cy=process-logs-filter]').click();
186 cy.get('body').contains('li', 'node-info').click();
187 cy.get('[data-cy=process-logs]')
188 .should('not.contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
189 .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
190 .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
191 // Select 'stdout' logs
192 cy.get('[data-cy=process-logs-filter]').click();
193 cy.get('body').contains('li', 'stdout').click();
194 cy.get('[data-cy=process-logs]')
195 .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
196 .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
197 .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
202 it('should show runtime status indicators', function() {
203 // Setup running container with runtime_status error & warning messages
204 createContainerRequest(
206 'test_container_request',
208 ['echo', 'hello world'],
210 .as('containerRequest')
211 .then(function(containerRequest) {
212 expect(containerRequest.state).to.equal('Committed');
213 expect(containerRequest.container_uuid).not.to.be.equal('');
215 cy.getContainer(activeUser.token, containerRequest.container_uuid)
216 .then(function(queuedContainer) {
217 expect(queuedContainer.state).to.be.equal('Queued');
219 cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
221 }).then(function(lockedContainer) {
222 expect(lockedContainer.state).to.be.equal('Locked');
224 cy.updateContainer(adminUser.token, lockedContainer.uuid, {
227 error: 'Something went wrong',
228 errorDetail: 'Process exited with status 1',
229 warning: 'Free disk space is low',
232 .as('runningContainer')
233 .then(function(runningContainer) {
234 expect(runningContainer.state).to.be.equal('Running');
235 expect(runningContainer.runtime_status).to.be.deep.equal({
236 'error': 'Something went wrong',
237 'errorDetail': 'Process exited with status 1',
238 'warning': 'Free disk space is low',
243 // Test that the UI shows the error and warning messages
244 cy.getAll('@containerRequest', '@runningContainer').then(function([containerRequest]) {
245 cy.loginAs(activeUser);
246 cy.goToPath(`/processes/${containerRequest.uuid}`);
247 cy.get('[data-cy=process-runtime-status-error]')
248 .should('contain', 'Something went wrong')
249 .and('contain', 'Process exited with status 1');
250 cy.get('[data-cy=process-runtime-status-warning]')
251 .should('contain', 'Free disk space is low')
252 .and('contain', 'No additional warning details available');
256 // Force container_count for testing
257 let containerCount = 2;
258 cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
260 res.body.container_count = containerCount;
264 cy.getAll('@containerRequest').then(function([containerRequest]) {
265 cy.goToPath(`/processes/${containerRequest.uuid}`);
266 cy.get('[data-cy=process-runtime-status-retry-warning]')
267 .should('contain', 'Process retried 1 time');
270 cy.getAll('@containerRequest').then(function([containerRequest]) {
272 cy.goToPath(`/processes/${containerRequest.uuid}`);
273 cy.get('[data-cy=process-runtime-status-retry-warning]')
274 .should('contain', 'Process retried 2 times');
282 "id": "#main/input_file",
283 "label": "Label Description",
288 "basename": "input1.tar",
290 "location": "keep:00000000000000000000000000000000+01/input1.tar",
293 "basename": "input1-2.txt",
295 "location": "keep:00000000000000000000000000000000+01/input1-2.txt"
298 "basename": "input1-3.txt",
300 "location": "keep:00000000000000000000000000000000+01/input1-3.txt"
303 "basename": "input1-4.txt",
305 "location": "keep:00000000000000000000000000000000+01/input1-4.txt"
313 "id": "#main/input_dir",
314 "doc": "Doc Description",
319 "basename": "11111111111111111111111111111111+01",
320 "class": "Directory",
321 "location": "keep:11111111111111111111111111111111+01"
327 "id": "#main/input_bool",
328 "doc": ["Doc desc 1", "Doc desc 2"],
337 "id": "#main/input_int",
346 "id": "#main/input_long",
355 "id": "#main/input_float",
364 "id": "#main/input_double",
373 "id": "#main/input_string",
377 "input_string": "Hello World",
382 "id": "#main/input_file_array",
389 "input_file_array": [
391 "basename": "input2.tar",
393 "location": "keep:00000000000000000000000000000000+02/input2.tar"
396 "basename": "input3.tar",
398 "location": "keep:00000000000000000000000000000000+03/input3.tar",
401 "basename": "input3-2.txt",
403 "location": "keep:00000000000000000000000000000000+03/input3-2.txt"
412 "id": "#main/input_dir_array",
414 "items": "Directory",
421 "basename": "11111111111111111111111111111111+02",
422 "class": "Directory",
423 "location": "keep:11111111111111111111111111111111+02"
426 "basename": "11111111111111111111111111111111+03",
427 "class": "Directory",
428 "location": "keep:11111111111111111111111111111111+03"
435 "id": "#main/input_int_array",
451 "id": "#main/input_long_array",
458 "input_long_array": [
466 "id": "#main/input_float_array",
473 "input_float_array": [
482 "id": "#main/input_double_array",
489 "input_double_array": [
498 "id": "#main/input_string_array",
505 "input_string_array": [
514 const testOutputs = [
517 "id": "#main/output_file",
518 "label": "Label Description",
523 "basename": "cat.png",
525 "location": "cat.png"
531 "id": "#main/output_file_with_secondary",
532 "doc": "Doc Description",
536 "output_file_with_secondary": {
537 "basename": "main.dat",
539 "location": "main.dat",
542 "basename": "secondary.dat",
544 "location": "secondary.dat"
547 "basename": "secondary2.dat",
549 "location": "secondary2.dat"
557 "id": "#main/output_dir",
558 "doc": ["Doc desc 1", "Doc desc 2"],
563 "basename": "outdir1",
564 "class": "Directory",
565 "location": "outdir1"
571 "id": "#main/output_bool",
580 "id": "#main/output_int",
589 "id": "#main/output_long",
598 "id": "#main/output_float",
602 "output_float": 100.5
607 "id": "#main/output_double",
611 "output_double": 100.3
616 "id": "#main/output_string",
620 "output_string": "Hello output"
625 "id": "#main/output_file_array",
632 "output_file_array": [
634 "basename": "output2.tar",
636 "location": "output2.tar"
639 "basename": "output3.tar",
641 "location": "output3.tar"
648 "id": "#main/output_dir_array",
650 "items": "Directory",
655 "output_dir_array": [
657 "basename": "outdir2",
658 "class": "Directory",
659 "location": "outdir2"
662 "basename": "outdir3",
663 "class": "Directory",
664 "location": "outdir3"
671 "id": "#main/output_int_array",
678 "output_int_array": [
687 "id": "#main/output_long_array",
694 "output_long_array": [
702 "id": "#main/output_float_array",
709 "output_float_array": [
718 "id": "#main/output_double_array",
725 "output_double_array": [
734 "id": "#main/output_string_array",
741 "output_string_array": [
750 const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
751 cy.get('table tr').contains(name).parents('tr').within(($mainRow) => {
752 label && cy.contains(label);
755 cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as('secondaryRows');
757 if (Array.isArray(val)) {
758 val.forEach(v => cy.get('@secondaryRows').contains(v));
760 cy.get('@secondaryRows').contains(val);
764 cy.get('@secondaryRows').contains(collection);
768 if (Array.isArray(val)) {
769 val.forEach(v => cy.contains(v));
775 cy.contains(collection);
783 const verifyIOParameterImage = (name, url) => {
784 cy.get('table tr').contains(name).parents('tr').within(() => {
785 cy.get('[alt="Inline Preview"]')
786 .should('be.visible')
788 expect($img[0].naturalWidth).to.be.greaterThan(0);
789 expect($img[0].src).contains(url);
794 it('displays IO parameters with keep links and previews', function() {
795 // Create output collection for real files
796 cy.createCollection(adminUser.token, {
797 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
798 owner_uuid: activeUser.user.uuid,
799 }).then((testOutputCollection) => {
800 cy.loginAs(activeUser);
802 cy.goToPath(`/collections/${testOutputCollection.uuid}`);
804 cy.get('[data-cy=upload-button]').click();
806 cy.fixture('files/cat.png', 'base64').then(content => {
807 cy.get('[data-cy=drag-and-drop]').upload(content, 'cat.png');
808 cy.get('[data-cy=form-submit-btn]').click();
809 cy.waitForDom().get('[data-cy=form-submit-btn]').should('not.exist');
810 // Confirm final collection state.
811 cy.get('[data-cy=collection-files-panel]')
812 .contains('cat.png').should('exist');
815 cy.getCollection(activeUser.token, testOutputCollection.uuid).as('testOutputCollection');
818 // Get updated collection pdh
819 cy.getAll('@testOutputCollection').then(([testOutputCollection]) => {
820 // Add output uuid and inputs to container request
821 cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
823 res.body.output_uuid = testOutputCollection.uuid;
824 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
825 content: testInputs.map((param) => (param.input)).reduce((acc, val) => (Object.assign(acc, val)), {})
827 res.body.mounts["/var/lib/cwl/workflow.json"] = {
831 inputs: testInputs.map((input) => (input.definition)),
832 outputs: testOutputs.map((output) => (output.definition))
839 // Stub fake output collection
840 cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${testOutputCollection.uuid}*`}, {
843 uuid: testOutputCollection.uuid,
844 portable_data_hash: testOutputCollection.portable_data_hash,
848 // Stub fake output json
849 cy.intercept({method: 'GET', url: '**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json'}, {
851 body: testOutputs.map((param) => (param.output)).reduce((acc, val) => (Object.assign(acc, val)), {})
854 // Stub webdav response, points to output json
855 cy.intercept({method: 'PROPFIND', url: '*'}, {
856 fixture: 'webdav-propfind-outputs.xml',
860 createContainerRequest(
862 'test_container_request',
864 ['echo', 'hello world'],
866 .as('containerRequest');
868 cy.getAll('@containerRequest', '@testOutputCollection').then(function([containerRequest, testOutputCollection]) {
869 cy.goToPath(`/processes/${containerRequest.uuid}`);
870 cy.get('[data-cy=process-io-card] h6').contains('Inputs')
871 .parents('[data-cy=process-io-card]').within(() => {
874 verifyIOParameter('input_file', null, "Label Description", 'input1.tar', '00000000000000000000000000000000+01');
875 verifyIOParameter('input_file', null, "Label Description", 'input1-2.txt', undefined, true);
876 verifyIOParameter('input_file', null, "Label Description", 'input1-3.txt', undefined, true);
877 verifyIOParameter('input_file', null, "Label Description", 'input1-4.txt', undefined, true);
878 verifyIOParameter('input_dir', null, "Doc Description", 'No value', '11111111111111111111111111111111+01');
879 verifyIOParameter('input_bool', null, "Doc desc 1, Doc desc 2", 'true');
880 verifyIOParameter('input_int', null, null, '1');
881 verifyIOParameter('input_long', null, null, '1');
882 verifyIOParameter('input_float', null, null, '1.5');
883 verifyIOParameter('input_double', null, null, '1.3');
884 verifyIOParameter('input_string', null, null, 'Hello World');
885 verifyIOParameter('input_file_array', null, null, 'input2.tar', '00000000000000000000000000000000+02');
886 verifyIOParameter('input_file_array', null, null, 'input3.tar', undefined, true);
887 verifyIOParameter('input_file_array', null, null, 'input3-2.txt', undefined, true);
888 verifyIOParameter('input_dir_array', null, null, 'No value', '11111111111111111111111111111111+02');
889 verifyIOParameter('input_dir_array', null, null, 'No value', '11111111111111111111111111111111+03', true);
890 verifyIOParameter('input_int_array', null, null, ["1", "3", "5"]);
891 verifyIOParameter('input_long_array', null, null, ["10", "20"]);
892 verifyIOParameter('input_float_array', null, null, ["10.2", "10.4", "10.6"]);
893 verifyIOParameter('input_double_array', null, null, ["20.1", "20.2", "20.3"]);
894 verifyIOParameter('input_string_array', null, null, ["Hello", "World", "!"]);
896 cy.get('[data-cy=process-io-card] h6').contains('Outputs')
897 .parents('[data-cy=process-io-card]').within((ctx) => {
898 cy.get(ctx).scrollIntoView();
899 cy.waitForDom().get('[data-cy="io-preview-image-toggle"]').click();
900 const outPdh = testOutputCollection.portable_data_hash;
902 verifyIOParameter('output_file', null, "Label Description", 'cat.png', `${outPdh}`);
903 verifyIOParameterImage('output_file', `/c=${outPdh}/cat.png`);
904 verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'main.dat', `${outPdh}`);
905 verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary.dat', undefined, true);
906 verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary2.dat', undefined, true);
907 verifyIOParameter('output_dir', null, "Doc desc 1, Doc desc 2", 'outdir1', `${outPdh}`);
908 verifyIOParameter('output_bool', null, null, 'true');
909 verifyIOParameter('output_int', null, null, '1');
910 verifyIOParameter('output_long', null, null, '1');
911 verifyIOParameter('output_float', null, null, '100.5');
912 verifyIOParameter('output_double', null, null, '100.3');
913 verifyIOParameter('output_string', null, null, 'Hello output');
914 verifyIOParameter('output_file_array', null, null, 'output2.tar', `${outPdh}`);
915 verifyIOParameter('output_file_array', null, null, 'output3.tar', undefined, true);
916 verifyIOParameter('output_dir_array', null, null, 'outdir2', `${outPdh}`);
917 verifyIOParameter('output_dir_array', null, null, 'outdir3', undefined, true);
918 verifyIOParameter('output_int_array', null, null, ["10", "11", "12"]);
919 verifyIOParameter('output_long_array', null, null, ["51", "52"]);
920 verifyIOParameter('output_float_array', null, null, ["100.2", "100.4", "100.6"]);
921 verifyIOParameter('output_double_array', null, null, ["100.1", "100.2", "100.3"]);
922 verifyIOParameter('output_string_array', null, null, ["Hello", "Output", "!"]);
927 it('displays IO parameters with no value', function() {
929 const fakeOutputUUID = 'zzzzz-4zz18-abcdefghijklmno';
930 const fakeOutputPDH = '11111111111111111111111111111111+99/';
932 cy.loginAs(activeUser);
934 // Add output uuid and inputs to container request
935 cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
937 res.body.output_uuid = fakeOutputUUID;
938 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
941 res.body.mounts["/var/lib/cwl/workflow.json"] = {
945 inputs: testInputs.map((input) => (input.definition)),
946 outputs: testOutputs.map((output) => (output.definition))
953 // Stub fake output collection
954 cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${fakeOutputUUID}*`}, {
957 uuid: fakeOutputUUID,
958 portable_data_hash: fakeOutputPDH,
962 // Stub fake output json
963 cy.intercept({method: 'GET', url: `**/c%3D${fakeOutputUUID}/cwl.output.json`}, {
968 cy.readFile('cypress/fixtures/webdav-propfind-outputs.xml').then((data) => {
969 // Stub webdav response, points to output json
970 cy.intercept({method: 'PROPFIND', url: '*'}, {
972 body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID)
976 createContainerRequest(
978 'test_container_request',
980 ['echo', 'hello world'],
982 .as('containerRequest');
984 cy.getAll('@containerRequest').then(function([containerRequest]) {
985 cy.goToPath(`/processes/${containerRequest.uuid}`);
986 cy.get('[data-cy=process-io-card] h6').contains('Inputs')
987 .parents('[data-cy=process-io-card]').within(() => {
990 cy.get('tbody tr').each((item) => {
991 cy.wrap(item).contains('No value');
994 cy.get('[data-cy=process-io-card] h6').contains('Outputs')
995 .parents('[data-cy=process-io-card]').within(() => {
996 cy.get('tbody tr').each((item) => {
997 cy.wrap(item).contains('No value');