diff --git a/server/app/db/version.json b/server/app/db/version.json index 255d4289..0735fb69 100644 --- a/server/app/db/version.json +++ b/server/app/db/version.json @@ -1 +1 @@ -{"version": "2026-0227-091935" } \ No newline at end of file +{"version": "2026-0302-111635" } \ No newline at end of file diff --git a/test/cypress/e2e/jobExecute.cy.js b/test/cypress/e2e/jobExecute.cy.js new file mode 100644 index 00000000..0c2a6cef --- /dev/null +++ b/test/cypress/e2e/jobExecute.cy.js @@ -0,0 +1,126 @@ +/** + * ジョブ実行テスト + */ +describe("jobExecute", ()=>{ + const TYPE_INPUT = "input"; + const TYPE_OUTPUT = "output"; + const DEF_COMPONENT_TASK = "task"; + const DEF_COMPONENT_FOREACH = "foreach"; + const DEF_COMPONENT_WHILE = "while"; + const DEF_COMPONENT_WORKFLOW = "workflow"; + const TASK_NAME_0 = "task0"; + const TASK_NAME_1 = "task1"; + + const FOREACH_NAME_0 = "foreach0"; + const WORKFLOW_NAME_0 = "workflow0"; + const WHILE_NAME_0 = "while0"; + const LOCAL_HOST = "localhost"; + const HOST_NAME = "wheel_release_test_server"; + const FILE_NAME_TASK1 = "task1.sh"; + const FILE_NAME_TASK2 = "task2.sh"; + const FILE_NAME_WHILE = "while.sh"; + + const codeTask1 = `echo "test" > message.txt`; + const codeTask2 = `cat message.txt >/dev/null 2>&1`; + const codeWhile = [`set -eu`, `cnt=$(cat counter.txt)`, `if [ "$cnt" -lt 10 ]; then`, ` exit 0`, `else`, ` exit 1`].join("\n"); + + before(()=>{ + return cy.removeAllProjects(); + }); + + beforeEach(()=>{ + cy.viewport("macbook-16"); + return cy.createAndOpenProject(); + }); + + after(()=>{ + return cy.removeAllProjects(); + }); + + /** + * localhostでのタスク実行 + * 試験確認内容:ローカルホストに対するタスク実行ワークフローが + * 完了(status:finished)となること + */ + it("executeLocalhost", ()=>{ + //workflow作成 + cy.createComponent(DEF_COMPONENT_WORKFLOW, WORKFLOW_NAME_0, 501, 500); + //while 作成 + cy.createComponent(DEF_COMPONENT_WHILE, WHILE_NAME_0, 501, 700); + cy.setupWhileWithScriptAndCondition(FILE_NAME_WHILE, codeWhile); + cy.closeProperty(); + //コンポーネント同士を接続 + cy.connectComponentMultiple(WORKFLOW_NAME_0, WHILE_NAME_0); + //workflow選択 + cy.doubleClickComponentName(WORKFLOW_NAME_0); + + //foreach作成 + cy.createComponent(DEF_COMPONENT_FOREACH, FOREACH_NAME_0, 501, 500); + cy.setForeachLoop(2); + cy.closeProperty(); + //foreach選択 + cy.doubleClickComponentName(FOREACH_NAME_0); + //task 1つ目 + cy.createComponent(DEF_COMPONENT_TASK, TASK_NAME_0, 501, 500); + cy.setupTaskWithScriptAndIO(FILE_NAME_TASK1, codeTask1, TYPE_OUTPUT, "message.txt", LOCAL_HOST); + cy.closeProperty(); + //task 2つ目 + cy.createComponent(DEF_COMPONENT_TASK, TASK_NAME_1, 501, 700); + cy.setupTaskWithScriptAndIO(FILE_NAME_TASK2, codeTask2, TYPE_INPUT, "message.txt", LOCAL_HOST); + cy.closeProperty(); + //コンポーネント同士を接続 + cy.connectComponentMultiple(TASK_NAME_0, TASK_NAME_1); + + //タスク実行 + cy.get("[data-cy=\"workflow-play-btn\"]", { timeout: 3000 }).click(); + + //完了まで待機 + cy.checkProjectStatus("finished"); + }); + + /** + * remoteHostでのタスク実行 + * 試験確認内容:リモートホストに対するタスク実行ワークフローが + * 完了(status:finished)となること + */ + it("executeRemoteHost", ()=>{ + //workflow作成 + cy.createComponent(DEF_COMPONENT_WORKFLOW, WORKFLOW_NAME_0, 501, 500); + //while 作成 + cy.createComponent(DEF_COMPONENT_WHILE, WHILE_NAME_0, 501, 700); + cy.setupWhileWithScriptAndCondition(FILE_NAME_WHILE, codeWhile); + cy.closeProperty(); + //コンポーネント同士を接続 + cy.connectComponentMultiple(WORKFLOW_NAME_0, WHILE_NAME_0); + //workflow選択 + cy.doubleClickComponentName(WORKFLOW_NAME_0); + //foreach作成 + cy.createComponent(DEF_COMPONENT_FOREACH, FOREACH_NAME_0, 501, 500); + cy.setForeachLoop(2); + cy.closeProperty(); + //foreach選択 + cy.doubleClickComponentName(FOREACH_NAME_0); + //task 1つ目 + cy.createComponent(DEF_COMPONENT_TASK, TASK_NAME_0, 501, 500); + cy.setupTaskWithScriptAndIO(FILE_NAME_TASK1, codeTask1, TYPE_OUTPUT, "message.txt", HOST_NAME); + cy.switchUseJobScheduler("on"); + cy.closeProperty(); + //task 2つ目 + cy.createComponent(DEF_COMPONENT_TASK, TASK_NAME_1, 501, 700); + cy.setupTaskWithScriptAndIO(FILE_NAME_TASK2, codeTask2, TYPE_INPUT, "message.txt", HOST_NAME); + cy.switchUseJobScheduler("on"); + cy.closeProperty(); + //コンポーネント同士を接続 + cy.connectComponentMultiple(TASK_NAME_0, TASK_NAME_1); + + //タスク実行 + cy.get("[data-cy=\"workflow-play-btn\"]", { timeout: 3000 }).click(); + + //リモートアクセスパスワード + cy.get("[data-cy=\"buttons-ok-btn\"]", { timeout: 3000 }); + cy.passwordType("passw0rd"); + + //完了まで待機 + cy.checkProjectStatus("finished"); + }); +}); diff --git a/test/cypress/support/commands-jobExecute.js b/test/cypress/support/commands-jobExecute.js new file mode 100644 index 00000000..9287ec17 --- /dev/null +++ b/test/cypress/support/commands-jobExecute.js @@ -0,0 +1,101 @@ +/** + * 正規表現で使えるようにメタ文字をエスケープする + * @param {string} s - 対象文字列 + * @returns {string} エスケープ済み文字列 + */ +function escapeRegExp(s) { + return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +/** + * whileにスクリプトファイルを作成 Conditionにセット + */ +Cypress.Commands.add("setupWhileWithScriptAndCondition", (scriptName, shellText)=>{ + const scriptEle = "[data-cy=\"component_property-condition_use_javascript-autocomplete\"]"; + //シェル作成 + cy.scriptMake(scriptName, shellText); + //Conditionをクリック + cy.get("[data-cy=\"component_property-condition-setting_title\"]").click(); + //Conditionにセット + cy.selectValueFromDropdownList(scriptEle, 3, scriptName); + //保存確認 + cy.waitForSnackbar(new RegExp(`${escapeRegExp(scriptName)}\\s+saved\\s*$`, "i")); +}); + +/** + * taskにスクリプトファイルを作成 i/oファイルにセット + */ +Cypress.Commands.add("setupTaskWithScriptAndIO", (scriptName, shellText, inputType, ioFileName, target)=>{ + const scriptEle = "[data-cy=\"component_property-script-autocomplete\"]"; + const hostEle = "[data-cy=\"component_property-host-select\"]"; + + //シェル作成 + cy.scriptMake(scriptName, shellText); + + //script でシェル選択 + cy.selectValueFromDropdownList(scriptEle, 3, scriptName); + + //ローカルホスト選択 + cy.selectValueFromDropdownList(hostEle, 3, target); + + //インプットファイル指定 + cy.enterInputOrOutputFile(inputType, ioFileName, true, true); + + //保存確認 + cy.waitForSnackbar(new RegExp(`${escapeRegExp(scriptName)}\\s+saved\\s*$`, "i")); +}); + +/** + * ファイル保存待機 + */ +Cypress.Commands.add("waitForSnackbar", (messageRe, options = {})=>{ + const timeout = options.timeout ?? 15000; + return cy + .contains("div.v-snackbar__content", messageRe, { timeout }) + .should("be.visible"); +}); + +/** + * foreach 繰り返し設定 + */ +Cypress.Commands.add("setForeachLoop", (length)=>{ + cy.get("[data-cy=\"component_property-loop_set_foreach-panel_title\"]").scrollIntoView() + .click(); + + for (let index = 0; index < length; index++) { + cy.get("[data-cy=\"component_property-index_foreach-list_form\"]").find("input") + .type(index); + cy.get("[data-cy=\"list_form-add-text_field\"]").find("[role=\"button\"]") + .eq(1) + .click(); + } +}); + +/** + * テストで使用するremotehost情報の設定 + */ +Cypress.Commands.add("setupRemotehost", (label, hostName)=>{ + const PORT_NUMBER = 22; + const TEST_USER = "testuser"; + + cy.get("[data-cy=\"tool_bar-navi-icon\"]").click(); + cy.get("[data-cy=\"navigation-manage_remote_host-btn\"]").click(); + cy.get("[data-cy=\"remotehost-new_remote_host_setting-btn\"]").click(); + cy.get("[data-cy=\"add_new_host-label-text_field\"]").type(label); + cy.get("[data-cy=\"add_new_host-hostname-text_field\"]").type(hostName); + cy.get("[data-cy=\"add_new_host-port_number_label-text_field\"]").type(`{selectall}{backspace}${PORT_NUMBER}`); + cy.get("[data-cy=\"add_new_host-user_id-text_field\"]").type(TEST_USER); + //Click on dialog title to trigger blur from all fields + cy.get("[data-cy=\"add_new_host-add_new_host-card_title\"]").click(); + //Wait for OK button to be enabled + cy.get("[data-cy=\"add_new_host-ok-btn\"]", { timeout: 1000 }).should("not.be.disabled") + .click(); + cy.contains("tr", label).find("[data-cy=\"action_row-edit-btn\"]") + .click(); + cy.get("[data-cy=\"add_new_host-add_new_host-card_title\"]").should("be.visible"); + //ダイアログ内のテキスト確認 + cy.get("[data-cy=\"add_new_host-label-text_field\"]").find("input") + .should("have.value", label); + //ダイアログ内のOKボタン + cy.get("[data-cy=\"add_new_host-ok-btn\"]").click(); +}); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 874d0b6c..90958699 100644 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -473,7 +473,7 @@ Cypress.Commands.add("swicthUseJavascriptExpressionForConditionCheck", (flg)=>{ //open file editer Cypress.Commands.add("clickFileEditer", ()=>{ - cy.get("[href=\"/editor\"]").click() + cy.get("[data-cy=\"file_browser-edit_files-btn\"]").click() .wait(animationWaitTime); }); @@ -488,9 +488,9 @@ Cypress.Commands.add("scriptEdit", (scriptName, script)=>{ cy.clickFileEditer(); cy.get("#editor").find("textarea") .type(script, { force: true }); - cy.get("button").contains("button", "save all files") - .click(); - cy.get("[href=\"/graph\"]", { timeout: 5500 }).click() + cy.get("[data-cy=\"workflow-text_editor_close-btn\"]").click(); + cy.get("button").contains(/^keep changes$/i) + .click() .wait(animationWaitTime); }); @@ -562,7 +562,7 @@ Cypress.Commands.add("resetProject", ()=>{ //input remotehost password Cypress.Commands.add("passwordType", (password)=>{ - cy.contains("input password or passphrase").parent() + cy.contains("input password for").parent() .parent() .next() .find("input") diff --git a/test/cypress/support/e2e.js b/test/cypress/support/e2e.js index 2ddf9078..f074e3af 100644 --- a/test/cypress/support/e2e.js +++ b/test/cypress/support/e2e.js @@ -20,6 +20,7 @@ import "./commands-home"; import "./commands-remoteHost"; import "./commands-workFlow"; import "./commands-shortcut"; +import "./commands-jobExecute"; import "cypress-real-events"; //Alternatively you can use CommonJS syntax: diff --git a/test/wheel_config/remotehost.json b/test/wheel_config/remotehost.json index fe51488c..3e7e2a6c 100644 --- a/test/wheel_config/remotehost.json +++ b/test/wheel_config/remotehost.json @@ -1 +1,58 @@ -[] +[ + { + "name": "componentTestLabel", + "host": "TestHostname", + "port": 8000, + "user": "testUser", + "numJob": 5, + "jobScheduler": "PBSPro", + "useBulkjob": true, + "useStepjob": true, + "queue": "testQueues", + "sharedHost": "", + "sharedPath": "", + "renewInterval": 0, + "statusCheckInterval": 60, + "maxStatusCheckError": 10, + "rcfile": "/etc/profile", + "useGfarm": true, + "id": "545dd3e0-b2;6d-11f0-bbe8-272d67e9e112" + }, + { + "name": "testServer", + "host": "wheel_release_test_server", + "port": 8000, + "numJob": 5, + "queue": "workq", + "jobScheduler": "PBSPro", + "useBulkjob": false, + "useStepjob": false, + "sharedHost": "", + "sharedPath": "", + "renewInterval": 0, + "statusCheckInterval": 60, + "maxStatusCheckError": 10, + "id": "91041660-c8a4-11ee-8b76-87edc1fbf31a", + "user": "testuser", + "rcfile": "/etc/profile", + "prependCmd": "" + }, + { + "name": "wheel_release_test_server", + "host": "wheel_release_test_server", + "port": 22, + "user": "testuser", + "id": "dd453fa0-0bc2-11f1-aec1-1f504ee4905a", + "numJob": 5, + "useBulkjob": false, + "useStepjob": false, + "sharedHost": "", + "sharedPath": "", + "renewInterval": 0, + "statusCheckInterval": 60, + "maxStatusCheckError": 10, + "rcfile": "/etc/profile", + "useGfarm": false, + "jobScheduler": "PBSPro" + } +]