A run on a box is your suite’s own @unotest/web, started in a container
of its own. This page is what that container looks like from inside: the
answer to “where may my suite write?” in one place.
The filesystem
| Path | Access | What it is |
|---|---|---|
/box/sources | read-only | The bundle you pushed: unotest/, its node_modules. The working directory of the run. |
/box/artifacts/unotest/.runs.<env> | read-write | This environment’s run history — journals, failure bundles, debug trees. Survives the run; the viewer shows it. |
/tmp | read-write, tmpfs, noexec | Scratch space. Gone when the run ends. |
| everything else | read-only | The container’s own filesystem. |
Those two are the only places a run may write. Everything else answers
with EROFS (“Read-only file system”) — or ENOENT for a directory that
would have to be created on the read-only part first.
/box/artifacts itself, and /box/artifacts/unotest, are not
writable: only the one runs directory under them is mounted read-write.
Where to write
- Something the run needs only while it runs — a seeded database, a
downloaded file, a cache:
/tmp, throughos.tmpdir()in Node or${TMPDIR:-/tmp}in a shell script. - Something that should outlive the run and be seen in the viewer:
$UNOTEST_ARTIFACTS_ROOT/unotest/.runs.$UNOTEST_ENV. Write files of your own into a directory of your own there, not next to the runner’s.
UNOTEST_ARTIFACTS_ROOT names the root the runner’s paths hang off,
not a directory you can write into: mkdir $UNOTEST_ARTIFACTS_ROOT/x
fails, with or without recursive. Nothing under unotest/ in the
sources is writable either — not unotest/.tmp, not unotest/.runs.
Variables a run is given
| Variable | Value |
|---|---|
HOME | /tmp |
TMPDIR | /tmp |
UNOTEST_ARTIFACTS_ROOT | /box/artifacts |
UNOTEST_FAILURES_ROOT | the failure bundles directory inside the runs mount |
UNOTEST_DEBUG_DIR | the run’s debug directory inside the runs mount |
UNOTEST_ENV | the environment’s name |
UNOTEST_RUN_ID | this run’s id |
The first five are the box’s: a value of the same name in an environment does not reach the run. The last two name the run; leave them alone. The environment’s own variables and secrets arrive beside them, as set on the box.
A box older than TMPDIR in this list leaves it unset; ${TMPDIR:-/tmp}
works on both.
shell() steps
A shell() command starts in sandbox.shellCwd, which defaults to the
working directory — on a box, the read-only /box/sources. A command
that writes relative to where it starts fails there. Either write to
/tmp, or point the shell somewhere writable in unotest.config:
sandbox: { shellCwd: process.env.UNOTEST_ARTIFACTS_ROOT ? `${process.env.UNOTEST_ARTIFACTS_ROOT}/unotest/.runs.${process.env.UNOTEST_ENV}` : process.cwd(),},The command inherits the run’s variables, HOME, TMPDIR and
UNOTEST_ARTIFACTS_ROOT included, and runs in the same container as the
scenario — a file it writes to /tmp is there for the next step.
The runner version
Pin @unotest/web 0.35.0 or newer in unotest/package.json. Older
runners do not write failure bundles into a box’s layout, so a failed run
leaves nothing to look at. A box may refuse such a bundle at upload with
min-version, naming both versions.
When something writes where it may not
The sign is EROFS / Read-only file system (or ENOENT on a mkdir
under /box/artifacts) in a step or in the suite’s prepare, on a path
under /box/sources or /box/artifacts. Move the write to one of the
two places above. Locally the same suite writes anywhere, which is why
the error only appears on the box.
See also Security model for why the container is shaped this way.