Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run_cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
run: sudo mount -t tmpfs -o size=512m tmpfs /dev/shm
- name: Run E2E tests with mock server
working-directory: test
run: npm run test:e2e:mock:run
run: npm run test:e2e:mock:run:sequential
- name: Collect container logs on failure
if: failure()
run: |
Expand Down
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
ARG PLATFORM=linux/amd64
ARG PLATFORM=${BUILDPLATFORM}
FROM --platform=${PLATFORM} node:22-slim AS base
RUN apt-get update && apt -y install curl git rsync openssh-server bzip2 python3 g++ build-essential&&\
curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash &&\
Expand All @@ -15,7 +15,10 @@ RUN mkdir server client
COPY server/package.json server/package.json
COPY client/package.json client/package.json
RUN npm install
RUN npm install --no-save @rollup/rollup-linux-x64-gnu
RUN arch=$(uname -m) && \
if [ "$arch" = "x86_64" ]; then npm install --no-save @rollup/rollup-linux-x64-gnu; \
elif [ "$arch" = "aarch64" ]; then npm install --no-save @rollup/rollup-linux-arm64-gnu; \
fi
# Ensure tar v7 dependencies are available
RUN cd server && npm install @isaacs/fs-minipass --no-save

Expand Down
18 changes: 13 additions & 5 deletions TEST_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,19 @@ npm run -w test test:e2e:mock

**個別スクリプト:**
```bash
npm run test:e2e:mock:start # 全コンテナ起動 + 起動完了待機
npm run test:e2e:mock:run # Cypressのみ実行
npm run test:e2e:mock:stop # 全コンテナ停止
npm run test:e2e:mock:start # 全コンテナ起動 + 起動完了待機
npm run test:e2e:mock:run # Cypressのみ実行 (全スペックを1プロセスで)
npm run test:e2e:mock:run:sequential # スペックを1ファイルずつ順番に実行 (Chrome OOM回避)
npm run test:e2e:mock:run:sequential:bail # 最初の失敗で即停止 (開発時のfail-fast用)
npm run test:e2e:mock:stop # 全コンテナ停止
```

#### シーケンシャル実行について

`test:e2e:mock:run:sequential` は `test/run-specs-sequential.sh` を呼び出し、gfarm以外の全スペックファイルを1つずつ別々の `cypress run` プロセスで実行します。各スペックが独立したChromeレンダラーを持つため、全スペックを1プロセスで実行した際に起きるChrome OOMクラッシュを防げます。失敗したスペックの一覧を実行後にまとめて表示します。

`--bail` フラグを指定する `test:e2e:mock:run:sequential:bail` は最初の失敗が出た時点で残りのスペックをスキップします。開発中に素早く失敗箇所を特定したい場合に便利です。

### 2.6 リモートモード — 既存サーバーへのテスト

WHEELサーバーがすでに起動済みの場合に、モックを使わず実サーバーに対してそのままCypressを実行します。
Expand Down Expand Up @@ -354,8 +362,8 @@ Dockerコンテナ (4台, ネットワーク: wheel-e2e-net):
mock (port 3101/3102): Socket.IOモック + HTTPモック
gateway (port 3001): リバースプロキシ (WHEELとモックへ振り分け)

Chromeクラッシュ対策: /dev/shm を 512MB に拡張
テスト: npm run test:e2e:mock:run (test/)
Chromeクラッシュ対策: /dev/shm を 512MB に拡張 + スペックを1ファイルずつ順番に実行
テスト: npm run test:e2e:mock:run:sequential (test/)

