diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..18ff1c2 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,103 @@ +name: Playwright Tests + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + playwrigth-standalone: + timeout-minutes: 60 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: yaml + + - name: Install System Dependencies + run: | + sudo apt-get update + sudo apt-get install -y unzip patchelf wget php-cli php-yaml gnuplot + + - name: Install SunCAE Dependencies (FeenoX & Gmsh) standalone + run: ./deps.sh + + - name: Check script + run: php html/check.php + + - name: Install Node Dependencies + run: npm ci + + - name: Install Playwright Browsers + run: npx playwright install --with-deps + + - name: Run Playwright tests + run: npx playwright test + + - name: Upload Playwright Report + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report-standalone + path: playwright-report-stanadlone/ + retention-days: 30 + + playwrigth-apt: + timeout-minutes: 60 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: yaml + + - name: Install System Dependencies + run: | + sudo apt-get update + sudo apt-get install -y unzip patchelf wget php-cli php-yaml gnuplot + +# feenox 1.2 is not available in ubuntu 24.04, only in 25.10 + - name: Install SunCAE Dependencies (FeenoX & Gmsh) from apt + run: | + sudo apt-get install -y python3-gmsh pandoc + ./deps.sh + + - name: Check script + run: php html/check.php + + - name: Install Node Dependencies + run: npm ci + + - name: Install Playwright Browsers + run: npx playwright install --with-deps + + - name: Run Playwright tests + run: npx playwright test + + - name: Upload Playwright Report + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report-apt + path: playwright-report-apt/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index ea89aa9..d1b551e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ bin deps data +node_modules/ +test-results/ +playwright-report/ diff --git a/autoclean.sh b/autoclean.sh index a7e1240..f89bf50 100644 --- a/autoclean.sh +++ b/autoclean.sh @@ -16,7 +16,8 @@ if [ ! -d auths ]; then echo 1 fi -for i in bin deps data; do +# cat .gitgnore? +for i in bin deps data node_modules test-results playwright-report; do echo -n "cleaning ${i}... " rm -rf ${i} || exit 1 echo "ok" @@ -32,3 +33,8 @@ for i in $(find . -name .gitignore); do cd ${pwd} fi done + +# more +for i in x3dom.js x3dom.css; do + find . -name {$i} | xargs rm -f +done diff --git a/cadimporters/upload/gmshcheck.py b/cadimporters/upload/gmshcheck.py deleted file mode 100755 index 0722292..0000000 --- a/cadimporters/upload/gmshcheck.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/python3 -import sys -sys.path.append("../../../../bin") -import gmsh - -gmsh.initialize() -gmsh.finalize() diff --git a/cadprocessors/gmsh/cad2stl.py b/cadprocessors/gmsh/cad2stl.py index 1bb34f9..8ccd313 100755 --- a/cadprocessors/gmsh/cad2stl.py +++ b/cadprocessors/gmsh/cad2stl.py @@ -1,8 +1,13 @@ #!/usr/bin/python3 import sys -sys.path.append("../../../../bin") -import gmsh import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +bin_dir = os.path.join(script_dir, '../../', 'bin') # if bin is one level up +if os.path.exists(bin_dir): + sys.path.insert(0, bin_dir) +import gmsh + import math import random import colorsys diff --git a/cadprocessors/gmsh/cadcheck.py b/cadprocessors/gmsh/cadcheck.py index 0a43995..8ec000c 100755 --- a/cadprocessors/gmsh/cadcheck.py +++ b/cadprocessors/gmsh/cadcheck.py @@ -1,8 +1,13 @@ #!/usr/bin/python3 import sys -sys.path.append("../../../../bin") -import gmsh import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +bin_dir = os.path.join(script_dir, '../../', 'bin') # if bin is one level up +if os.path.exists(bin_dir): + sys.path.insert(0, bin_dir) +import gmsh + import math import json diff --git a/cadprocessors/gmsh/cadimport.py b/cadprocessors/gmsh/cadimport.py index 1779891..56225f5 100755 --- a/cadprocessors/gmsh/cadimport.py +++ b/cadprocessors/gmsh/cadimport.py @@ -1,9 +1,14 @@ #!/usr/bin/python3 import sys -sys.path.append("../../../../bin") -import gmsh import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +bin_dir = os.path.join(script_dir, '../../', 'bin') # if bin is one level up +if os.path.exists(bin_dir): + sys.path.insert(0, bin_dir) import gmsh + +import os import math import random import colorsys diff --git a/cadprocessors/gmsh/gmshcheck.py b/cadprocessors/gmsh/gmshcheck.py new file mode 100755 index 0000000..c8beabc --- /dev/null +++ b/cadprocessors/gmsh/gmshcheck.py @@ -0,0 +1,21 @@ +#!/usr/bin/python3 +import sys +import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +bin_dir = os.path.join(script_dir, '../../', 'bin') # if bin is one level up +if os.path.exists(bin_dir): + sys.path.insert(0, bin_dir) +import gmsh + +# Initialize the Gmsh API +gmsh.initialize() + +# Get the Gmsh version string +version = gmsh.option.getString("General.Version") + +# Print the version +print(version) + +# Finalize the Gmsh API +gmsh.finalize() diff --git a/cadprocessors/gmsh/process.php b/cadprocessors/gmsh/process.php index 00154c3..a2cae73 100644 --- a/cadprocessors/gmsh/process.php +++ b/cadprocessors/gmsh/process.php @@ -14,16 +14,20 @@ return_error_json("username is empty"); } -$cad_dir = "../../data/{$username}/cads/{$cad_hash}"; +$cad_dir = $data_dir . "{$username}/cads/{$cad_hash}"; if (file_exists($cad_dir) === false) { - mkdir($cad_dir, $permissions, true); + if (mkdir($cad_dir, $permissions, true) === false) { + return_error_json("cannot mkdir {$cad_dir}"); + } +} +if (chdir($cad_dir) === false) { + return_error_json("cannot chdir to {$cad_dir}"); } -chdir($cad_dir); // ------------------------------------------------------------ if (file_exists("cad.json") === false) { exec(sprintf("%s/cadimport.py 2>&1", __DIR__), $output, $error_level); - + // TODO: keep output if ($error_level != 0) { $error_message = "Error {$error_level} when importing CAD: "; diff --git a/deps.sh b/deps.sh index 7985bc0..1db9b64 100755 --- a/deps.sh +++ b/deps.sh @@ -19,15 +19,19 @@ for i in wget tar unzip python3; do fi done +# create gitignored directories +mkdir -p deps bin + +# this one needs to be either world writable or owned by the user running the web server +# we start with 0777 but a sane admin would change it back to 0744 (or less) if [ ! -d data ]; then mkdir -p data chmod 0777 data fi -mkdir -p deps -# Function to compare versions (include in main deps.sh or source from a utils file) +# Function to compare versions version_ge() { printf '%s\n%s\n' "$2" "$1" | sort -V -C return $? diff --git a/html/check.php b/html/check.php new file mode 100644 index 0000000..bc1454b --- /dev/null +++ b/html/check.php @@ -0,0 +1,200 @@ +\n"; + +$username_output = []; +exec('whoami', $username_output); +$user = $username_output[0]; + +echo "[info] username running the web server is {$user}
\n"; + +if (file_exists($data_dir) === false) { + if (mkdir($data_dir, 0777) === false) { + echo "[error] cannot create data dir {$data_dir}
\n"; + exit(1); + } +} else { + if (is_dir($data_dir) === false) { + echo "[error] data dir exists but is not a directory
\n"; + exit(2); + } +} + +if (is_writable($data_dir)) { + echo "[good] {$data_dir} is writable by user {$user}
\n"; +} else { + echo "[error] {$data_dir} is not writable by user {$user}
\n"; + exit(3); +} + +// --- bin dir ---------------- +$bin_dir = __DIR__ . "/../bin"; +echo "[info] bin_dir is {$bin_dir}
\n"; + +if (file_exists($bin_dir) && is_dir($bin_dir)) { + echo "[good] {$bin_dir} exists
\n"; +} else { + echo "[error] {$bin_dir} does not exist
\n"; + exit(4); +} + + + + +// --- logging ---------------------------------- +include(__DIR__ . "/common.php"); +$username = "root"; +$err = suncae_log("running check.php script"); + +if ($err == 0) { + echo "[good] logging works
\n"; +} else { + echo "[error] cannot create a log entry
\n"; + exit(5); +} + + +// conf +include(__DIR__ . "/../conf.php"); + +// --- auth ---------------------------------- +if (file_exists(__DIR__ . "/../auths/{$auth}/auth.php")) { + echo "[good] auth {$auth} exists
\n"; +} else { + echo "[error] auth {$auth} does not exist
\n"; + exit(6); +} + +// --- ux ---------------------------------- +if (file_exists(__DIR__ . "/../uxs/{$ux}/index.php")) { + echo "[good] ux {$ux} exists
\n"; +} else { + echo "[error] ux {$ux} does not exist
\n"; + exit(7); +} + +if ($ux == "faster-than-quick") { + foreach (['css/bootstrap.min.css', 'css/katex.min.css', 'css/x3dom.css'] as $i) { + if (file_exists(__DIR__ . "/../uxs/{$ux}/{$i}")) { + echo "[good] {$i} exists
\n"; + } else { + echo "[error] {$i} does not exist
\n"; + exit(8); + } + } + + // pandoc + if (file_exists("{$bin_dir}/pandoc")) { + echo "[good] pandoc binary exists
\n"; + echo "[info] " . shell_exec("ls -la {$bin_dir}/pandoc") . "
\n"; + } else { + echo "[error] pandoc binary does not exist
\n"; + exit(9); + } + $exec_output = []; + exec("{$bin_dir}/pandoc --version 2>&1", $exec_output, $err); + // TODO: check version is good enough + if ($err == 0) { + echo "[good] pandoc version is {$exec_output[0]}
\n"; + } else { + echo "[error] pandoc binary does not work
\n"; + for ($i = 0; $i < count($exec_output); $i++) { + echo "[info] {$exec_output[$i]}
\n"; + } + exit(10); + } +} + + +// --- cadimporter ---------------------------------- +if (file_exists(__DIR__ . "/../cadimporters/{$cadimporter}/import_cad.php")) { + echo "[good] cadimporter {$cadimporter} exists
\n"; +} else { + echo "[error] cadimporters {$cadimporter} does not exist
\n"; + exit(11); +} + + + +// --- cadprocessor ---------------------------------- +if (file_exists(__DIR__ . "/../cadprocessors/{$cadprocessor}/process.php")) { + echo "[good] cadprocessor {$cadprocessor} exists
\n"; +} else { + echo "[error] cadprocessor {$cadprocessor} does not exist
\n"; + exit(12); +} + + +if ($cadprocessor == "gmsh") { + foreach (['css/bootstrap.min.css', 'css/katex.min.css', 'css/x3dom.css'] as $i) { + if (file_exists(__DIR__ . "/../uxs/{$ux}/{$i}")) { + echo "[good] {$i} exists
\n"; + } else { + echo "[error] {$i} does not exist
\n"; + exit(13); + } + } + + // gmsh + if (file_exists("{$bin_dir}/gmsh")) { + echo "[good] gmsh binary exists
\n"; + echo "[info] " . shell_exec("ls -la {$bin_dir}/gmsh") . "
\n"; + } else { + echo "[error] gmsh binary does not exist
\n"; + exit(14); + } + $exec_output = []; + exec("{$bin_dir}/gmsh -version 2>&1", $exec_output, $err); + // TODO: check version is good enough + if ($err == 0) { + echo "[good] gmsh version is {$exec_output[0]}
\n"; + } else { + echo "[error] gmsh binary does not work
\n"; + for ($i = 0; $i < count($exec_output); $i++) { + echo "[info] {$exec_output[$i]}
\n"; + } + exit(15); + } + + // python + $exec_output = []; + exec("which python", $exec_output, $err); + if ($err == 0) { + echo "[good] python binary exists at {$exec_output[0]}
\n"; + } else { + echo "[error] python binary does not exist
\n"; + exit(16); + } + $exec_output = []; + exec("python --version 2>&1", $exec_output, $err); + // TODO: check version is good enough + if ($err == 0) { + echo "[good] python version is {$exec_output[0]}
\n"; + } else { + echo "[error] python binary does not work
\n"; + for ($i = 0; $i < count($exec_output); $i++) { + echo "[info] {$exec_output[$i]}
\n"; + } + exit(17); + } + + // gmsh python wrapper + $exec_output = []; + exec("python " . __DIR__ . "/../cadprocessors/gmsh/gmshcheck.py 2>&1", $exec_output, $err); + // TODO: check version is good enough + if ($err == 0) { + echo "[good] python gmsh wrapper version is {$exec_output[0]}
\n"; + } else { + echo "[error] python gmsh wrapper does not work
\n"; + for ($i = 0; $i < count($exec_output); $i++) { + echo "[info] {$exec_output[$i]}
\n"; + } + exit(18); + } + + // TODO: check python and binary versions match + + +} diff --git a/html/common.php b/html/common.php index a1e52fe..ec90dfe 100644 --- a/html/common.php +++ b/html/common.php @@ -3,8 +3,15 @@ // SunCAE is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // SunCAE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. -$permissions = 0755; +$permissions = 0777; $id = (isset($_POST["id"])) ? $_POST["id"] : ((isset($_GET["id"])) ? $_GET["id"] : ""); +$data_dir = __DIR__ . "/../data/"; +if (file_exists($data_dir) === false) { + if (mkdir($data_dir, $permissions, true) === false) { + echo "cannot mkdir {$data_dir}, please check permissions"; + exit(1); + } +} // based on original work from the PHP Laravel framework if (!function_exists('str_contains')) { @@ -16,12 +23,14 @@ function str_contains($haystack, $needle) { function suncae_log_write($file_path, $username, $message) { $log = fopen($file_path, "a"); - if ($log === false) { - suncae_error("Cannot open log file, please check permissions."); + if ($log) { + $ip = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : "localhost"; + fprintf($log, "%s %s\t%s: %s\n", date("c"), $ip, $username, $message); + fclose($log); + return 0; + } else { + return 1; } - fprintf($log, "%s %s\t%s: %s\n", date("c"), $_SERVER['REMOTE_ADDR'], $username, $message); - fclose($log); - } function suncae_log_error($message, $level = 0) { @@ -33,7 +42,7 @@ function suncae_log_error($message, $level = 0) { $log_dir = __DIR__ . "/../data/logs/"; if (file_exists($log_dir) == false) { if (mkdir($log_dir, $permissions, true) == false) { - suncae_error("error: cannot create log directory"); + exit(1); } } @@ -43,7 +52,6 @@ function suncae_log_error($message, $level = 0) { if ($level > 0) { suncae_log_write("{$log_dir}0-{$date}.log", $username, $message); } - } @@ -64,25 +72,30 @@ function suncae_log($message, $level = 0) { $log_dir = __DIR__ . "/../data/logs/"; if (file_exists($log_dir) == false) { if (mkdir($log_dir, $permissions, true) == false) { - suncae_error("error: cannot create log directory"); + return 1; } } $date = date('Y-m-d'); suncae_log_write("{$log_dir}0-{$date}.log", $username, $message); if ($level > 0) { - suncae_log_write("{$log_dir}{$level}-{$date}.log", $username, $message); + if (suncae_log_write("{$log_dir}{$level}-{$date}.log", $username, $message) != 0) { + return 1; + } } if ($username != "anonymous") { $log_dir = __DIR__ . "/../data/{$username}/"; if (file_exists($log_dir) == false) { if (mkdir($log_dir, $permissions, true) == false) { - suncae_error("error: cannot create log directory"); + return 2; } } - suncae_log_write("{$log_dir}activity.log", $username, $message); + if (suncae_log_write("{$log_dir}activity.log", $username, $message) != 0) { + return 1; + } } + return 0; } diff --git a/meshers/gmsh/deps.sh b/meshers/gmsh/deps.sh index af53a80..dc93e76 100644 --- a/meshers/gmsh/deps.sh +++ b/meshers/gmsh/deps.sh @@ -1,7 +1,7 @@ #!/bin/false gmsh_version=4.14.0 -gmsh_min_version=4.11.0 # Minimum required version +gmsh_min_version=4.13.0 # Minimum required version for xao support # Function to extract version from binary get_gmsh_version() { @@ -36,11 +36,13 @@ if [ $use_system_binary = 0 ]; then echo "error: downloaded gmsh needs ${i}, please do sudo apt install patchelf" exit 1 fi + mkdir -p bin cd deps if [ ! -e ${gmsh_tarball}.tgz ]; then - wget -c http://gmsh.info/bin/Linux/${gmsh_tarball}.tgz + wget -q -c http://gmsh.info/bin/Linux/${gmsh_tarball}.tgz fi tar xzf ${gmsh_tarball}.tgz + rm -f ../bin/gmsh.py ../bin/gmsh ../bin/libgmsh.so* cp ${gmsh_tarball}/bin/gmsh ../bin cp ${gmsh_tarball}/lib/gmsh.py ../bin cp -d ${gmsh_tarball}/lib/libgmsh.so* ../bin diff --git a/meshers/gmsh/mesh_data.py b/meshers/gmsh/mesh_data.py index 1b330fb..3b13f0a 100755 --- a/meshers/gmsh/mesh_data.py +++ b/meshers/gmsh/mesh_data.py @@ -1,8 +1,13 @@ #!/usr/bin/python3 import sys -sys.path.append("../../../../bin") -import gmsh import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +bin_dir = os.path.join(script_dir, '../../', 'bin') # if bin is one level up +if os.path.exists(bin_dir): + sys.path.insert(0, bin_dir) +import gmsh + import json # Add a segment, always in increasing order, to make duplicate detection easy diff --git a/meshers/gmsh/mesh_meta.py b/meshers/gmsh/mesh_meta.py index 8382b19..1097cec 100755 --- a/meshers/gmsh/mesh_meta.py +++ b/meshers/gmsh/mesh_meta.py @@ -1,9 +1,13 @@ #!/usr/bin/python3 import sys -sys.path.append("../../../../bin") -import gmsh import os -import sys + +script_dir = os.path.dirname(os.path.abspath(__file__)) +bin_dir = os.path.join(script_dir, '../../', 'bin') # if bin is one level up +if os.path.exists(bin_dir): + sys.path.insert(0, bin_dir) +import gmsh + import math import json diff --git a/meshers/gmsh/quality.py b/meshers/gmsh/quality.py index d5855f1..9df9519 100644 --- a/meshers/gmsh/quality.py +++ b/meshers/gmsh/quality.py @@ -1,7 +1,13 @@ #!/usr/bin/python3 import sys -sys.path.append("../../../../bin") +import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +bin_dir = os.path.join(script_dir, '../../', 'bin') # if bin is one level up +if os.path.exists(bin_dir): + sys.path.insert(0, bin_dir) import gmsh + import math gmsh.initialize(sys.argv) diff --git a/meshers/gmsh/trymesh.py b/meshers/gmsh/trymesh.py index edfe304..6a430f3 100755 --- a/meshers/gmsh/trymesh.py +++ b/meshers/gmsh/trymesh.py @@ -1,9 +1,14 @@ #!/usr/bin/python3 import sys -sys.path.append("../../../../bin") +import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +bin_dir = os.path.join(script_dir, '../../', 'bin') # if bin is one level up +if os.path.exists(bin_dir): + sys.path.insert(0, bin_dir) import gmsh + import math -import os import json import signal diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ba8c545 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,75 @@ +{ + "name": "suncae", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "suncae", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.57.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "dev": true, + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f9672cc --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "suncae", + "version": "1.0.0", + "description": "![](doc/logo.svg)", + "main": "index.js", + "directories": { + "doc": "doc" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/seamplex/suncae.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/seamplex/suncae/issues" + }, + "homepage": "https://github.com/seamplex/suncae#readme", + "devDependencies": { + "@playwright/test": "^1.57.0" + } +} diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..a2327e3 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,35 @@ +// @ts-check +const { defineConfig, devices } = require('@playwright/test'); + +module.exports = defineConfig({ + testDir: './tests/e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:8000', + trace: 'on-first-retry', + video: 'retain-on-failure', + screenshot: 'only-on-failure', + // Add HAR recording to capture all network traffic + recordHar: { + path: 'test-results/network.har', + content: 'attach' // Include response bodies + }, + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'php -S localhost:8000 -t html', + url: 'http://localhost:8000', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/renderers/x3dom/deps.sh b/renderers/x3dom/deps.sh index 826ec60..c6fef5c 100644 --- a/renderers/x3dom/deps.sh +++ b/renderers/x3dom/deps.sh @@ -14,13 +14,13 @@ if [ $force = 1 ] || [ ! -e renderers/x3dom/x3dom.js ]; then # fi # if [ ! -d x3dom ]; then # mkdir -p x3dom -# unzip ${x3dom_tarball}.zip -d x3dom +# unzip -q ${x3dom_tarball}.zip -d x3dom # fi # cp x3dom/x3dom.js ../renderers/x3dom # cp x3dom/x3dom.css ../renderers/x3dom - wget -c https://andreasplesch.github.io/x3dom/dist/x3dom.js -o ../renderers/x3dom/x3dom.js - wget -c https://andreasplesch.github.io/x3dom/dist/x3dom.css -o ../renderers/x3dom/x3dom.css + wget -q -c https://andreasplesch.github.io/x3dom/dist/x3dom.js -O ../renderers/x3dom/x3dom.js + wget -q -c https://andreasplesch.github.io/x3dom/dist/x3dom.css -O ../renderers/x3dom/x3dom.css cd ../uxs/faster-than-quick/js if [ ! -e x3dom.js ]; then diff --git a/solvers/ccx/deps.sh b/solvers/ccx/deps.sh index d4a0acc..33d23b7 100644 --- a/solvers/ccx/deps.sh +++ b/solvers/ccx/deps.sh @@ -10,7 +10,7 @@ ccx_tarball=ccx-${ccx_version}-linux-static if [ $force = 1 ] || [ ! -x bin/ccx ] || [ ! -f deps/${ccx_tarball}.tar.gz ]; then cd deps if [ ! -e ${ccx_tarball}.tar.gz ]; then - wget -c https://www.seamplex.com/suncae/deps/${ccx_tarball}.tar.gz + wget -q -c https://www.seamplex.com/suncae/deps/${ccx_tarball}.tar.gz fi tar xzf ${ccx_tarball}.tar.gz cp ${ccx_tarball}/ccx ../bin diff --git a/solvers/feenox/deps.sh b/solvers/feenox/deps.sh index f235db6..97f8a03 100644 --- a/solvers/feenox/deps.sh +++ b/solvers/feenox/deps.sh @@ -1,6 +1,6 @@ #!/bin/false -feenox_version=1.2 +feenox_version=1.2.1 feenox_version_min=1.72 # feenox @@ -16,14 +16,14 @@ echo -n "meshers/feenox... " use_system_binary=0 if [ -x "$(which feenox 2>/dev/null)" ] && [ $force = 0 ]; then installed_version=$(get_feenox_version "$(which feenox)") - if [ -n "$installed_version" ] && version_ge "$installed_version" "$feenox_min_version"; then - echo "found system version $installed_version (>= $feenox_min_version), using it" + if [ -n "$installed_version" ] && version_ge "$installed_version" "$feenox_version_min"; then + echo "found system version $installed_version (>= $feenox_version_min), using it" use_system_binary=1 # Create symlink to system binary mkdir -p bin ln -sf "$(which feenox)" bin/feenox else - echo "system version $installed_version is too old (need >= $feenox_min_version), will download" + echo "system version $installed_version is too old (need >= $feenox_version_min), will download" fi fi @@ -32,7 +32,7 @@ if [ $use_system_binary = 0 ]; then if [ $force = 1 ] || [ ! -x bin/feenox ] || [ ! -f deps/${feenox_tarball}.tgz ]; then cd deps if [ ! -e ${feenox_tarball}.tar.gz ]; then - wget -c https://www.seamplex.com/feenox/dist/linux/${feenox_tarball}.tar.gz + wget -q -c https://www.seamplex.com/feenox/dist/linux/${feenox_tarball}.tar.gz fi tar xzf ${feenox_tarball}.tar.gz cp ${feenox_tarball}/bin/feenox ../bin diff --git a/tests/e2e/create_case.spec.js b/tests/e2e/create_case.spec.js new file mode 100644 index 0000000..32211ba --- /dev/null +++ b/tests/e2e/create_case.spec.js @@ -0,0 +1,97 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); +const path = require('path'); + +test('create new case from sample.step', async ({ page }) => { + // Add network request/response logging + page.on('request', request => { + console.log('>>', request.method(), request.url()); + }); + + page.on('response', async response => { + const url = response.url(); + console.log('<<', response.status(), url); + + // Log responses from PHP scripts involved in CAD processing + if (url.includes('import_cad.php') || url.includes('process.php')) { + try { + const body = await response.text(); + console.log('Response body:', body); + } catch (e) { + console.log('Could not read response body:', e.message); + } + } + }); + + // Add browser console logging + page.on('console', msg => { + console.log('BROWSER:', msg.type(), msg.text()); + }); + + // 1. Go to /new + await page.goto('/new/'); + + // 2. In that view, there's a file upload input box. Put the file html/new/sample.step in the upload box + // Note: path is relative to the test file. + // tests/e2e/create_case.spec.js -> ../../html/new/sample.step + const sampleFile = path.join(__dirname, '../../html/new/sample.step'); + + // Wait for the import response + const importResponsePromise = page.waitForResponse(response => + response.url().includes('import_cad.php') && response.status() === 200, + { timeout: 30000 } + ); + + await page.setInputFiles('#cad', sampleFile); + + await importResponsePromise; + + // Wait for the process response + const processResponse = await page.waitForResponse(response => + response.url().includes('process.php') && response.status() === 200, + { timeout: 30000 } + ); + + // Wait for the file to be processed and preview to be shown + try { + await expect(page.locator('#cad_preview')).toBeVisible({ timeout: 30000 }); + } catch (error) { + // Check if there's an error message displayed + const errorDiv = page.locator('#cad_error'); + if (await errorDiv.isVisible()) { + const errorText = await errorDiv.textContent(); + console.log('CAD Error displayed:', errorText); + } + + // Take a screenshot for debugging + await page.screenshot({ path: 'test-results/upload-failure.png', fullPage: true }); + + throw error; + } + await page.setInputFiles('#cad', sampleFile); + + // Wait for the file to be processed and preview to be shown + await expect(page.locator('#cad_preview')).toBeVisible({ timeout: 30000 }); + + // 3. Select "Solid mechanics" in the combo box for physics + await page.selectOption('#physics', 'solid'); + + // 4. Select "Mechanical elasticty" in the combo box for problem + await page.selectOption('#problem', 'mechanical'); + + // 5. Select "FeenoX" in the combo box for solver + await page.selectOption('#solver', 'feenox'); + + // 6. Select "Gmsh" in the combo box for mesher + // 6. Select "Gmsh" in thecombo box for mesher + await page.selectOption('#mesher', 'gmsh'); + + // Verify start button is enabled before clicking + await expect(page.locator('#btn_start')).toBeEnabled(); + + // 7. Click "Start" + await page.click('#btn_start'); + + // Optional: Verify we moved to the next step (e.g., URL changes to create.php or creating a case) + // For now, just ensuring no error occurs immediately after click. +}); diff --git a/tests/e2e/example.spec.js b/tests/e2e/example.spec.js new file mode 100644 index 0000000..f36a88d --- /dev/null +++ b/tests/e2e/example.spec.js @@ -0,0 +1,9 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +test('homepage has title', async ({ page }) => { + await page.goto('/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Faster-than-quick/); +}); diff --git a/uxs/faster-than-quick/about.php b/uxs/faster-than-quick/about.php index 7f3134d..be6f335 100644 --- a/uxs/faster-than-quick/about.php +++ b/uxs/faster-than-quick/about.php @@ -18,16 +18,15 @@
Versions
"; -echo "Gmsh ".`../bin/gmsh -info | head -n1`; +// get versions from a txt file? ?> +To be done.
License
GNU Affero General Public License version 3, or at your option, any later version. You can get a copy of the source code of this web interface here. - +