Test tooling: parallel "Run ALL tests" + fix flaky Playwright card/board tests

rebuild-wekan.sh/.bat run all test jobs in parallel against one dev server
(menu option 9) plus a new "all browsers in parallel" option (16), with
per-job logs and a PASS/FAIL summary. Mocha runs in its own .meteor/local-test
build dir; .gitignore ignores the new build dirs.

Fix three load-induced flaky failures surfaced by the parallel run:
- 03-cards-operations "move does not create duplicate cards": snapshot the
  card titles only after the move settles (wait for the card in the target
  list / gone from the source list) instead of mid-flight.
- 03-cards-operations "add-to-bottom places the card last": poll until the
  reactive re-sort places the new card last, since submitNewCard only waits
  for the card to exist.
- helpers/auth.js openBoard: 5 attempts x 20s so the slowest browser (WebKit)
  survives contention from the 3-browser parallel run against one server.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lauri OjansivuandClaude Opus 4.8 committed 2026-06-16 21:41:06 +03:00
1 parent f4111cb064
commit 19fe2e2b6f
5 files changed
+314 -99

No files matched your search

+6
View File
@@ -32,6 +32,7 @@ Thumbs.db
ehthumbs.db
.eslintcache
.meteor/local
.meteor/local-test
.devcontainer/docker-compose.extend.yml
.devcontainer/volumes*/
.coverage
@@ -64,3 +65,8 @@ test-results/
# Tools like mongosh etc so that they work inside Flatpak sandbox
.tools/
# Meteor custom local directory (METEOR_LOCAL_DIR)
_build-local-test
*/build-assets-local-test
*/build-chunks-local-test
+93 -55
View File
@@ -48,9 +48,10 @@ echo 12^) Test Node E2E regressions ^(tests\e2e\list-regressions.js, needs runn
echo 13^) Test Playwright Chromium
echo 14^) Test Playwright Firefox
echo 15^) Test Playwright Webkit
echo 16^) Check floating promises guard ^(@typescript-eslint/no-floating-promises + auth await scan^)
echo 17^) Save Meteor dependency chain to ..\meteor-deps.txt
echo 18^) Quit
echo 16^) Test Playwright ALL browsers in parallel ^(Chromium + Firefox + WebKit^), server already running on :3000
echo 17^) Check floating promises guard ^(@typescript-eslint/no-floating-promises + auth await scan^)
echo 18^) Save Meteor dependency chain to ..\meteor-deps.txt
echo 19^) Quit
echo ==========================================================
set "choice="
set /p "choice=Please enter your choice: "
@@ -70,9 +71,10 @@ if "%choice%"=="12" goto test_e2e
if "%choice%"=="13" goto test_pw_chromium
if "%choice%"=="14" goto test_pw_firefox
if "%choice%"=="15" goto test_pw_webkit
if "%choice%"=="16" goto check_floating
if "%choice%"=="17" goto save_deps
if "%choice%"=="18" goto end
if "%choice%"=="16" goto test_pw_parallel
if "%choice%"=="17" goto check_floating
if "%choice%"=="18" goto save_deps
if "%choice%"=="19" goto end
echo invalid option
goto menu
@@ -162,33 +164,30 @@ goto end
REM ===========================================================================
:test_all
echo Running ALL tests: import regression + Mocha (server-side) + Node E2E + Playwright (Chromium, Firefox, WebKit).
echo Running ALL tests against ONE WeKan server on http://localhost:3000 - all jobs in PARALLEL with live progress.
echo Mocha uses its own build dir (.meteor\local-test) so it runs at the same time as the :3000 server, which keeps .meteor\local.
curl -fsS http://127.0.0.1:3000 >nul 2>&1
if not errorlevel 1 (
echo ERROR: Port 3000 is already in use. Stop any running dev server before running this option.
goto end
)
set "TOTAL=5"
set "FAILED=0"
set "R_IMPORT=" & set "R_MOCHA=" & set "R_SERVER=" & set "R_E2E="
set "R_CHROMIUM=" & set "R_FIREFOX=" & set "R_WEBKIT="
set "S_mocha=RUN" & set "S_import=RUN" & set "S_e2e=RUN" & set "S_browsers=RUN"
REM Clear completion flags from any previous run.
del /q ".done-mocha" ".done-import" ".done-e2e" ".done-browsers" 2>nul
REM Mocha and the import regression do not need the :3000 server, so start
REM them now (each in its own minimized window; /D sets the working dir so all
REM paths are relative and space-safe). Each writes a log and, on exit, its
REM return code to .done-<job>, which the poll loop below watches.
echo.
echo ==^> [1/%TOTAL%] Import regression (node, no server)
call node tests\wekanCreator.import.test.js
if errorlevel 1 ( set "R_IMPORT=FAIL" & set "FAILED=1" ) else ( set "R_IMPORT=PASS" )
echo.
echo ==^> [2/%TOTAL%] Mocha unit + security + API-logic tests (server-side, meteor test, port 3100)
call meteor test --once --driver-package meteortesting:mocha --port 3100
if errorlevel 1 ( set "R_MOCHA=FAIL" & set "FAILED=1" ) else ( set "R_MOCHA=PASS" )
echo.
echo ==^> [3/%TOTAL%] Starting WeKan server on http://localhost:3000 (WITH_API=true)
REM Set the dev env in this shell; the started child process inherits it,
REM which avoids fragile nested quotes inside cmd /c.
echo ==^> Starting Mocha (separate .meteor\local-test build, port 3100) and import regression in parallel.
call :set_dev_env
start "Wekan mocha" /MIN /D "%REPO%" cmd /c "set METEOR_LOCAL_DIR=.meteor\local-test&& call meteor test --once --driver-package meteortesting:mocha --port 3100 1>..\wekan-alltests-mocha.log 2>&1 & if errorlevel 1 (echo FAIL>.done-mocha) else (echo PASS>.done-mocha)"
start "Wekan import" /MIN /D "%REPO%" cmd /c "call node tests\wekanCreator.import.test.js 1>..\wekan-alltests-import.log 2>&1 & if errorlevel 1 (echo FAIL>.done-import) else (echo PASS>.done-import)"
echo ==^> Starting the single WeKan server on http://localhost:3000 (WITH_API=true, .meteor\local)
set "ROOT_URL=http://localhost:3000"
start "WekanTestServer" /MIN /D "%REPO%" cmd /c "meteor run --port 3000 1>..\wekan-test-server.log 2>&1"
@@ -206,49 +205,52 @@ echo.
if "!SERVER_READY!"=="0" (
echo FAIL: server did not become ready on http://localhost:3000 ^(see ..\wekan-test-server.log^)
set "R_SERVER=FAIL" & set "FAILED=1"
set "R_E2E=SKIP"
set "R_CHROMIUM=SKIP" & set "R_FIREFOX=SKIP" & set "R_WEBKIT=SKIP"
set "S_e2e=SKIP" & set "S_browsers=SKIP" & set "FAILED=1"
) else (
set "R_SERVER=PASS"
echo.
echo ==^> [4/%TOTAL%] Node E2E regressions ^(tests\e2e\list-regressions.js^)
call meteor npm run test:e2e
if errorlevel 1 ( set "R_E2E=FAIL" & set "FAILED=1" ) else ( set "R_E2E=PASS" )
echo ==^> Server is up: starting Node E2E and Playwright ^(Chromium + Firefox + WebKit, --workers=3^) in parallel.
start "Wekan e2e" /MIN /D "%REPO%" cmd /c "call meteor npm run test:e2e 1>..\wekan-alltests-e2e.log 2>&1 & if errorlevel 1 (echo FAIL>.done-e2e) else (echo PASS>.done-e2e)"
start "Wekan browsers" /MIN /D "%REPO%\tests\playwright" cmd /c "set WEKAN_PLAYWRIGHT_ALL=1&& call meteor npm exec playwright test -- --project=chromium --project=firefox --project=webkit --workers=3 --reporter=list 1>..\..\wekan-alltests-browsers.log 2>&1 & if errorlevel 1 (echo FAIL>..\..\.done-browsers) else (echo PASS>..\..\.done-browsers)"
)
echo.
echo ==^> [5/%TOTAL%] Playwright browser specs ^(Chromium + Firefox + WebKit^)
echo.
echo -- Playwright Chromium --
call :run_pw_all chromium
if errorlevel 1 ( set "R_CHROMIUM=FAIL" & set "FAILED=1" ) else ( set "R_CHROMIUM=PASS" )
echo.
echo -- Playwright Firefox --
call :run_pw_all firefox
if errorlevel 1 ( set "R_FIREFOX=FAIL" & set "FAILED=1" ) else ( set "R_FIREFOX=PASS" )
echo.
echo -- Playwright WebKit --
call :run_pw_all webkit
if errorlevel 1 ( set "R_WEBKIT=FAIL" & set "FAILED=1" ) else ( set "R_WEBKIT=PASS" )
REM Live progress: re-print a status line every ~3s until all expected jobs
REM have written their .done flag (cmd has no native wait).
echo Live progress (refreshes every few seconds) - RUN / PASS / FAIL per job:
:wait_all
call :jstate mocha
call :jstate import
if "!SERVER_READY!"=="1" ( call :jstate e2e & call :jstate browsers )
echo mocha=!S_mocha! import=!S_import! e2e=!S_e2e! browsers=!S_browsers!
set "ALLDONE=1"
if not exist ".done-mocha" set "ALLDONE=0"
if not exist ".done-import" set "ALLDONE=0"
if "!SERVER_READY!"=="1" (
if not exist ".done-e2e" set "ALLDONE=0"
if not exist ".done-browsers" set "ALLDONE=0"
)
if "!ALLDONE!"=="0" (
ping -n 4 127.0.0.1 >nul
goto wait_all
)
echo.
echo Stopping WeKan test server.
taskkill /FI "WINDOWTITLE eq WekanTestServer*" /T /F >nul 2>&1
REM Final pass/fail per job (RUN means it never wrote a flag = treat as FAIL).
if "!S_mocha!"=="FAIL" set "FAILED=1"
if "!S_import!"=="FAIL" set "FAILED=1"
if "!S_e2e!"=="FAIL" set "FAILED=1"
if "!S_browsers!"=="FAIL" set "FAILED=1"
echo.
echo ==================== TEST SUMMARY ====================
call :report "!R_IMPORT!" "Import regression"
call :report "!R_MOCHA!" "Mocha (server-side)"
call :report "!R_SERVER!" "Server startup"
call :report "!R_E2E!" "Node E2E regressions"
call :report "!R_CHROMIUM!" "Playwright Chromium"
call :report "!R_FIREFOX!" "Playwright Firefox"
call :report "!R_WEBKIT!" "Playwright WebKit"
call :report "!S_mocha!" "Mocha (server-side)"
call :report "!S_import!" "Import regression"
if "!SERVER_READY!"=="1" ( call :report "PASS" "Server startup" ) else ( call :report "FAIL" "Server startup" )
call :report "!S_e2e!" "Node E2E regressions"
call :report "!S_browsers!" "Playwright (Chromium+Firefox+WebKit)"
echo =====================================================
echo (per-job logs: ..\wekan-alltests-^<mocha^|import^|e2e^|browsers^>.log ; server: ..\wekan-test-server.log)
if "!FAILED!"=="0" ( echo RESULT: All tests passed. ) else ( echo RESULT: Some tests FAILED ^(see details above^). )
goto end
@@ -297,6 +299,35 @@ if /i "%INSTALL_DEPS%"=="y" (
call meteor npm exec playwright test -- --project=%PW_PROJECT%
goto end
REM ===========================================================================
:test_pw_parallel
echo Running Chromium, Firefox and WebKit Playwright suites at the same time.
echo NOTE: needs a WeKan server already running on http://localhost:3000 (menu option 3).
curl -fsS http://127.0.0.1:3000/sign-in >nul 2>&1
if errorlevel 1 (
echo ERROR: WeKan does not appear to be running on http://localhost:3000.
echo Start it first with menu option 3, then re-run this option.
goto end
)
set "INSTALL_DEPS="
set /p "INSTALL_DEPS=Install Playwright test dependencies and browsers first? [y/N] "
if /i "%INSTALL_DEPS%"=="y" (
pushd "%REPO%\tests\playwright"
call meteor npm install
call meteor npm exec playwright install
popd
)
REM All three browsers run natively on Windows, so a single Playwright run with
REM 3 workers executes Chromium, Firefox and WebKit tests concurrently (no
REM Docker, no background windows). WEKAN_PLAYWRIGHT_ALL=1 enables all projects.
cd /d "%REPO%\tests\playwright"
set "WEKAN_PLAYWRIGHT_ALL=1"
call meteor npm exec playwright test -- --project=chromium --project=firefox --project=webkit --workers=3 --reporter=list
if errorlevel 1 ( echo RESULT: Some Playwright browsers FAILED ^(see details above^). ) else ( echo RESULT: All Playwright browsers passed. )
goto end
REM ===========================================================================
:check_floating
echo Ensuring missing ESLint dependencies for the no-floating-promises rule.
@@ -369,6 +400,13 @@ if "%ST%"=="" set "ST=----"
echo %ST% %~2
exit /b 0
:jstate
REM %1 = job key. Sets S_<key> to RUN / PASS / FAIL from .done-<key>.
REM Called repeatedly by the :test_all poll loop and once more for the summary.
if not exist ".done-%1" ( set "S_%1=RUN" & exit /b 0 )
set /p "S_%1="<".done-%1"
exit /b 0
REM ===========================================================================
:end
ENDLOCAL
+197 -40
View File
@@ -76,7 +76,13 @@ function run_playwright_webkit_docker(){
echo "Expecting WeKan at ${WEKAN_BASE_URL:-http://127.0.0.1:3000} (container uses --network host)."
# Mount the whole repo so specs that reach the repo-root node_modules
# (e.g. @wekanteam/exceljs) and .tools resolve; run from tests/playwright.
# Run as the host user (--user) with a writable HOME so the container does
# not leave root-owned files under test-results/ (which would later make
# native Chromium/Firefox runs fail with "EACCES: permission denied, mkdir
# .../test-results/.playwright-artifacts-N").
docker run --rm --init --ipc=host --network host \
--user "$(id -u):$(id -g)" \
-e HOME=/tmp \
-e WEKAN_BASE_URL="${WEKAN_BASE_URL:-http://127.0.0.1:3000}" \
-e WEKAN_MONGO_URL="${WEKAN_MONGO_URL:-mongodb://127.0.0.1:3001/meteor}" \
-e WEKAN_PLAYWRIGHT_ALL=1 \
@@ -88,6 +94,19 @@ function run_playwright_webkit_docker(){
sh -c 'export PATH=/repo/tests/playwright/node_modules/.bin:$PATH; exec npx playwright test --project=webkit "$@"' sh "$@"
}
# Reset test-results/ ownership when an older WebKit-in-Docker run left it owned
# by root. Removal needs write permission on test-results/ itself (which the
# host user lacks when it is root-owned), so this needs sudo; it is a no-op when
# the directory is already writable or absent.
function ensure_test_results_writable(){
local pwdir="$ORIG_HOME/repos/wekan/tests/playwright"
[ -e "$pwdir/test-results" ] || return 0
[ -w "$pwdir/test-results" ] && return 0
echo "Fixing test-results/ ownership (left as root by an earlier Docker WebKit run)."
sudo chown -R "$(id -u):$(id -g)" "$pwdir/test-results" 2>/dev/null || \
sudo rm -rf "$pwdir/test-results" 2>/dev/null || true
}
# Print "<n> passed, <n> failed, ..." for a Playwright JSON report file.
function pw_stats_of(){
[ -f "$1" ] || return 0
@@ -107,9 +126,14 @@ function run_pw_all_browser(){
local browser="$1"
local pwdir="$ORIG_HOME/repos/wekan/tests/playwright"
local json="test-results/all-tests-${browser}.json"
# Per-browser output dir so suites can run in parallel without their
# artifacts (.playwright-artifacts-N, screenshots, traces) colliding or
# wiping each other when Playwright clears its output dir at startup.
local outdir="test-results/${browser}"
ensure_test_results_writable
rm -f "$pwdir/$json"
if [ "$browser" = "webkit" ] && webkit_needs_docker; then
( cd "$pwdir" && export PLAYWRIGHT_JSON_OUTPUT_NAME="$json" && run_playwright_webkit_docker --reporter=list,json )
( cd "$pwdir" && export PLAYWRIGHT_JSON_OUTPUT_NAME="$json" && run_playwright_webkit_docker --output="$outdir" --reporter=list,json )
return $?
fi
(
@@ -120,11 +144,86 @@ function run_pw_all_browser(){
export PLAYWRIGHT_BROWSERS_PATH="$ORIG_HOME/.var/app/com.visualstudio.code/cache/ms-playwright"
export WEKAN_PLAYWRIGHT_ALL=1
export PLAYWRIGHT_JSON_OUTPUT_NAME="$json"
PLAYWRIGHT_HTML_OPEN=never meteor npm exec playwright test -- --project="$browser" --reporter=list,json
PLAYWRIGHT_HTML_OPEN=never meteor npm exec playwright test -- --project="$browser" --output="$outdir" --reporter=list,json
)
return $?
}
# Run Chromium, Firefox and WebKit suites concurrently against a WeKan server
# that is already running on http://localhost:3000 (menu option 3). Each suite
# streams to ../wekan-playwright-<browser>.log; once all finish we print each
# log followed by a per-browser PASS/FAIL summary. Tests seed their own random
# users/boards and clean up by id, so running the three browsers at once is safe.
function run_playwright_parallel(){
ORIG_HOME="$HOME"
local pwdir="$ORIG_HOME/repos/wekan/tests/playwright"
ensure_test_results_writable
if ! curl -fsS http://127.0.0.1:3000/sign-in >/dev/null 2>&1; then
echo "ERROR: WeKan does not appear to be running on http://localhost:3000."
echo " Start it first with menu option 3, then re-run this option."
return 1
fi
read -p "Install Playwright test dependencies first? [y/N] " INSTALL_DEPS
case "$INSTALL_DEPS" in [Yy]*) ( cd "$pwdir" && meteor npm install ) ;; esac
echo "Running Chromium, Firefox and WebKit Playwright suites at the same time."
echo "Live output goes to ../wekan-playwright-<browser>.log; a summary prints when all finish."
# bash 3.2 (macOS) has no associative arrays, so track the three PIDs in
# plain variables instead of a map.
( run_pw_all_browser chromium ) > ../wekan-playwright-chromium.log 2>&1 &
local pid_chromium=$!
( run_pw_all_browser firefox ) > ../wekan-playwright-firefox.log 2>&1 &
local pid_firefox=$!
( run_pw_all_browser webkit ) > ../wekan-playwright-webkit.log 2>&1 &
local pid_webkit=$!
echo " chromium pid $pid_chromium, firefox pid $pid_firefox, webkit pid $pid_webkit"
local rc_chromium rc_firefox rc_webkit
wait "$pid_chromium"; rc_chromium=$?
wait "$pid_firefox"; rc_firefox=$?
wait "$pid_webkit"; rc_webkit=$?
local PW_FAILURES=""
SUMMARY=()
record() { SUMMARY+=("$1|$2|${3:-}"); }
for entry in "chromium:Chromium:$rc_chromium" "firefox:Firefox:$rc_firefox" "webkit:WebKit:$rc_webkit"; do
browser="${entry%%:*}"; rest="${entry#*:}"; label="${rest%%:*}"; rc="${rest#*:}"
echo
echo "==================== Playwright $label output ===================="
cat "../wekan-playwright-${browser}.log" 2>/dev/null || true
local json="$pwdir/test-results/all-tests-${browser}.json"
local stats; stats="$(pw_stats_of "$json")"
if [ "$rc" -eq 0 ]; then record PASS "Playwright $label" "$stats"; else record FAIL "Playwright $label" "$stats"; fi
local fails; fails="$(pw_failures_of "$json" "$label")"
[ -n "$fails" ] && PW_FAILURES="${PW_FAILURES}${fails}"$'\n'
done
echo
echo "==================== PLAYWRIGHT SUMMARY ===================="
local FAILED=0
for line in "${SUMMARY[@]}"; do
status="${line%%|*}"; rest="${line#*|}"; name="${rest%%|*}"; stats="${rest#*|}"
suffix=""; [ -n "$stats" ] && suffix=" ($stats)"
printf ' %-6s %s%s\n' "$status" "$name" "$suffix"
[ "$status" = "FAIL" ] && FAILED=1
done
echo "==========================================================="
if [ -n "$PW_FAILURES" ]; then
echo
echo "Failing Playwright tests:"
while IFS= read -r f; do
[ -n "$f" ] && printf ' FAIL %s\n' "$f"
done <<< "$PW_FAILURES"
echo "(full per-browser output above; HTML report in tests/playwright/playwright-report)"
echo "==========================================================="
fi
if [ "$FAILED" -eq 0 ]; then echo "RESULT: All Playwright browsers passed."; else echo "RESULT: Some Playwright browsers FAILED (see details above)."; fi
return $FAILED
}
# Run one Playwright browser project interactively (single-browser menu items).
function run_playwright_single(){
local browser="$1"
@@ -148,7 +247,7 @@ function run_playwright_single(){
echo
PS3='Please enter your choice: '
options=("Install WeKan dependencies" "Build WeKan" "Run Meteor for dev on http://localhost:3000" "Run Meteor for dev on http://localhost:3000 with trace warnings, and warnings using old Meteor API that will not exist in Meteor 3.0" "Run Meteor for dev on http://localhost:3000 with bundle visualizer" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:3000" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:3000 with MONGO_URL=mongodb://127.0.0.1:27019/wekan" "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT" "Run ALL tests on http://localhost:3000 (start server, progress + summary)" "Test Mocha unit + security + API-logic tests (server-side only, no browser)" "Test import regression (tests/wekanCreator.import.test.js, fast, no server)" "Test Node E2E regressions (tests/e2e/list-regressions.js, needs running server)" "Test Playwright Chromium" "Test Playwright Firefox" "Test Playwright Webkit" "Check floating promises guard (@typescript-eslint/no-floating-promises + auth await scan)" "Save Meteor dependency chain to ../meteor-deps.txt" "Quit")
options=("Install WeKan dependencies" "Build WeKan" "Run Meteor for dev on http://localhost:3000" "Run Meteor for dev on http://localhost:3000 with trace warnings, and warnings using old Meteor API that will not exist in Meteor 3.0" "Run Meteor for dev on http://localhost:3000 with bundle visualizer" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:3000" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:3000 with MONGO_URL=mongodb://127.0.0.1:27019/wekan" "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT" "Run ALL tests on http://localhost:3000 (start server, progress + summary)" "Test Mocha unit + security + API-logic tests (server-side only, no browser)" "Test import regression (tests/wekanCreator.import.test.js, fast, no server)" "Test Node E2E regressions (tests/e2e/list-regressions.js, needs running server)" "Test Playwright Chromium" "Test Playwright Firefox" "Test Playwright Webkit" "Test Playwright ALL browsers in parallel (Chromium + Firefox + WebKit), server already running on :3000" "Check floating promises guard (@typescript-eslint/no-floating-promises + auth await scan)" "Save Meteor dependency chain to ../meteor-deps.txt" "Quit")
select opt in "${options[@]}"
do
@@ -315,29 +414,54 @@ do
;;
"Run ALL tests on http://localhost:3000 (start server, progress + summary)")
echo "Running ALL tests: import regression + Mocha (server-side) + Node E2E + Playwright (Chromium, Firefox, WebKit)."
echo "Running ALL tests against ONE WeKan server on http://localhost:3000 - all jobs in PARALLEL with live progress."
echo "Mocha gets its own build dir (.meteor/local-test) so it runs at the same time as the :3000 server, which keeps .meteor/local."
SUMMARY=()
record() { SUMMARY+=("$1|$2|${3:-}"); }
label_of() { case "$1" in
mocha) echo "Mocha (server-side)" ;;
import) echo "Import regression" ;;
e2e) echo "Node E2E regressions" ;;
chromium) echo "Playwright Chromium" ;;
firefox) echo "Playwright Firefox" ;;
webkit) echo "Playwright WebKit" ;;
esac; }
ORIG_HOME="$HOME"
PW_FAILURES=""
TEST_SERVER_PID=""
STATDIR="$(mktemp -d)"
BPIDS=""
# Launch one test job in the background: run it, record its exit code in
# STATDIR/<key>, and send all of its output to ../wekan-alltests-<key>.log.
launch_job() {
local k="$1"
(
rc=0
case "$k" in
mocha) METEOR_LOCAL_DIR=.meteor/local-test meteor test --once --driver-package meteortesting:mocha --port 3100 || rc=$? ;;
import) node tests/wekanCreator.import.test.js || rc=$? ;;
e2e) meteor npm run test:e2e || rc=$? ;;
*) run_pw_all_browser "$k" || rc=$? ;;
esac
echo "$rc" > "$STATDIR/$k"
) > "../wekan-alltests-$k.log" 2>&1 &
BPIDS="$BPIDS $!"
}
if curl -fsS http://127.0.0.1:3000 >/dev/null 2>&1; then
echo "ERROR: Port 3000 is already in use. Stop any running dev server before running this option."
rm -rf "$STATDIR"
break
fi
TOTAL=5
TEST_SERVER_PID=""
# Mocha and the import regression do not need the :3000 server, so start them
# now; they run while the server boots and while the browsers run.
echo
echo "==> [1/$TOTAL] Import regression (node, no server)"
if node tests/wekanCreator.import.test.js; then record PASS "Import regression"; else record FAIL "Import regression"; fi
echo "==> Starting Mocha (separate .meteor/local-test build, port 3100) and import regression in parallel."
launch_job mocha
launch_job import
echo
echo "==> [2/$TOTAL] Mocha unit + security + API-logic tests (server-side, meteor test, port 3100)"
if meteor test --once --driver-package meteortesting:mocha --port 3100; then record PASS "Mocha (server-side)"; else record FAIL "Mocha (server-side)"; fi
echo
echo "==> [3/$TOTAL] Starting WeKan server on http://localhost:3000 (WITH_API=true)"
echo "==> Starting the single WeKan server on http://localhost:3000 (WITH_API=true, .meteor/local)"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 > ../wekan-test-server.log 2>&1 &
TEST_SERVER_PID=$!
SERVER_READY=0
@@ -346,6 +470,7 @@ do
printf '.'; sleep 1
done
echo
if [ "$SERVER_READY" -ne 1 ]; then
echo "FAIL: server did not become ready on http://localhost:3000 (see ../wekan-test-server.log)"
record FAIL "Server startup"
@@ -353,33 +478,60 @@ do
record SKIP "Playwright Chromium"
record SKIP "Playwright Firefox"
record SKIP "Playwright WebKit"
ALLKEYS="mocha import"
else
record PASS "Server startup"
echo
echo "==> [4/$TOTAL] Node E2E regressions (tests/e2e/list-regressions.js)"
if meteor npm run test:e2e; then record PASS "Node E2E regressions"; else record FAIL "Node E2E regressions"; fi
echo
echo "==> [5/$TOTAL] Playwright browser specs (Chromium + Firefox + WebKit)"
PW_FAILURES=""
# entry = browser:Label (no bash 4 ${var^} so this works on macOS bash 3.2)
for entry in "chromium:Chromium" "firefox:Firefox" "webkit:WebKit"; do
browser="${entry%%:*}"; label="${entry#*:}"
echo
echo "-- Playwright $label --"
if run_pw_all_browser "$browser"; then pw_ok=1; else pw_ok=0; fi
json="$ORIG_HOME/repos/wekan/tests/playwright/test-results/all-tests-${browser}.json"
stats="$(pw_stats_of "$json")"
if [ "$pw_ok" -eq 1 ]; then
record PASS "Playwright $label" "$stats"
else
record FAIL "Playwright $label" "$stats"
fi
fails="$(pw_failures_of "$json" "$label")"
[ -n "$fails" ] && PW_FAILURES="${PW_FAILURES}${fails}"$'\n'
done
# Server is up: add the server-facing jobs to the running set.
launch_job e2e
launch_job chromium
launch_job firefox
launch_job webkit
ALLKEYS="mocha import e2e chromium firefox webkit"
fi
# Live combined progress: one refreshing line per running job until all end.
BN="$(set -- $ALLKEYS; echo $#)"
echo "Live progress (refreshes every second) - [RUN]/[PASS]/[FAIL], checks passed / x failed:"
for k in $ALLKEYS; do echo; done
while :; do
printf '\033[%dA' "$BN"
alldone=1
for k in $ALLKEYS; do
log="../wekan-alltests-$k.log"
ok=$(grep -c $'\xe2\x9c\x93' "$log" 2>/dev/null); ok=${ok:-0}
bad=$(grep -c $'\xe2\x9c\x98' "$log" 2>/dev/null); bad=${bad:-0}
if [ -f "$STATDIR/$k" ]; then
rc=$(cat "$STATDIR/$k" 2>/dev/null)
if [ "${rc:-1}" = "0" ]; then st="PASS"; else st="FAIL"; fi
else
st="RUN "; alldone=0
fi
printf '\033[K [%-4s] %-22s ok:%s fail:%s\n' "$st" "$(label_of "$k")" "$ok" "$bad"
done
[ "$alldone" -eq 1 ] && break
sleep 1
done
for p in $BPIDS; do wait "$p" 2>/dev/null || true; done
# Roll the parallel results into the summary (browsers carry pass/fail stats).
for k in $ALLKEYS; do
rc=$(cat "$STATDIR/$k" 2>/dev/null); rc=${rc:-1}
label="$(label_of "$k")"
case "$k" in
chromium|firefox|webkit)
json="$ORIG_HOME/repos/wekan/tests/playwright/test-results/all-tests-${k}.json"
stats="$(pw_stats_of "$json")"
if [ "$rc" = "0" ]; then record PASS "$label" "$stats"; else record FAIL "$label" "$stats"; fi
fails="$(pw_failures_of "$json" "$label")"
[ -n "$fails" ] && PW_FAILURES="${PW_FAILURES}${fails}"$'\n'
;;
*)
if [ "$rc" = "0" ]; then record PASS "$label"; else record FAIL "$label"; fi
;;
esac
done
rm -rf "$STATDIR"
if [ -n "$TEST_SERVER_PID" ]; then
echo
echo "Stopping WeKan test server."
@@ -398,14 +550,14 @@ do
[ "$status" = "FAIL" ] && FAILED=1
done
echo "====================================================="
# Per-test details for any failing Playwright specs.
echo "(per-job logs: ../wekan-alltests-<mocha|import|e2e|chromium|firefox|webkit>.log ; server: ../wekan-test-server.log)"
if [ -n "$PW_FAILURES" ]; then
echo
echo "Failing Playwright tests:"
while IFS= read -r f; do
[ -n "$f" ] && printf ' FAIL %s\n' "$f"
done <<< "$PW_FAILURES"
echo "(full output and traces above; HTML report in tests/playwright/playwright-report)"
echo "(full output and traces in the per-browser logs; HTML report in tests/playwright/playwright-report)"
echo "====================================================="
fi
if [ "$FAILED" -eq 0 ]; then echo "RESULT: All tests passed."; else echo "RESULT: Some tests FAILED (see details above)."; fi
@@ -448,6 +600,11 @@ do
break
;;
"Test Playwright ALL browsers in parallel (Chromium + Firefox + WebKit), server already running on :3000")
run_playwright_parallel
break
;;
"Check floating promises guard (@typescript-eslint/no-floating-promises + auth await scan)")
if ! command -v rg >/dev/null 2>&1; then
echo "ripgrep (rg) not found. Installing dependency."
+4 -2
View File
@@ -95,12 +95,14 @@ async function waitForMeteor(page) {
/** Navigate to a board and wait for it to render lists. */
async function openBoard(page, boardId, slug) {
for (let attempt = 1; attempt <= 3; attempt++) {
// Up to 5 attempts so the slowest browser (WebKit) survives the contention
// of the 3-browser parallel run against a single shared dev server.
for (let attempt = 1; attempt <= 5; attempt++) {
await page.goto(`${BASE_URL}/b/${boardId}/${slug}`, { waitUntil: 'commit' });
const hasList = await page
.locator('.js-list:not(.js-list-composer)')
.first()
.waitFor({ timeout: 15_000 })
.waitFor({ timeout: 20_000 })
.then(() => true)
.catch(() => false);
if (hasList) return;
@@ -103,6 +103,11 @@ test.describe('Cards – operations', () => {
const targetTitle = await bp.listHeader(listB).innerText();
await cp.moveCard(null, targetTitle.trim());
// Wait for the move to settle (card removed from listA, rendered in listB)
// before snapshotting titles — otherwise we can catch the card mid-flight.
await expect(bp.minicard(listB, 'Alpha Card')).toBeVisible({ timeout: 10_000 });
await expect(bp.minicard(listA, 'Alpha Card')).not.toBeVisible({ timeout: 5_000 });
const titlesA = await bp.getCardTitles(listA);
const titlesB = await bp.getCardTitles(listB);
const occurrences = [...titlesA, ...titlesB].filter(t => t.includes('Alpha Card')).length;
@@ -268,7 +273,14 @@ test.describe('Cards – operations', () => {
await bp.openAddCardBottom(listC);
await bp.submitNewCard(listC, 'New Bottom Card');
const titles = await bp.getCardTitles(listC);
expect(titles[titles.length - 1]).toContain('New Bottom Card');
// submitNewCard only waits for the card to exist, not for its sort position
// to settle. Poll until the reactive re-sort places it last in the list.
await expect.poll(
async () => {
const titles = await bp.getCardTitles(listC);
return titles[titles.length - 1] || '';
},
{ timeout: 10_000 },
).toContain('New Bottom Card');
});
});