Fix flaky art upload E2E test by verifying MinIO bucket

The test failed intermittently in CI with a 500 from POST /art/new.data:
the sendou bucket was missing for the whole run, so the upload threw
NoSuchBucket. art.spec.ts is the only e2e test that writes to S3, which
is why it was the only casualty.
This commit is contained in:
Kalle
2026-08-03 16:08:27 +03:00
parent fd313b8fac
commit ef368e9fa2
3 changed files with 35 additions and 22 deletions

View File

@@ -23,10 +23,13 @@ jobs:
- name: Start MinIO
run: docker compose up -d minio
# the bucket, not the health endpoint: MinIO answers health/live while it is still
# bootstrapping, so a run whose bucket never got created would only surface much later
# as a 500 in the one test that uploads an image
- name: Wait for MinIO to be ready
run: |
for i in {1..30}; do
if curl -sf http://127.0.0.1:9000/minio/health/live; then
if curl -sf -o /dev/null http://127.0.0.1:9000/sendou/; then
echo "MinIO is ready"
exit 0
fi

View File

@@ -12,9 +12,9 @@ services:
isAlive() { curl -sf http://127.0.0.1:9000/minio/health/live; } # check if Minio is alive
minio $$0 "$$@" --quiet & echo $$! > /tmp/minio.pid # start Minio in the background
while ! isAlive; do sleep 0.1; done # wait until Minio is alive
mc alias set minio http://127.0.0.1:9000 minio-user minio-password # setup Minio client
mc mb minio/sendou || true # create a test bucket
mc anonymous set public minio/sendou # make the test bucket public
until mc alias set minio http://127.0.0.1:9000 minio-user minio-password; do sleep 0.5; done # setup Minio client (health/live can answer before credentials are accepted)
until mc mb --ignore-existing minio/sendou; do sleep 0.5; done # create a test bucket
until mc anonymous set public minio/sendou; do sleep 0.5; done # make the test bucket public
kill -s INT $$(cat /tmp/minio.pid) && rm /tmp/minio.pid # stop Minio
while isAlive; do sleep 0.1; done # wait until Minio is stopped
exec minio $$0 "$$@" # start Minio in the foreground

View File

@@ -7,6 +7,9 @@ import { E2E_BASE_PORT, e2eWorkerPort } from "./helpers/playwright";
const DEBUG = process.env.E2E_DEBUG === "true";
const SERVER_PROCESSES: ChildProcess[] = [];
const MINIO_MARKER_FILE = ".e2e-minio-started";
const STORAGE_BUCKET = "sendou";
/** Anonymously listable only once the bucket exists and its public policy is set. */
const MINIO_BUCKET_URL = `http://127.0.0.1:9000/${STORAGE_BUCKET}/`;
const BUILD_MARKER_FILE = ".e2e-build-marker";
const BUILD_INPUTS = [
"app",
@@ -21,16 +24,26 @@ declare global {
var __E2E_SERVERS__: ChildProcess[];
}
/**
* Whether the image storage is usable, which takes more than MinIO answering its health check:
* the container bootstraps the bucket only after startup, and a run whose bucket never got created
* would otherwise fail deep inside the one test that uploads an image (`art.spec.ts`) with an
* opaque 500.
*/
async function isMinioBucketReady(): Promise<boolean> {
try {
const response = await fetch(MINIO_BUCKET_URL);
return response.ok;
} catch {
return false;
}
}
async function waitForMinio(timeout = 60000): Promise<boolean> {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
const response = await fetch("http://127.0.0.1:9000/minio/health/live");
if (response.ok) {
return true;
}
} catch {
// MinIO not ready yet
if (await isMinioBucketReady()) {
return true;
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
@@ -39,15 +52,10 @@ async function waitForMinio(timeout = 60000): Promise<boolean> {
async function ensureMinioRunning(): Promise<boolean> {
// Check if MinIO is already running
try {
const response = await fetch("http://127.0.0.1:9000/minio/health/live");
if (response.ok) {
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("MinIO is already running");
return false;
}
} catch {
// MinIO not running, we need to start it
if (await isMinioBucketReady()) {
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("MinIO is already running");
return false;
}
// biome-ignore lint/suspicious/noConsole: CLI script output
@@ -56,7 +64,9 @@ async function ensureMinioRunning(): Promise<boolean> {
const isReady = await waitForMinio();
if (!isReady) {
throw new Error("MinIO failed to start within timeout");
throw new Error(
`MinIO did not become usable within timeout (${MINIO_BUCKET_URL} never answered OK). If MinIO is running, its "${STORAGE_BUCKET}" bucket is missing or not public — recreate the container with "docker compose up -d --force-recreate minio".`,
);
}
// biome-ignore lint/suspicious/noConsole: CLI script output
@@ -225,7 +235,7 @@ async function globalSetup(config: FullConfig) {
STORAGE_ACCESS_KEY: "minio-user",
STORAGE_SECRET: "minio-password",
STORAGE_REGION: "us-east-1",
STORAGE_BUCKET: "sendou",
STORAGE_BUCKET,
// no system messages to a shared skalop instance (see build env above)
SKALOP_SYSTEM_MESSAGE_URL: "",
SKALOP_TOKEN: "",