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)
17 adminUser = this.adminUser;
19 cy.getUser("user", "Active", "User", false, true)
22 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, {
36 ". d21353cfe035e3e384563ee55eadbb2f+67108864 5c77a43e329b9838cbec18ff42790e57+55605760 0:122714624:sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n",
39 .then(function (dockerImage) {
40 // Give read permissions to the active user on the docker image.
41 cy.createLink(adminUser.token, {
42 link_class: "permission",
44 tail_uuid: activeUser.user.uuid,
45 head_uuid: dockerImage.uuid,
47 .as("dockerImagePermission")
49 // Set-up docker image collection tags
50 cy.createLink(activeUser.token, {
51 link_class: "docker_image_repo+tag",
53 head_uuid: dockerImage.uuid,
54 }).as("dockerImageRepoTag");
55 cy.createLink(activeUser.token, {
56 link_class: "docker_image_hash",
57 name: "sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678",
58 head_uuid: dockerImage.uuid,
59 }).as("dockerImageHash");
62 return cy.getAll("@dockerImage", "@dockerImageRepoTag", "@dockerImageHash", "@dockerImagePermission").then(function ([dockerImage]) {
67 function createContainerRequest(user, name, docker_image, command, reuse = false, state = "Uncommitted") {
68 return setupDockerImage(docker_image).then(function (dockerImage) {
69 return cy.createContainerRequest(user.token, {
72 container_image: dockerImage.portable_data_hash, // for some reason, docker_image doesn't work here
73 output_path: "stdout.txt",
75 runtime_constraints: {
91 it("shows process logs", function () {
92 const crName = "test_container_request";
93 createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
94 cy.loginAs(activeUser);
95 cy.goToPath(`/processes/${containerRequest.uuid}`);
96 cy.get("[data-cy=process-details]").should("contain", crName);
97 cy.get("[data-cy=process-logs]").should("contain", "No logs yet").and("not.contain", "hello world");
98 cy.createLog(activeUser.token, {
99 object_uuid: containerRequest.container_uuid,
103 event_type: "stdout",
104 }).then(function (log) {
105 cy.get("[data-cy=process-logs]", { timeout: 7000 }).should("not.contain", "No logs yet").and("contain", "hello world");
110 it("shows process details", function () {
111 createContainerRequest(
113 `test_container_request ${Math.floor(Math.random() * 999999)}`,
115 ["echo", "hello world"],
118 ).then(function (containerRequest) {
119 cy.loginAs(activeUser);
120 cy.goToPath(`/processes/${containerRequest.uuid}`);
121 cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
122 cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`Active User (${activeUser.user.uuid})`);
123 cy.get("[data-cy=process-details-attributes-runtime-user]").should("not.exist");
126 // Fake submitted by another user
127 cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
129 res.body.modified_by_user_uuid = "zzzzz-tpzed-000000000000000";
133 createContainerRequest(
135 `test_container_request ${Math.floor(Math.random() * 999999)}`,
137 ["echo", "hello world"],
140 ).then(function (containerRequest) {
141 cy.loginAs(activeUser);
142 cy.goToPath(`/processes/${containerRequest.uuid}`);
143 cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
144 cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`zzzzz-tpzed-000000000000000`);
145 cy.get("[data-cy=process-details-attributes-runtime-user]").contains(`Active User (${activeUser.user.uuid})`);
149 it("filters process logs by event type", function () {
150 const nodeInfoLogs = [
152 "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",
155 "vendor_id : GenuineIntel",
158 "model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz",
160 const crunchRunLogs = [
161 "2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection",
162 "2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started",
163 "2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)",
164 "2022-03-22T13:56:26.244862836Z Executing container 'zzzzz-dz642-1wokwvcct9s9du3' using docker runtime",
165 "2022-03-22T13:56:26.245037738Z Executing on host 'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p'",
168 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.",
169 "Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.",
170 "In hac habitasse platea dictumst.",
171 "Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.",
172 "Interdum et malesuada fames ac ante ipsum primis in faucibus.",
173 "Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.",
174 "Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.",
175 "Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.",
176 "Donec vitae leo id augue gravida bibendum.",
177 "Nam libero libero, pretium ac faucibus elementum, mattis nec ex.",
178 "Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.",
179 "Aliquam viverra nisi nulla, et efficitur dolor mattis in.",
180 "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.",
181 "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.",
182 "Phasellus non ex quis arcu tempus faucibus molestie in sapien.",
183 "Duis tristique semper dolor, vitae pulvinar risus.",
184 "Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.",
185 "Nulla eget mollis ipsum.",
188 createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
191 cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "node-info", nodeInfoLogs).as("nodeInfoLogs");
192 cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "crunch-run", crunchRunLogs).as("crunchRunLogs");
193 cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "stdout", stdoutLogs).as("stdoutLogs");
194 cy.getAll("@stdoutLogs", "@nodeInfoLogs", "@crunchRunLogs").then(function () {
195 cy.loginAs(activeUser);
196 cy.goToPath(`/processes/${containerRequest.uuid}`);
197 // Should show main logs by default
198 cy.get("[data-cy=process-logs-filter]", { timeout: 7000 }).should("contain", "Main logs");
199 cy.get("[data-cy=process-logs]")
200 .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
201 .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
202 .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
204 cy.get("[data-cy=process-logs-filter]").click();
205 cy.get("body").contains("li", "All logs").click();
206 cy.get("[data-cy=process-logs]")
207 .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
208 .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
209 .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
210 // Select 'node-info' logs
211 cy.get("[data-cy=process-logs-filter]").click();
212 cy.get("body").contains("li", "node-info").click();
213 cy.get("[data-cy=process-logs]")
214 .should("not.contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
215 .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
216 .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
217 // Select 'stdout' logs
218 cy.get("[data-cy=process-logs-filter]").click();
219 cy.get("body").contains("li", "stdout").click();
220 cy.get("[data-cy=process-logs]")
221 .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
222 .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
223 .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
228 it("should show runtime status indicators", function () {
229 // Setup running container with runtime_status error & warning messages
230 createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed")
231 .as("containerRequest")
232 .then(function (containerRequest) {
233 expect(containerRequest.state).to.equal("Committed");
234 expect(containerRequest.container_uuid).not.to.be.equal("");
236 cy.getContainer(activeUser.token, containerRequest.container_uuid).then(function (queuedContainer) {
237 expect(queuedContainer.state).to.be.equal("Queued");
239 cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
241 }).then(function (lockedContainer) {
242 expect(lockedContainer.state).to.be.equal("Locked");
244 cy.updateContainer(adminUser.token, lockedContainer.uuid, {
247 error: "Something went wrong",
248 errorDetail: "Process exited with status 1",
249 warning: "Free disk space is low",
252 .as("runningContainer")
253 .then(function (runningContainer) {
254 expect(runningContainer.state).to.be.equal("Running");
255 expect(runningContainer.runtime_status).to.be.deep.equal({
256 error: "Something went wrong",
257 errorDetail: "Process exited with status 1",
258 warning: "Free disk space is low",
263 // Test that the UI shows the error and warning messages
264 cy.getAll("@containerRequest", "@runningContainer").then(function ([containerRequest]) {
265 cy.loginAs(activeUser);
266 cy.goToPath(`/processes/${containerRequest.uuid}`);
267 cy.get("[data-cy=process-runtime-status-error]").should("contain", "Something went wrong").and("contain", "Process exited with status 1");
268 cy.get("[data-cy=process-runtime-status-warning]")
269 .should("contain", "Free disk space is low")
270 .and("contain", "No additional warning details available");
273 // Force container_count for testing
274 let containerCount = 2;
275 cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
277 res.body.container_count = containerCount;
281 cy.getAll("@containerRequest").then(function ([containerRequest]) {
282 cy.goToPath(`/processes/${containerRequest.uuid}`);
283 cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 1 time");
286 cy.getAll("@containerRequest").then(function ([containerRequest]) {
288 cy.goToPath(`/processes/${containerRequest.uuid}`);
289 cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 2 times");
296 id: "#main/input_file",
297 label: "Label Description",
302 basename: "input1.tar",
304 location: "keep:00000000000000000000000000000000+01/input1.tar",
307 basename: "input1-2.txt",
309 location: "keep:00000000000000000000000000000000+01/input1-2.txt",
312 basename: "input1-3.txt",
314 location: "keep:00000000000000000000000000000000+01/input1-3.txt",
317 basename: "input1-4.txt",
319 location: "keep:00000000000000000000000000000000+01/input1-4.txt",
327 id: "#main/input_dir",
328 doc: "Doc Description",
333 basename: "11111111111111111111111111111111+01",
335 location: "keep:11111111111111111111111111111111+01",
341 id: "#main/input_bool",
342 doc: ["Doc desc 1", "Doc desc 2"],
351 id: "#main/input_int",
360 id: "#main/input_long",
369 id: "#main/input_float",
378 id: "#main/input_double",
387 id: "#main/input_string",
391 input_string: "Hello World",
396 id: "#main/input_file_array",
405 basename: "input2.tar",
407 location: "keep:00000000000000000000000000000000+02/input2.tar",
410 basename: "input3.tar",
412 location: "keep:00000000000000000000000000000000+03/input3.tar",
415 basename: "input3-2.txt",
417 location: "keep:00000000000000000000000000000000+03/input3-2.txt",
422 $import: "import_path",
429 id: "#main/input_dir_array",
438 basename: "11111111111111111111111111111111+02",
440 location: "keep:11111111111111111111111111111111+02",
443 basename: "11111111111111111111111111111111+03",
445 location: "keep:11111111111111111111111111111111+03",
448 $import: "import_path",
455 id: "#main/input_int_array",
467 $import: "import_path",
474 id: "#main/input_long_array",
485 $import: "import_path",
492 id: "#main/input_float_array",
504 $import: "import_path",
511 id: "#main/input_double_array",
518 input_double_array: [
523 $import: "import_path",
530 id: "#main/input_string_array",
537 input_string_array: [
542 $import: "import_path",
549 id: "#main/input_bool_include",
553 input_bool_include: {
554 $include: "include_path",
560 id: "#main/input_int_include",
565 $include: "include_path",
571 id: "#main/input_float_include",
575 input_float_include: {
576 $include: "include_path",
582 id: "#main/input_string_include",
586 input_string_include: {
587 $include: "include_path",
593 id: "#main/input_file_include",
597 input_file_include: {
598 $include: "include_path",
604 id: "#main/input_directory_include",
608 input_directory_include: {
609 $include: "include_path",
615 id: "#main/input_file_url",
620 basename: "index.html",
622 location: "http://example.com/index.html",
628 const testOutputs = [
631 id: "#main/output_file",
632 label: "Label Description",
645 id: "#main/output_file_with_secondary",
646 doc: "Doc Description",
650 output_file_with_secondary: {
651 basename: "main.dat",
653 location: "main.dat",
656 basename: "secondary.dat",
658 location: "secondary.dat",
661 basename: "secondary2.dat",
663 location: "secondary2.dat",
671 id: "#main/output_dir",
672 doc: ["Doc desc 1", "Doc desc 2"],
685 id: "#main/output_bool",
694 id: "#main/output_int",
703 id: "#main/output_long",
712 id: "#main/output_float",
721 id: "#main/output_double",
725 output_double: 100.3,
730 id: "#main/output_string",
734 output_string: "Hello output",
739 id: "#main/output_file_array",
748 basename: "output2.tar",
750 location: "output2.tar",
753 basename: "output3.tar",
755 location: "output3.tar",
762 id: "#main/output_dir_array",
785 id: "#main/output_int_array",
792 output_int_array: [10, 11, 12],
797 id: "#main/output_long_array",
804 output_long_array: [51, 52],
809 id: "#main/output_float_array",
816 output_float_array: [100.2, 100.4, 100.6],
821 id: "#main/output_double_array",
828 output_double_array: [100.1, 100.2, 100.3],
833 id: "#main/output_string_array",
840 output_string_array: ["Hello", "Output", "!"],
845 const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
849 .within($mainRow => {
850 label && cy.contains(label);
853 cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as("secondaryRows");
855 if (Array.isArray(val)) {
856 val.forEach(v => cy.get("@secondaryRows").contains(v));
858 cy.get("@secondaryRows").contains(val);
862 cy.get("@secondaryRows").contains(collection);
866 if (Array.isArray(val)) {
867 val.forEach(v => cy.contains(v));
873 cy.contains(collection);
879 const verifyIOParameterImage = (name, url) => {
884 cy.get('[alt="Inline Preview"]')
885 .should("be.visible")
887 expect($img[0].naturalWidth).to.be.greaterThan(0);
888 expect($img[0].src).contains(url);
893 it("displays IO parameters with keep links and previews", function () {
894 // Create output collection for real files
895 cy.createCollection(adminUser.token, {
896 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
897 owner_uuid: activeUser.user.uuid,
898 }).then(testOutputCollection => {
899 cy.loginAs(activeUser);
901 cy.goToPath(`/collections/${testOutputCollection.uuid}`);
903 cy.get("[data-cy=upload-button]").click();
905 cy.fixture("files/cat.png", "base64").then(content => {
906 cy.get("[data-cy=drag-and-drop]").upload(content, "cat.png");
907 cy.get("[data-cy=form-submit-btn]").click();
908 cy.waitForDom().get("[data-cy=form-submit-btn]").should("not.exist");
909 // Confirm final collection state.
910 cy.get("[data-cy=collection-files-panel]").contains("cat.png").should("exist");
913 cy.getCollection(activeUser.token, testOutputCollection.uuid).as("testOutputCollection");
916 // Get updated collection pdh
917 cy.getAll("@testOutputCollection").then(([testOutputCollection]) => {
918 // Add output uuid and inputs to container request
919 cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
921 res.body.output_uuid = testOutputCollection.uuid;
922 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
923 content: testInputs.map(param => param.input).reduce((acc, val) => Object.assign(acc, val), {}),
925 res.body.mounts["/var/lib/cwl/workflow.json"] = {
930 inputs: testInputs.map(input => input.definition),
931 outputs: testOutputs.map(output => output.definition),
939 // Stub fake output collection
941 { method: "GET", url: `**/arvados/v1/collections/${testOutputCollection.uuid}*` },
945 uuid: testOutputCollection.uuid,
946 portable_data_hash: testOutputCollection.portable_data_hash,
951 // Stub fake output json
953 { method: "GET", url: "**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json" },
956 body: testOutputs.map(param => param.output).reduce((acc, val) => Object.assign(acc, val), {}),
960 // Stub webdav response, points to output json
962 { method: "PROPFIND", url: "*" },
964 fixture: "webdav-propfind-outputs.xml",
969 createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
973 cy.getAll("@containerRequest", "@testOutputCollection").then(function ([containerRequest, testOutputCollection]) {
974 cy.goToPath(`/processes/${containerRequest.uuid}`);
975 cy.get("[data-cy=process-io-card] h6")
977 .parents("[data-cy=process-io-card]")
979 verifyIOParameter("input_file", null, "Label Description", "input1.tar", "00000000000000000000000000000000+01");
980 verifyIOParameter("input_file", null, "Label Description", "input1-2.txt", undefined, true);
981 verifyIOParameter("input_file", null, "Label Description", "input1-3.txt", undefined, true);
982 verifyIOParameter("input_file", null, "Label Description", "input1-4.txt", undefined, true);
983 verifyIOParameter("input_dir", null, "Doc Description", "/", "11111111111111111111111111111111+01");
984 verifyIOParameter("input_bool", null, "Doc desc 1, Doc desc 2", "true");
985 verifyIOParameter("input_int", null, null, "1");
986 verifyIOParameter("input_long", null, null, "1");
987 verifyIOParameter("input_float", null, null, "1.5");
988 verifyIOParameter("input_double", null, null, "1.3");
989 verifyIOParameter("input_string", null, null, "Hello World");
990 verifyIOParameter("input_file_array", null, null, "input2.tar", "00000000000000000000000000000000+02");
991 verifyIOParameter("input_file_array", null, null, "input3.tar", undefined, true);
992 verifyIOParameter("input_file_array", null, null, "input3-2.txt", undefined, true);
993 verifyIOParameter("input_file_array", null, null, "Cannot display value", undefined, true);
994 verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+02");
995 verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+03", true);
996 verifyIOParameter("input_dir_array", null, null, "Cannot display value", undefined, true);
997 verifyIOParameter("input_int_array", null, null, ["1", "3", "5", "Cannot display value"]);
998 verifyIOParameter("input_long_array", null, null, ["10", "20", "Cannot display value"]);
999 verifyIOParameter("input_float_array", null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
1000 verifyIOParameter("input_double_array", null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
1001 verifyIOParameter("input_string_array", null, null, ["Hello", "World", "!", "Cannot display value"]);
1002 verifyIOParameter("input_bool_include", null, null, "Cannot display value");
1003 verifyIOParameter("input_int_include", null, null, "Cannot display value");
1004 verifyIOParameter("input_float_include", null, null, "Cannot display value");
1005 verifyIOParameter("input_string_include", null, null, "Cannot display value");
1006 verifyIOParameter("input_file_include", null, null, "Cannot display value");
1007 verifyIOParameter("input_directory_include", null, null, "Cannot display value");
1008 verifyIOParameter("input_file_url", null, null, "http://example.com/index.html");
1010 cy.get("[data-cy=process-io-card] h6")
1011 .contains("Outputs")
1012 .parents("[data-cy=process-io-card]")
1014 cy.get(ctx).scrollIntoView();
1015 cy.get('[data-cy="io-preview-image-toggle"]').click({ waitForAnimations: false });
1016 const outPdh = testOutputCollection.portable_data_hash;
1018 verifyIOParameter("output_file", null, "Label Description", "cat.png", `${outPdh}`);
1019 verifyIOParameterImage("output_file", `/c=${outPdh}/cat.png`);
1020 verifyIOParameter("output_file_with_secondary", null, "Doc Description", "main.dat", `${outPdh}`);
1021 verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary.dat", undefined, true);
1022 verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary2.dat", undefined, true);
1023 verifyIOParameter("output_dir", null, "Doc desc 1, Doc desc 2", "outdir1", `${outPdh}`);
1024 verifyIOParameter("output_bool", null, null, "true");
1025 verifyIOParameter("output_int", null, null, "1");
1026 verifyIOParameter("output_long", null, null, "1");
1027 verifyIOParameter("output_float", null, null, "100.5");
1028 verifyIOParameter("output_double", null, null, "100.3");
1029 verifyIOParameter("output_string", null, null, "Hello output");
1030 verifyIOParameter("output_file_array", null, null, "output2.tar", `${outPdh}`);
1031 verifyIOParameter("output_file_array", null, null, "output3.tar", undefined, true);
1032 verifyIOParameter("output_dir_array", null, null, "outdir2", `${outPdh}`);
1033 verifyIOParameter("output_dir_array", null, null, "outdir3", undefined, true);
1034 verifyIOParameter("output_int_array", null, null, ["10", "11", "12"]);
1035 verifyIOParameter("output_long_array", null, null, ["51", "52"]);
1036 verifyIOParameter("output_float_array", null, null, ["100.2", "100.4", "100.6"]);
1037 verifyIOParameter("output_double_array", null, null, ["100.1", "100.2", "100.3"]);
1038 verifyIOParameter("output_string_array", null, null, ["Hello", "Output", "!"]);
1043 it("displays IO parameters with no value", function () {
1044 const fakeOutputUUID = "zzzzz-4zz18-abcdefghijklmno";
1045 const fakeOutputPDH = "11111111111111111111111111111111+99/";
1047 cy.loginAs(activeUser);
1049 // Add output uuid and inputs to container request
1050 cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
1052 res.body.output_uuid = fakeOutputUUID;
1053 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
1056 res.body.mounts["/var/lib/cwl/workflow.json"] = {
1061 inputs: testInputs.map(input => input.definition),
1062 outputs: testOutputs.map(output => output.definition),
1070 // Stub fake output collection
1072 { method: "GET", url: `**/arvados/v1/collections/${fakeOutputUUID}*` },
1076 uuid: fakeOutputUUID,
1077 portable_data_hash: fakeOutputPDH,
1082 // Stub fake output json
1084 { method: "GET", url: `**/c%3D${fakeOutputUUID}/cwl.output.json` },
1091 cy.readFile("cypress/fixtures/webdav-propfind-outputs.xml").then(data => {
1092 // Stub webdav response, points to output json
1094 { method: "PROPFIND", url: "*" },
1097 body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID),
1102 createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
1106 cy.getAll("@containerRequest").then(function ([containerRequest]) {
1107 cy.goToPath(`/processes/${containerRequest.uuid}`);
1108 cy.get("[data-cy=process-io-card] h6")
1110 .parents("[data-cy=process-io-card]")
1114 cy.get("tbody tr").each(item => {
1115 cy.wrap(item).contains("No value");
1118 cy.get("[data-cy=process-io-card] h6")
1119 .contains("Outputs")
1120 .parents("[data-cy=process-io-card]")
1122 cy.get("tbody tr").each(item => {
1123 cy.wrap(item).contains("No value");
1129 it("allows copying processes", function () {
1130 const crName = "first_container_request";
1131 const copiedCrName = "copied_container_request";
1132 createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
1133 cy.loginAs(activeUser);
1134 cy.goToPath(`/processes/${containerRequest.uuid}`);
1135 cy.get("[data-cy=process-details]").should("contain", crName);
1137 cy.get("[data-cy=process-details]").find('button[title="More options"]').click();
1138 cy.get("ul[data-cy=context-menu]").contains("Copy and re-run process").click();
1141 cy.get("[data-cy=form-dialog]").within(() => {
1142 cy.get("input[name=name]").clear().type(copiedCrName);
1143 cy.get("[data-cy=projects-tree-home-tree-picker]").click();
1144 cy.get("[data-cy=form-submit-btn]").click();
1147 cy.get("[data-cy=process-details]").should("contain", copiedCrName);
1148 cy.get("[data-cy=process-details]").find("button").contains("Run");
1151 const getFakeContainer = fakeContainerUuid => ({
1152 href: `/containers/${fakeContainerUuid}`,
1153 kind: "arvados#container",
1154 etag: "ecfosljpnxfari9a8m7e4yv06",
1155 uuid: fakeContainerUuid,
1156 owner_uuid: "zzzzz-tpzed-000000000000000",
1157 created_at: "2023-02-13T15:55:47.308915000Z",
1158 modified_by_client_uuid: "zzzzz-ozdt8-q6dzdi1lcc03155",
1159 modified_by_user_uuid: "zzzzz-tpzed-000000000000000",
1160 modified_at: "2023-02-15T19:12:45.987086000Z",
1162 "arvados-cwl-runner",
1165 "--project-uuid=zzzzz-j7d0g-yr18k784zplfeza",
1166 "/var/lib/cwl/workflow.json#main",
1167 "/var/lib/cwl/cwl.input.json",
1169 container_image: "4ad7d11381df349e464694762db14e04+303",
1170 cwd: "/var/spool/cwl",
1174 locked_by_uuid: null,
1177 output_path: "/var/spool/cwl",
1179 runtime_constraints: {
1184 hardware_capability: "",
1186 keep_cache_disk: 2147483648,
1194 scheduling_parameters: {
1199 runtime_user_uuid: "zzzzz-tpzed-vllbpebicy84rd5",
1200 runtime_auth_scopes: ["all"],
1202 gateway_address: null,
1203 interactive_session_started: false,
1204 output_storage_classes: ["default"],
1205 output_properties: {},
1207 subrequests_cost: 0.0,
1210 it("shows cancel button when appropriate", function () {
1211 // Ignore collection requests
1213 { method: "GET", url: `**/arvados/v1/collections/*` },
1220 // Uncommitted container
1221 const crUncommitted = `Test process ${Math.floor(Math.random() * 999999)}`;
1222 createContainerRequest(activeUser, crUncommitted, "arvados/jobs", ["echo", "hello world"], false, "Uncommitted").then(function (
1225 // Navigate to process and verify run / cancel button
1226 cy.goToPath(`/processes/${containerRequest.uuid}`);
1228 cy.get("[data-cy=process-details]").should("contain", crUncommitted);
1229 cy.get("[data-cy=process-run-button]").should("exist");
1230 cy.get("[data-cy=process-cancel-button]").should("not.exist");
1234 const crQueued = `Test process ${Math.floor(Math.random() * 999999)}`;
1235 const fakeCrUuid = "zzzzz-dz642-000000000000001";
1236 createContainerRequest(activeUser, crQueued, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
1237 // Fake container uuid
1238 cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
1240 res.body.output_uuid = fakeCrUuid;
1241 res.body.priority = 500;
1242 res.body.state = "Committed";
1247 const container = getFakeContainer(fakeCrUuid);
1249 { method: "GET", url: `**/arvados/v1/container/${fakeCrUuid}` },
1252 body: { ...container, state: "Queued", priority: 500 },
1256 // Navigate to process and verify cancel button
1257 cy.goToPath(`/processes/${containerRequest.uuid}`);
1259 cy.get("[data-cy=process-details]").should("contain", crQueued);
1260 cy.get("[data-cy=process-cancel-button]").contains("Cancel");
1264 const crLocked = `Test process ${Math.floor(Math.random() * 999999)}`;
1265 const fakeCrLockedUuid = "zzzzz-dz642-000000000000002";
1266 createContainerRequest(activeUser, crLocked, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
1267 // Fake container uuid
1268 cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
1270 res.body.output_uuid = fakeCrLockedUuid;
1271 res.body.priority = 500;
1272 res.body.state = "Committed";
1277 const container = getFakeContainer(fakeCrLockedUuid);
1279 { method: "GET", url: `**/arvados/v1/container/${fakeCrLockedUuid}` },
1282 body: { ...container, state: "Locked", priority: 500 },
1286 // Navigate to process and verify cancel button
1287 cy.goToPath(`/processes/${containerRequest.uuid}`);
1289 cy.get("[data-cy=process-details]").should("contain", crLocked);
1290 cy.get("[data-cy=process-cancel-button]").contains("Cancel");
1293 // On Hold container
1294 const crOnHold = `Test process ${Math.floor(Math.random() * 999999)}`;
1295 const fakeCrOnHoldUuid = "zzzzz-dz642-000000000000003";
1296 createContainerRequest(activeUser, crOnHold, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
1297 // Fake container uuid
1298 cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
1300 res.body.output_uuid = fakeCrOnHoldUuid;
1301 res.body.priority = 0;
1302 res.body.state = "Committed";
1307 const container = getFakeContainer(fakeCrOnHoldUuid);
1309 { method: "GET", url: `**/arvados/v1/container/${fakeCrOnHoldUuid}` },
1312 body: { ...container, state: "Queued", priority: 0 },
1316 // Navigate to process and verify cancel button
1317 cy.goToPath(`/processes/${containerRequest.uuid}`);
1319 cy.get("[data-cy=process-details]").should("contain", crOnHold);
1320 cy.get("[data-cy=process-run-button]").should("exist");
1321 cy.get("[data-cy=process-cancel-button]").should("not.exist");