失敗時のアーティファクト:
container-logs : gateway/mock/wheel/wheel_auth のコンテナログ
Expand Down
25 changes: 13 additions & 12 deletions client/src/components/common/applicationToolBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@
<template>
<v-app-bar>
<template #prepend>
<a
:href="homeURL"
data-cy="tool_bar-wheel_logo-logo"
<v-tooltip
location="right"
text="Go to Home Page"
>
<v-tooltip
location="right"
text="Go to Home Page"
>
<template #activator="{ props }">
<template #activator="{ props }">
<a
v-bind="props"
:href="homeURL"
data-cy="tool_bar-wheel_logo-logo"
>
<v-img
v-bind="props"
eager
height="72px"
width="180px"
:src="imgLogo"
alt="wheel title logo"
/>
</template>
</v-tooltip>
</a>
</a>
</template>
</v-tooltip>
</template>
<v-app-bar-title
class="text-lowercase text-decoration-none text-h5 white--text"
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/common/innerTreeview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<v-icon
v-bind="props"
:icon="getNodeIcon(isOpen, item)"
@click.stop="(e) => { props.onClick(e); if (!isOpen) { onClickNodeIcon(item); } }"
@click.stop="() => { if (!isOpen) { onClickNodeIcon(item); } }"
/>
</template>
<div @click="(e) => { if (!isOpen) { props.onClick(e); onClickNodeIcon(item); } onActiveted(item); }">
Expand Down
9 changes: 7 additions & 2 deletions client/src/components/fileBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -454,15 +454,20 @@ export default {
const cb = (fileList)=>{
if (!Array.isArray(fileList)) {
reject(fileList);
return;
}
item.children = fileList
const children = fileList
.filter((e)=>{ return !e.isComponentDir; })
.map(fileListModifier.bind(null, this.pathSep));
// Re-look up the item at callback time: this.items may have been replaced
// by a getComponentDirRootFiles() refresh while the socket request was in flight.
const currentItem = this.getActiveItem(item.id);
(currentItem || item).children = children;
resolve();
};
const activeItem = this.getActiveItem(item.id);
if (activeItem === null) {
console.log("failed to get current selected Item");
resolve();
return;
}
if (item.type === "dir" || item.type === "dir-link") {
Expand Down
2 changes: 1 addition & 1 deletion server/app/core/exportComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ async function exportComponent(projectRootDir, componentID) {

//Get path relative to tempdRoot, then prepend /exportComponent/
const relativePath = path.relative(root, archiveFilename);
const url = `${baseURL}/exportComponent/${relativePath}`;
const url = `${baseURL.replace(/\/$/, "")}/exportComponent/${relativePath}`;
return url;
}

Expand Down
2 changes: 1 addition & 1 deletion server/app/core/exportProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async function exportProject(projectRootDir, name = null, mail = null, memo = nu
[`${projectJson.name}.wheel`]
);

const url = `${baseURL}/${path.join(path.relative(path.dirname(dir), archiveFilename))}`;
const url = `${baseURL.replace(/\/$/, "")}/${path.join(path.relative(path.dirname(dir), archiveFilename))}`;
return url;
}

Expand Down
5 changes: 3 additions & 2 deletions server/app/core/sshManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const _internal = {
getSsh,
hasEntry,
askPassword,
emitAll
emitAll,
verboseSsh
};

/**
Expand Down Expand Up @@ -205,7 +206,7 @@ async function createSsh(projectRootDir, remoteHostName, hostinfo, clientID, isS
if (hostinfo.readyTimeout) {
hostinfo.ConnectTimeout = Math.floor(hostinfo.readyTimeout / 1000);
}
if (verboseSsh) {
if (_internal.verboseSsh) {
hostinfo.sshOpt = ["-vvv"];
}
if (hostinfo.username) {
Expand Down
2 changes: 1 addition & 1 deletion server/app/db/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ function getIntVar(target, alt) {
* @returns {string} -
*/
function getStringVar(target, alt) {
return typeof target === "string" ? target : alt;
return (typeof target === "string" && target !== "") ? target : alt;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions server/app/handlers/fileManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ export async function onDownload(projectRootDir, target, cb) {
}

const ext = downloadZip ? ".zip" : "";
const url = `${baseURL}/${path.join(path.relative(downloadRootDir, tmpDir), targetBasename)}${ext}`;
const url = `${baseURL.replace(/\/$/, "")}/${path.join(path.relative(downloadRootDir, tmpDir), targetBasename)}${ext}`;
getLogger(projectRootDir).debug("Download url is ready", url);
cb(url);
};
Expand Down Expand Up @@ -453,7 +453,7 @@ export async function onDownloadFullLog(projectRootDir, cb) {
//remove temporary directory
await fs.remove(archiveDir);

const url = `${baseURL}/${path.join(path.relative(downloadRootDir, tmpDir), archiveName)}.zip`;
const url = `${baseURL.replace(/\/$/, "")}/${path.join(path.relative(downloadRootDir, tmpDir), archiveName)}.zip`;
getLogger(projectRootDir).info("Debug log archive is ready for download", url);
cb(url);
} catch (e) {
Expand Down
15 changes: 9 additions & 6 deletions server/test/app/core/projectOperator.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ describe("UT for projectOperation callback function", function () {
beforeEach(async ()=>{
await fs.remove(testDirRoot);
await createNewProject(projectRootDir, "test project", null, "test", "test@example.com");
const sbs = _internal.projectOperationQueues.get(projectRootDir);
if (sbs) {
sbs.clear();
}
_internal.projectOperationQueues.clear();
sinon.stub(_internal, "onRunProject").value(onRunProject);
sinon.stub(_internal, "onStopProject").value(onStopProject);
sinon.stub(_internal, "onCleanProject").value(onCleanProject);
Expand All @@ -64,7 +59,15 @@ describe("UT for projectOperation callback function", function () {
onRevertProject.reset();
onSaveProject.reset();
});
afterEach(()=>{
afterEach(async ()=>{
const sbs = _internal.projectOperationQueues.get(projectRootDir);
if (sbs) {
sbs.clear();
while (sbs.running.size > 0) {
await sleep(10);
}
}
_internal.projectOperationQueues.clear();
sinon.restore();
});
after(async ()=>{
Expand Down
12 changes: 2 additions & 10 deletions server/test/app/core/sshManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,6 @@ describe("#createSsh", ()=>{
let askPasswordStub;
let SshClientWrapperStub;
let canConnectStub;
let originalWheelVerboseSsh;

beforeEach(()=>{
hasEntryStub = sinon.stub(_internal, "hasEntry");
getSshStub = sinon.stub(_internal, "getSsh");
Expand All @@ -387,17 +385,11 @@ describe("#createSsh", ()=>{
canConnect: canConnectStub
};
});
originalWheelVerboseSsh = process.env.WHEEL_VERBOSE_SSH;
delete process.env.WHEEL_VERBOSE_SSH;
sinon.stub(_internal, "verboseSsh").value(false);
});

afterEach(()=>{
sinon.restore();
if (originalWheelVerboseSsh !== undefined) {
process.env.WHEEL_VERBOSE_SSH = originalWheelVerboseSsh;
} else {
delete process.env.WHEEL_VERBOSE_SSH;
}
});

it("should return an existing ssh instance if hasEntry is true", async ()=>{
Expand Down Expand Up @@ -509,7 +501,7 @@ describe("#createSsh", ()=>{
});

it("should set sshOpt=['-vvv'] if WHEEL_VERBOSE_SSH is truthy", async ()=>{
process.env.WHEEL_VERBOSE_SSH = "true";
sinon.stub(_internal, "verboseSsh").value(true);
hasEntryStub.returns(false);
canConnectStub.resolves(true);

Expand Down
2 changes: 2 additions & 0 deletions test/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ services:
WHEEL_ANONYMOUS_LOGIN: YES
WHEEL_ANONYMOUS_PASSWORD: WheelTest123!
WHEEL_LOG_LEVEL: OFF
volumes:
- "./wheel_config_auth:/root/.wheel"
depends_on:
- wheel_release_test

Expand Down
2 changes: 1 addition & 1 deletion test/cypress/e2e/components/hpciss.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe("components", ()=>{
it("転送対象ファイル・フォルダの設定-削除ボタン表示確認(output file)-削除ボタンが表示されることを確認", ()=>{
cy.createComponent(DEF_COMPONENT_HPCISS, HPCISS_NAME_0, 501, 500);
cy.enterInputOrOutputFile(TYPE_OUTPUT, "testOutputFile", true, true);
cy.get("[data-cy=\"action_row-delete-btn\"]").should("be.visible");
cy.get("[data-cy=\"action_row-delete-btn\"]").scrollIntoView().should("be.visible");
});

/**
Expand Down
2 changes: 1 addition & 1 deletion test/cypress/e2e/components/hpcisstar.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ describe("components", ()=>{
it("tarコンポーネント共通機能確認-転送対象ファイル・フォルダの設定-削除ボタン表示確認(output file)-削除ボタンが表示されることを確認", ()=>{
cy.createComponent(DEF_COMPONENT_HPCISS, HPCISS_NAME_0, 501, 500);
cy.enterInputOrOutputFile(TYPE_OUTPUT, "testOutputFile", true, true);
cy.get("[data-cy=\"action_row-delete-btn\"]").should("be.visible");
cy.get("[data-cy=\"action_row-delete-btn\"]").scrollIntoView().should("be.visible");
});

/**
Expand Down
6 changes: 3 additions & 3 deletions test/cypress/e2e/components/ps.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -637,15 +637,15 @@ describe("components", ()=>{
cy.get("body").should(($b)=>{
const editorGone = $b.find("[data-cy=\"workflow-text_editor_close-btn\"]").length === 0;
const discardShown = $b.find("button").filter((i, el)=>{
return /discard all changes/i.test(el.textContent);
return /Discard changes/i.test(el.textContent);
}).length > 0;
expect(editorGone || discardShown, "editor closed or discard appeared").to.be.true;
})
.then(($b)=>{
if ($b.find("button").filter((i, el)=>{
return /discard all changes/i.test(el.textContent);
return /Discard changes/i.test(el.textContent);
}).length) {
cy.contains("button", /discard all changes/i).click();
cy.contains("button", /Discard changes/i).click();
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion test/cypress/e2e/components/stepjobTask.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe("components", ()=>{
cy.createStepjobComponentAndDoubleClick(DEF_COMPONENT_STEPJOB, STEPJOB_NAME_0, 501, 500);
cy.createComponent(DEF_COMPONENT_STEPJOB_TASK, STEPJOB_TASK_NAME_0, 501, 500);
cy.enterInputOrOutputFile(TYPE_OUTPUT, "testOutputFile", true, true);
cy.get("[data-cy=\"action_row-delete-btn\"]").should("be.visible");
cy.get("[data-cy=\"action_row-delete-btn\"]").scrollIntoView().should("be.visible");
});

/**
Expand Down
2 changes: 1 addition & 1 deletion test/cypress/e2e/components/storage.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe("components", ()=>{
it("転送対象ファイル・フォルダの設定-削除ボタン表示確認(output file)-削除ボタンが表示されることを確認", ()=>{
cy.createComponent(DEF_COMPONENT_STORAGE, STORAGE_NAME_0, 501, 500);
cy.enterInputOrOutputFile(TYPE_OUTPUT, "testOutputFile", true, true);
cy.get("[data-cy=\"action_row-delete-btn\"]").should("be.visible");
cy.get("[data-cy=\"action_row-delete-btn\"]").scrollIntoView().should("be.visible");
});

/**
Expand Down
10 changes: 5 additions & 5 deletions test/cypress/e2e/components/task.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,9 +366,9 @@ describe("components", ()=>{
cy.get("[data-cy=\"file_browser-treeview-treeview\"]").contains("task1-run")
.should("exist")
.click();
cy.get("[data-cy=\"file_browser-treeview-treeview\"]").contains("run-a.sh")
cy.contains("[data-cy=\"file_browser-treeview-treeview\"]", "run-a.sh", { timeout: 15000 })
.should("exist");
cy.get("[data-cy=\"file_browser-treeview-treeview\"]").contains("run-b.sh")
cy.contains("[data-cy=\"file_browser-treeview-treeview\"]", "run-b.sh", { timeout: 15000 })
.should("exist");
cy.closeProperty();
});
Expand Down Expand Up @@ -410,7 +410,7 @@ describe("components", ()=>{
cy.get("[data-cy=\"file_browser-treeview-treeview\"]").contains("task1-run")
.should("exist")
.click();
cy.get("[data-cy=\"file_browser-treeview-treeview\"]").contains("run-a.sh")
cy.contains("[data-cy=\"file_browser-treeview-treeview\"]", "run-a.sh", { timeout: 15000 })
.should("exist");
cy.closeProperty();
});
Expand Down Expand Up @@ -1002,7 +1002,7 @@ describe("components", ()=>{
it("プロパティ設定確認-シェルスクリプト選択セレクトボックス表示確認-シェルスクリプト選択セレクトボックスが表示されていることを確認", ()=>{
cy.get("[data-cy=\"component_property-advanced-panel_title\"]").click();
cy.get("[data-cy=\"component_property-task_use_javascript-autocomplete\"]").find("input")
.should("be.visible");
.should("exist");
});

/**
Expand Down Expand Up @@ -1166,7 +1166,7 @@ describe("components", ()=>{
let targetDropBoxCy = "[data-cy=\"component_property-host-select\"]";
cy.selectValueFromDropdownList(targetDropBoxCy, 2, COMPONENT_TEST_LABEL);
cy.get("[data-cy=\"component_property-remote_file-panel_title\"]").click();
cy.get("[data-cy=\"component_property-exclude-list_form\"]").should("be.visible");
cy.get("[data-cy=\"component_property-exclude-list_form\"]").find("input").first().should("exist");
});

/**
Expand Down
2 changes: 1 addition & 1 deletion test/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ Cypress.Commands.add("scriptEdit", (scriptName, script)=>{
cy.get("#editor").find("textarea")
.type(script, { force: true });
cy.get("[data-cy=\"workflow-text_editor_close-btn\"]").click();
cy.get("button").contains(/^keep changes$/i)
cy.get("button").contains(/Keep changes/i)
.click()
.wait(animationWaitTime);
});
Expand Down
4 changes: 3 additions & 1 deletion test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
"test": "npx cypress open",
"gateway": "GW_PORT=3001 REAL_APP=http://localhost:8089 MOCK_SIO=http://localhost:3101 node ws-gateway.cjs",
"test:e2e:mock": "npm run test:e2e:mock:start && npx cypress run --browser chrome; npm run test:e2e:mock:stop",
"test:e2e:mock:start": "docker compose up -d --build && npx wait-on http://localhost:8089 http://localhost:8090 tcp:127.0.0.1:3001 tcp:127.0.0.1:3101 tcp:127.0.0.1:3102 --timeout 300000",
"test:e2e:mock:start": "docker compose up -d --build && npx wait-on http://localhost:8089 http://localhost:8090 tcp:127.0.0.1:3001 tcp:127.0.0.1:3101 tcp:127.0.0.1:3102 --timeout 600000",
"test:e2e:mock:stop": "docker compose down",
"test:e2e:mock:run": "npx cypress run --browser chrome",
"test:e2e:mock:run:sequential": "bash run-specs-sequential.sh",
"test:e2e:mock:run:sequential:bail": "bash run-specs-sequential.sh --bail",
"test:e2e": "npm run test:e2e:start && npx cypress run --browser chrome --config requestTimeout=300000,defaultCommandTimeout=300000,retries=1 --env USE_MOCK=false; npm run test:e2e:stop",
"test:e2e:start": "docker compose up -d --build wheel_release_test_server wheel_release_test",
"test:e2e:stop": "docker compose down",
Expand Down
Loading
Loading