19482: Fix tests
[arvados-workbench2.git] / cypress / integration / process.spec.js
index 48b936cf69c8e50e225b32e8d316826cec67c141..f6fbffdc2e039cbe48fdfd3cd5817381522afd67 100644 (file)
@@ -106,13 +106,50 @@ describe('Process tests', function() {
                 },
                 event_type: 'stdout'
             }).then(function(log) {
-                cy.get('[data-cy=process-logs]')
+                cy.get('[data-cy=process-logs]', {timeout: 7000})
                     .should('not.contain', 'No logs yet')
                     .and('contain', 'hello world');
             })
         });
     });
 
+    it('shows process details', function() {
+        createContainerRequest(
+            activeUser,
+            `test_container_request ${Math.floor(Math.random() * 999999)}`,
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .then(function(containerRequest) {
+            cy.loginAs(activeUser);
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-details]').should('contain', containerRequest.name);
+            cy.get('[data-cy=process-details-attributes-modifiedby-user]').contains(`Active User (${activeUser.user.uuid})`);
+            cy.get('[data-cy=process-details-attributes-runtime-user]').should('not.exist');
+        });
+
+        // Fake submitted by another user
+        cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
+            req.reply((res) => {
+                res.body.modified_by_user_uuid = 'zzzzz-tpzed-000000000000000';
+            });
+        });
+
+        createContainerRequest(
+            activeUser,
+            `test_container_request ${Math.floor(Math.random() * 999999)}`,
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .then(function(containerRequest) {
+            cy.loginAs(activeUser);
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-details]').should('contain', containerRequest.name);
+            cy.get('[data-cy=process-details-attributes-modifiedby-user]').contains(`zzzzz-tpzed-000000000000000`);
+            cy.get('[data-cy=process-details-attributes-runtime-user]').contains(`Active User (${activeUser.user.uuid})`);
+        });
+    });
+
     it('filters process logs by event type', function() {
         const nodeInfoLogs = [
             'Host Information',
@@ -168,8 +205,15 @@ describe('Process tests', function() {
             cy.getAll('@stdoutLogs', '@nodeInfoLogs', '@crunchRunLogs').then(function() {
                 cy.loginAs(activeUser);
                 cy.goToPath(`/processes/${containerRequest.uuid}`);
-                // Should should all logs
-                cy.get('[data-cy=process-logs-filter]').should('contain', 'All logs');
+                // Should show main logs by default
+                cy.get('[data-cy=process-logs-filter]', {timeout: 7000}).should('contain', 'Main logs');
+                cy.get('[data-cy=process-logs]')
+                    .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
+                    .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
+                    .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                // Select 'All logs'
+                cy.get('[data-cy=process-logs-filter]').click();
+                cy.get('body').contains('li', 'All logs').click();
                 cy.get('[data-cy=process-logs]')
                     .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
                     .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
@@ -244,5 +288,889 @@ describe('Process tests', function() {
                 .should('contain', 'Free disk space is low')
                 .and('contain', 'No additional warning details available');
         });
+
+
+        // Force container_count for testing
+        let containerCount = 2;
+        cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
+            req.reply((res) => {
+                res.body.container_count = containerCount;
+            });
+        });
+
+        cy.getAll('@containerRequest').then(function([containerRequest]) {
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-runtime-status-retry-warning]', {timeout: 7000})
+                .should('contain', 'Process retried 1 time');
+        });
+
+        cy.getAll('@containerRequest').then(function([containerRequest]) {
+            containerCount = 3;
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-runtime-status-retry-warning]', {timeout: 7000})
+                .should('contain', 'Process retried 2 times');
+        });
+    });
+
+
+    const testInputs = [
+        {
+            definition: {
+                "id": "#main/input_file",
+                "label": "Label Description",
+                "type": "File"
+            },
+            input: {
+                "input_file": {
+                    "basename": "input1.tar",
+                    "class": "File",
+                    "location": "keep:00000000000000000000000000000000+01/input1.tar",
+                    "secondaryFiles": [
+                        {
+                            "basename": "input1-2.txt",
+                            "class": "File",
+                            "location": "keep:00000000000000000000000000000000+01/input1-2.txt"
+                        },
+                        {
+                            "basename": "input1-3.txt",
+                            "class": "File",
+                            "location": "keep:00000000000000000000000000000000+01/input1-3.txt"
+                        },
+                        {
+                            "basename": "input1-4.txt",
+                            "class": "File",
+                            "location": "keep:00000000000000000000000000000000+01/input1-4.txt"
+                        }
+                    ]
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_dir",
+                "doc": "Doc Description",
+                "type": "Directory"
+            },
+            input: {
+                "input_dir": {
+                    "basename": "11111111111111111111111111111111+01",
+                    "class": "Directory",
+                    "location": "keep:11111111111111111111111111111111+01"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_bool",
+                "doc": ["Doc desc 1", "Doc desc 2"],
+                "type": "boolean"
+            },
+            input: {
+                "input_bool": true,
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_int",
+                "type": "int"
+            },
+            input: {
+                "input_int": 1,
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_long",
+                "type": "long"
+            },
+            input: {
+                "input_long" : 1,
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_float",
+                "type": "float"
+            },
+            input: {
+                "input_float": 1.5,
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_double",
+                "type": "double"
+            },
+            input: {
+                "input_double": 1.3,
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_string",
+                "type": "string"
+            },
+            input: {
+                "input_string": "Hello World",
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_file_array",
+                "type": {
+                  "items": "File",
+                  "type": "array"
+                }
+            },
+            input: {
+                "input_file_array": [
+                    {
+                        "basename": "input2.tar",
+                        "class": "File",
+                        "location": "keep:00000000000000000000000000000000+02/input2.tar"
+                    },
+                    {
+                        "basename": "input3.tar",
+                        "class": "File",
+                        "location": "keep:00000000000000000000000000000000+03/input3.tar",
+                        "secondaryFiles": [
+                            {
+                                "basename": "input3-2.txt",
+                                "class": "File",
+                                "location": "keep:00000000000000000000000000000000+03/input3-2.txt"
+                            }
+                        ]
+                    },
+                    {
+                        "$import": "import_path"
+                    }
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_dir_array",
+                "type": {
+                  "items": "Directory",
+                  "type": "array"
+                }
+            },
+            input: {
+                "input_dir_array": [
+                    {
+                        "basename": "11111111111111111111111111111111+02",
+                        "class": "Directory",
+                        "location": "keep:11111111111111111111111111111111+02"
+                    },
+                    {
+                        "basename": "11111111111111111111111111111111+03",
+                        "class": "Directory",
+                        "location": "keep:11111111111111111111111111111111+03"
+                    },
+                    {
+                        "$import": "import_path"
+                    }
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_int_array",
+                "type": {
+                  "items": "int",
+                  "type": "array"
+                }
+            },
+            input: {
+                "input_int_array": [
+                    1,
+                    3,
+                    5,
+                    {
+                        "$import": "import_path"
+                    }
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_long_array",
+                "type": {
+                  "items": "long",
+                  "type": "array"
+                }
+            },
+            input: {
+                "input_long_array": [
+                    10,
+                    20,
+                    {
+                        "$import": "import_path"
+                    }
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_float_array",
+                "type": {
+                  "items": "float",
+                  "type": "array"
+                }
+            },
+            input: {
+                "input_float_array": [
+                    10.2,
+                    10.4,
+                    10.6,
+                    {
+                        "$import": "import_path"
+                    }
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_double_array",
+                "type": {
+                  "items": "double",
+                  "type": "array"
+                }
+            },
+            input: {
+                "input_double_array": [
+                    20.1,
+                    20.2,
+                    20.3,
+                    {
+                        "$import": "import_path"
+                    }
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_string_array",
+                "type": {
+                  "items": "string",
+                  "type": "array"
+                }
+            },
+            input: {
+                "input_string_array": [
+                    "Hello",
+                    "World",
+                    "!",
+                    {
+                        "$import": "import_path"
+                    }
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_bool_include",
+                "type": "boolean"
+            },
+            input: {
+                "input_bool_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_int_include",
+                "type": "int"
+            },
+            input: {
+                "input_int_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_float_include",
+                "type": "float"
+            },
+            input: {
+                "input_float_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_string_include",
+                "type": "string"
+            },
+            input: {
+                "input_string_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_file_include",
+                "type": "File"
+            },
+            input: {
+                "input_file_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_directory_include",
+                "type": "Directory"
+            },
+            input: {
+                "input_directory_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_file_url",
+                "type": "File"
+            },
+            input: {
+                "input_file_url": {
+                    "basename": "index.html",
+                    "class": "File",
+                    "location": "http://example.com/index.html"
+                  }
+            }
+        }
+    ];
+
+    const testOutputs = [
+        {
+            definition: {
+                "id": "#main/output_file",
+                "label": "Label Description",
+                "type": "File"
+            },
+            output: {
+                "output_file": {
+                    "basename": "cat.png",
+                    "class": "File",
+                    "location": "cat.png"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_file_with_secondary",
+                "doc": "Doc Description",
+                "type": "File"
+            },
+            output: {
+                "output_file_with_secondary": {
+                    "basename": "main.dat",
+                    "class": "File",
+                    "location": "main.dat",
+                    "secondaryFiles": [
+                        {
+                            "basename": "secondary.dat",
+                            "class": "File",
+                            "location": "secondary.dat"
+                        },
+                        {
+                            "basename": "secondary2.dat",
+                            "class": "File",
+                            "location": "secondary2.dat"
+                        }
+                    ]
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_dir",
+                "doc": ["Doc desc 1", "Doc desc 2"],
+                "type": "Directory"
+            },
+            output: {
+                "output_dir": {
+                    "basename": "outdir1",
+                    "class": "Directory",
+                    "location": "outdir1"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_bool",
+                "type": "boolean"
+            },
+            output: {
+                "output_bool": true
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_int",
+                "type": "int"
+            },
+            output: {
+                "output_int": 1
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_long",
+                "type": "long"
+            },
+            output: {
+                "output_long": 1
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_float",
+                "type": "float"
+            },
+            output: {
+                "output_float": 100.5
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_double",
+                "type": "double"
+            },
+            output: {
+                "output_double": 100.3
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_string",
+                "type": "string"
+            },
+            output: {
+                "output_string": "Hello output"
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_file_array",
+                "type": {
+                    "items": "File",
+                    "type": "array"
+                }
+            },
+            output: {
+                "output_file_array": [
+                    {
+                        "basename": "output2.tar",
+                        "class": "File",
+                        "location": "output2.tar"
+                    },
+                    {
+                        "basename": "output3.tar",
+                        "class": "File",
+                        "location": "output3.tar"
+                    }
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_dir_array",
+                "type": {
+                    "items": "Directory",
+                    "type": "array"
+                }
+            },
+            output: {
+                "output_dir_array": [
+                    {
+                        "basename": "outdir2",
+                        "class": "Directory",
+                        "location": "outdir2"
+                    },
+                    {
+                        "basename": "outdir3",
+                        "class": "Directory",
+                        "location": "outdir3"
+                    }
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_int_array",
+                "type": {
+                    "items": "int",
+                    "type": "array"
+                }
+            },
+            output: {
+                "output_int_array": [
+                    10,
+                    11,
+                    12
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_long_array",
+                "type": {
+                    "items": "long",
+                    "type": "array"
+                }
+            },
+            output: {
+                "output_long_array": [
+                    51,
+                    52
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_float_array",
+                "type": {
+                    "items": "float",
+                    "type": "array"
+                }
+            },
+            output: {
+                "output_float_array": [
+                    100.2,
+                    100.4,
+                    100.6
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_double_array",
+                "type": {
+                    "items": "double",
+                    "type": "array"
+                }
+            },
+            output: {
+                "output_double_array": [
+                    100.1,
+                    100.2,
+                    100.3
+                ]
+            }
+        },
+        {
+            definition: {
+                "id": "#main/output_string_array",
+                "type": {
+                    "items": "string",
+                    "type": "array"
+                }
+            },
+            output: {
+                "output_string_array": [
+                    "Hello",
+                    "Output",
+                    "!"
+                ]
+            }
+        }
+    ];
+
+    const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
+        cy.get('table tr').contains(name).parents('tr').within(($mainRow) => {
+            label && cy.contains(label);
+
+            if (multipleRows) {
+                cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as('secondaryRows');
+                if (val) {
+                    if (Array.isArray(val)) {
+                        val.forEach(v => cy.get('@secondaryRows').contains(v));
+                    } else {
+                        cy.get('@secondaryRows').contains(val);
+                    }
+                }
+                if (collection) {
+                    cy.get('@secondaryRows').contains(collection);
+                }
+            } else {
+                if (val) {
+                    if (Array.isArray(val)) {
+                        val.forEach(v => cy.contains(v));
+                    } else {
+                        cy.contains(val);
+                    }
+                }
+                if (collection) {
+                    cy.contains(collection);
+                }
+            }
+
+
+        });
+    };
+
+    const verifyIOParameterImage = (name, url) => {
+        cy.get('table tr').contains(name).parents('tr').within(() => {
+            cy.get('[alt="Inline Preview"]')
+                .should('be.visible')
+                .and(($img) => {
+                    expect($img[0].naturalWidth).to.be.greaterThan(0);
+                    expect($img[0].src).contains(url);
+                })
+        });
+    };
+
+    it('displays IO parameters with keep links and previews', function() {
+        // Create output collection for real files
+        cy.createCollection(adminUser.token, {
+            name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+            owner_uuid: activeUser.user.uuid,
+        }).then((testOutputCollection) => {
+                    cy.loginAs(activeUser);
+
+                    cy.goToPath(`/collections/${testOutputCollection.uuid}`);
+
+                    cy.get('[data-cy=upload-button]').click();
+
+                    cy.fixture('files/cat.png', 'base64').then(content => {
+                        cy.get('[data-cy=drag-and-drop]').upload(content, 'cat.png');
+                        cy.get('[data-cy=form-submit-btn]').click();
+                        cy.waitForDom().get('[data-cy=form-submit-btn]').should('not.exist');
+                        // Confirm final collection state.
+                        cy.get('[data-cy=collection-files-panel]')
+                            .contains('cat.png').should('exist');
+                    });
+
+                    cy.getCollection(activeUser.token, testOutputCollection.uuid).as('testOutputCollection');
+                });
+
+        // Get updated collection pdh
+        cy.getAll('@testOutputCollection').then(([testOutputCollection]) => {
+            // Add output uuid and inputs to container request
+            cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
+                req.reply((res) => {
+                    res.body.output_uuid = testOutputCollection.uuid;
+                    res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
+                        content: testInputs.map((param) => (param.input)).reduce((acc, val) => (Object.assign(acc, val)), {})
+                    };
+                    res.body.mounts["/var/lib/cwl/workflow.json"] = {
+                        content: {
+                            $graph: [{
+                                id: "#main",
+                                inputs: testInputs.map((input) => (input.definition)),
+                                outputs: testOutputs.map((output) => (output.definition))
+                            }]
+                        }
+                    };
+                });
+            });
+
+            // Stub fake output collection
+            cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${testOutputCollection.uuid}*`}, {
+                statusCode: 200,
+                body: {
+                    uuid: testOutputCollection.uuid,
+                    portable_data_hash: testOutputCollection.portable_data_hash,
+                }
+            });
+
+            // Stub fake output json
+            cy.intercept({method: 'GET', url: '**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json'}, {
+                statusCode: 200,
+                body: testOutputs.map((param) => (param.output)).reduce((acc, val) => (Object.assign(acc, val)), {})
+            });
+
+            // Stub webdav response, points to output json
+            cy.intercept({method: 'PROPFIND', url: '*'}, {
+                fixture: 'webdav-propfind-outputs.xml',
+            });
+        });
+
+        createContainerRequest(
+            activeUser,
+            'test_container_request',
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .as('containerRequest');
+
+        cy.getAll('@containerRequest', '@testOutputCollection').then(function([containerRequest, testOutputCollection]) {
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-io-card] h6').contains('Inputs')
+                .parents('[data-cy=process-io-card]').within(() => {
+                    verifyIOParameter('input_file', null, "Label Description", 'input1.tar', '00000000000000000000000000000000+01');
+                    verifyIOParameter('input_file', null, "Label Description", 'input1-2.txt', undefined, true);
+                    verifyIOParameter('input_file', null, "Label Description", 'input1-3.txt', undefined, true);
+                    verifyIOParameter('input_file', null, "Label Description", 'input1-4.txt', undefined, true);
+                    verifyIOParameter('input_dir', null, "Doc Description", '/', '11111111111111111111111111111111+01');
+                    verifyIOParameter('input_bool', null, "Doc desc 1, Doc desc 2", 'true');
+                    verifyIOParameter('input_int', null, null, '1');
+                    verifyIOParameter('input_long', null, null, '1');
+                    verifyIOParameter('input_float', null, null, '1.5');
+                    verifyIOParameter('input_double', null, null, '1.3');
+                    verifyIOParameter('input_string', null, null, 'Hello World');
+                    verifyIOParameter('input_file_array', null, null, 'input2.tar', '00000000000000000000000000000000+02');
+                    verifyIOParameter('input_file_array', null, null, 'input3.tar', undefined, true);
+                    verifyIOParameter('input_file_array', null, null, 'input3-2.txt', undefined, true);
+                    verifyIOParameter('input_file_array', null, null, 'Cannot display value', undefined, true);
+                    verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+02');
+                    verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+03', true);
+                    verifyIOParameter('input_dir_array', null, null, 'Cannot display value', undefined, true);
+                    verifyIOParameter('input_int_array', null, null, ["1", "3", "5", "Cannot display value"]);
+                    verifyIOParameter('input_long_array', null, null, ["10", "20", "Cannot display value"]);
+                    verifyIOParameter('input_float_array', null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
+                    verifyIOParameter('input_double_array', null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
+                    verifyIOParameter('input_string_array', null, null, ["Hello", "World", "!", "Cannot display value"]);
+                    verifyIOParameter('input_bool_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_int_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_float_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_string_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_file_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_directory_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_file_url', null, null, "http://example.com/index.html");
+                });
+            cy.get('[data-cy=process-io-card] h6').contains('Outputs')
+                .parents('[data-cy=process-io-card]').within((ctx) => {
+                    cy.get(ctx).scrollIntoView();
+                    cy.get('[data-cy="io-preview-image-toggle"]').click({waitForAnimations: false});
+                    const outPdh = testOutputCollection.portable_data_hash;
+
+                    verifyIOParameter('output_file', null, "Label Description", 'cat.png', `${outPdh}`);
+                    verifyIOParameterImage('output_file', `/c=${outPdh}/cat.png`);
+                    verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'main.dat', `${outPdh}`);
+                    verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary.dat', undefined, true);
+                    verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary2.dat', undefined, true);
+                    verifyIOParameter('output_dir', null, "Doc desc 1, Doc desc 2", 'outdir1', `${outPdh}`);
+                    verifyIOParameter('output_bool', null, null, 'true');
+                    verifyIOParameter('output_int', null, null, '1');
+                    verifyIOParameter('output_long', null, null, '1');
+                    verifyIOParameter('output_float', null, null, '100.5');
+                    verifyIOParameter('output_double', null, null, '100.3');
+                    verifyIOParameter('output_string', null, null, 'Hello output');
+                    verifyIOParameter('output_file_array', null, null, 'output2.tar', `${outPdh}`);
+                    verifyIOParameter('output_file_array', null, null, 'output3.tar', undefined, true);
+                    verifyIOParameter('output_dir_array', null, null, 'outdir2', `${outPdh}`);
+                    verifyIOParameter('output_dir_array', null, null, 'outdir3', undefined, true);
+                    verifyIOParameter('output_int_array', null, null, ["10", "11", "12"]);
+                    verifyIOParameter('output_long_array', null, null, ["51", "52"]);
+                    verifyIOParameter('output_float_array', null, null, ["100.2", "100.4", "100.6"]);
+                    verifyIOParameter('output_double_array', null, null, ["100.1", "100.2", "100.3"]);
+                    verifyIOParameter('output_string_array', null, null, ["Hello", "Output", "!"]);
+                });
+        });
     });
+
+    it('displays IO parameters with no value', function() {
+
+        const fakeOutputUUID = 'zzzzz-4zz18-abcdefghijklmno';
+        const fakeOutputPDH = '11111111111111111111111111111111+99/';
+
+        cy.loginAs(activeUser);
+
+        // Add output uuid and inputs to container request
+        cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
+            req.reply((res) => {
+                res.body.output_uuid = fakeOutputUUID;
+                res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
+                    content: {}
+                };
+                res.body.mounts["/var/lib/cwl/workflow.json"] = {
+                    content: {
+                        $graph: [{
+                            id: "#main",
+                            inputs: testInputs.map((input) => (input.definition)),
+                            outputs: testOutputs.map((output) => (output.definition))
+                        }]
+                    }
+                };
+            });
+        });
+
+        // Stub fake output collection
+        cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${fakeOutputUUID}*`}, {
+            statusCode: 200,
+            body: {
+                uuid: fakeOutputUUID,
+                portable_data_hash: fakeOutputPDH,
+            }
+        });
+
+        // Stub fake output json
+        cy.intercept({method: 'GET', url: `**/c%3D${fakeOutputUUID}/cwl.output.json`}, {
+            statusCode: 200,
+            body: {}
+        });
+
+        cy.readFile('cypress/fixtures/webdav-propfind-outputs.xml').then((data) => {
+            // Stub webdav response, points to output json
+            cy.intercept({method: 'PROPFIND', url: '*'}, {
+                statusCode: 200,
+                body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID)
+            });
+        });
+
+        createContainerRequest(
+            activeUser,
+            'test_container_request',
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .as('containerRequest');
+
+        cy.getAll('@containerRequest').then(function([containerRequest]) {
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-io-card] h6').contains('Inputs')
+                .parents('[data-cy=process-io-card]').within(() => {
+                    cy.wait(2000);
+                    cy.waitForDom();
+                    cy.get('tbody tr').each((item) => {
+                        cy.wrap(item).contains('No value');
+                    });
+                });
+            cy.get('[data-cy=process-io-card] h6').contains('Outputs')
+                .parents('[data-cy=process-io-card]').within(() => {
+                    cy.get('tbody tr').each((item) => {
+                        cy.wrap(item).contains('No value');
+                    });
+                });
+        });
+    });
+
+
+    it('allows copying processes', function() {
+        const crName = 'first_container_request';
+        const copiedCrName = 'copied_container_request';
+        createContainerRequest(
+            activeUser,
+            crName,
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .then(function(containerRequest) {
+            cy.loginAs(activeUser);
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-details]').should('contain', crName);
+
+            cy.get('[data-cy=process-details]').find('button[title="More options"]').click();
+            cy.get('ul[data-cy=context-menu]').contains("Copy and re-run process").click();
+        });
+
+        cy.get('[data-cy=form-dialog]').within(() => {
+            cy.get('input[name=name]').clear().type(copiedCrName);
+            cy.get('[data-cy=projects-tree-home-tree-picker]').click();
+            cy.get('[data-cy=form-submit-btn]').click();
+        });
+
+        cy.get('[data-cy=process-details]').should('contain', copiedCrName);
+        cy.get('[data-cy=process-details]').find('button').contains('Run Process');
+    });
+
 });