local ROOT = env.get("ASSAY_REPO_ROOT") or "."
local ASSAY_BIN = ROOT .. "/target/release/assay"
local ENGINE_BIN = ROOT .. "/target/release/assay-engine"
local HERE = ROOT .. "/crates/assay-workflow/tests-e2e"
local WORKER = HERE .. "/fixtures/demo-worker.lua"
local PORT = tonumber(env.get("ASSAY_E2E_PORT") or "8080")
local BASE = "http://localhost:" .. PORT
local DB = env.get("ASSAY_E2E_DB") or "/tmp/assay-e2e.sqlite"
local ENGINE_CONFIG = env.get("ASSAY_E2E_ENGINE_CONFIG") or "/tmp/assay-e2e-engine.toml"
local ENGINE_LOG = env.get("ASSAY_E2E_ENGINE_LOG") or "/tmp/assay-e2e-engine.log"
local WORKER_LOG = env.get("ASSAY_E2E_WORKER_LOG") or "/tmp/assay-e2e-worker.log"
local ADMIN_KEY = env.get("ASSAY_E2E_ADMIN_KEY") or "dev-admin-key-change-me"
local ADMIN_HEADERS = { ["Authorization"] = "Bearer " .. ADMIN_KEY }
local function log(msg)
io.write("[e2e] " .. msg .. "\n")
io.flush()
end
local function fail(msg)
error("[e2e] FATAL: " .. msg, 0)
end
local function reset_db()
pcall(fs.remove, DB)
fs.write(DB, "")
end
local function write_engine_config()
local toml = string.format([[
[server]
bind_addr = "127.0.0.1:%d"
[backend]
type = "sqlite"
path = "%s"
# Plan-15 slice 3 (v0.14.0): the engine refuses to start when there are
# no operator users AND no admin api keys. The dashboard e2e never
# bootstraps an admin user, so seed a break-glass key here. The key is
# only consumed by the engine's own gate — the dashboard suite doesn't
# call admin routes, so the value is arbitrary.
[auth]
admin_api_keys = ["dev-admin-key-change-me"]
[logging]
level = "info"
format = "pretty"
]], PORT, DB)
fs.write(ENGINE_CONFIG, toml)
end
local function wait_for_engine()
for _ = 1, 30 do
local ok, resp = pcall(http.get, BASE .. "/api/v1/engine/workflow/version", { timeout = 1 })
if ok and resp and resp.status == 200 then return true end
sleep(0.5)
end
return false
end
local engine_pid, worker_pid
local function teardown()
if worker_pid then pcall(process.kill, worker_pid) end
if engine_pid then pcall(process.kill, engine_pid) end
if worker_pid then pcall(process.wait, worker_pid, { timeout = 3 }) end
if engine_pid then pcall(process.wait, engine_pid, { timeout = 3 }) end
end
local ok, err = pcall(function()
reset_db()
write_engine_config()
log("starting assay-engine on :" .. PORT)
local h = process.spawn({
cmd = ENGINE_BIN,
args = { "serve", "--config", ENGINE_CONFIG },
stdout = ENGINE_LOG,
stderr = ENGINE_LOG,
})
engine_pid = h.pid
if not wait_for_engine() then
fail("engine never came up; tail of " .. ENGINE_LOG)
end
log("engine ready (pid " .. engine_pid .. ")")
log("creating namespace 'demo'")
local r = http.post(BASE .. "/api/v1/engine/workflow/namespaces", { name = "demo" }, { headers = ADMIN_HEADERS })
if r.status >= 400 and r.status ~= 409 then
fail("namespace create failed: " .. r.status .. " " .. (r.body or ""))
end
log("starting demo worker (assay runtime)")
local hw = process.spawn({
cmd = ASSAY_BIN,
args = { "run", WORKER },
stdout = WORKER_LOG,
stderr = WORKER_LOG,
env = {
ASSAY_ENGINE_URL = BASE,
ASSAY_ADMIN_KEY = ADMIN_KEY,
},
})
worker_pid = hw.pid
sleep(1.5)
log("seeding DemoPipeline (id=demo-2)")
local rs = http.post(BASE .. "/api/v1/engine/workflow/workflows", {
workflow_type = "DemoPipeline",
workflow_id = "demo-2",
namespace = "demo",
task_queue = "demo-q",
input = {},
}, { headers = ADMIN_HEADERS })
if rs.status >= 400 then
fail("workflow seed failed: " .. rs.status .. " " .. (rs.body or ""))
end
log("running playwright")
local res = shell.exec("npx playwright test", {
cwd = HERE,
env = {
ASSAY_E2E_BASE = BASE,
ASSAY_E2E_ADMIN_KEY = ADMIN_KEY,
CI = env.get("CI") or "",
},
})
io.write(res.stdout)
io.stderr:write(res.stderr)
if res.status ~= 0 then
fail("playwright suite failed (exit " .. tostring(res.status) .. ")")
end
end)
teardown()
if not ok then
error(tostring(err), 0)
end
log("OK")