Skip to main content
The default mode for web apps that live in one repository and run on their own. For every pull request, qlane clones the pull request’s code, installs and builds it, starts it in a fresh sandbox, and connects the agent.
Name in the product: this mode is a Simple sandbox. The environment page’s header calls it that, and when you create the environment it is the Single service option. This page says Simple sandbox from here on.

When to use this mode

  • Your app is one repository that runs on its own: a static site, a front end, or an API that only talks to hosted services.
  • You can install, build and serve it with your package manager’s scripts.
  • You want the agent to test the exact code in the pull request, not a separate preview deployment.
If your app needs a database, cache, queue or object store running beside it, use Docker Compose (a Composed sandbox) instead. If you already build per-PR previews on Vercel, Netlify or Cloudflare, Existing previews is usually a better fit: the same coverage with no extra build time.

Set it up

From your project, click Add environment and choose Test every pull request.
1

Repo

Pick the Repository and Branch (the default to test against, typically main; on a pull request qlane uses its head commit automatically). If the app is not at the repository root, set the Root directory. See When the app is not at the repository root.
2

Runtime

Under How does this app run?, choose Single service.
3

Login

Add a test account only if signing up isn’t enough for your app; see Accounts and data. Otherwise skip this step.
4

Build

Fill in the four fields. qlane reads your package.json and suggests values; check them against the rules below, especially serve the production build.
5

Review

Check the summary and create the environment.
Then add any variables your app needs to boot (an auth secret, an API base URL) under Runtime Variables on the environment’s page. Mark sensitive values as secret to encrypt them and mask them in the UI. Without the values it needs, the app won’t start and the run fails before the agent connects.

Accounts and data

Most apps need neither a test account nor seed data. Every run gives the agent an email inbox of its own, so it can sign up to your app the way a new user does, open the confirmation email, and sign in. Before testing a pull request, Qlane also tries to set up what each planned case needs, such as an account, a record or a screen in the right state, mostly through your app’s own pages. That is Set up preconditions before testing under Project settings → PR testing. It is on by default; check that it is on for your project. Add a test account when signing up isn’t enough:
  • Your app has no self sign-up: accounts are invite-only, created by an admin, or signed in through single sign-on.
  • Signing up needs something an inbox can’t give, such as a phone number, a CAPTCHA or an address on your own company’s domain, or a confirmation email your app can’t send from the sandbox.
  • Cases need a role or a plan that a new sign-up doesn’t get.
Seed data only when cases need state your app’s pages can’t create, such as history, usage figures or records that background jobs produce. A Simple sandbox has no step that runs once the app is up, so put that state in the hosted services your app reads from before the run, or move to a Composed sandbox, which has one. A Simple sandbox runs only your app, so whatever your app stores in hosted services, including the accounts and records the agent creates by signing up or during setup, stays there after the run. Point the sandbox’s variables at a test or staging instance of those services, never production.

The four fields

Install and build must run to completion and exit 0. A non-zero exit fails the environment and reports that step’s own output, so a failure names the step it came from. The start command must keep running. It is launched in the background as a long-running server. If it exits with an error, the step fails at once and shows its output. If it exits cleanly, nothing is left listening, and the step fails when the 30-second window ends.
There is no ready-check setting. Your app has 30 seconds to answer. Once the start command is launched, a request to the preview URL must reach your app within a fixed 30-second window. Build time does not count against it, because the build is a separate step that finishes first. The window measures how long your server takes to begin serving, not how long your project takes to compile.
Any response your app returns counts, including a 4xx or a 5xx: an app that is up and failing is a finding for the agent to report, not a reason to keep waiting. A reply from the sandbox’s own edge saying nothing is listening yet does not count. PORT is set in the start command’s environment to the value of the Port field. Some frameworks read it and others ignore it, so treat it as a convenience rather than a guarantee. If your framework takes a port flag or reads a port from its config file, make sure that value and the Port field agree.

Serve the production build, not the dev server

This is the most consequential choice on the screen, and the default answer is the production build:
  • Build command: your production build.
  • Start command: the command that serves what the build produced.
A dev server skips the production transforms: minification, bundling, tree-shaking, dead-code and unused-CSS elimination, asset hashing, and build-time inlining of environment variables. A whole class of regressions lives there. A class name works in dev and is stripped from the build. A dynamic import breaks only once bundled. A variable is read at runtime in dev but baked in at build time in production. Point the agent at a dev server and it can’t see any of them, so it passes a pull request that breaks the moment it ships. Dev servers also add machinery production doesn’t have: hot-reload sockets, and error overlays that can mask, restyle or swallow a real failure. Fall back to a dev server only when the repository has no way to serve built output.

Listen on 0.0.0.0, not 127.0.0.1

Most dev and preview servers listen on localhost only. That default is invisible on a laptop and fatal in a sandbox: a server bound to loopback inside the sandbox can’t be reached from outside it. There is no error message. The 30-second wait expires while the process looks perfectly healthy. Pass whatever your framework uses to listen on every interface: commonly a --host 0.0.0.0 flag, sometimes a HOST=0.0.0.0 variable. A Composed sandbox has the same requirement.

What you can type in the command fields

Each command is run through sh -c. What limits you is the check that runs before it. Because the shell is real, that check is what stands between a command and everything a shell would otherwise let it do. Two restrictions apply before anything runs:
  • The first word must be one of: npm, npx, yarn, pnpm, bun, node, next, turbo, tsc.
  • These characters are rejected anywhere in the field, as are line breaks and carriage returns: ; | & ` $ ( ) { } \ < >
So you can’t chain, redirect or substitute in a field. npm run build && npm run postbuild is refused, so is cd app && npm start, and so is anything using $(...). The remedy is better than the thing it replaces: put the chain in a package.json script and call the script. The restriction applies to what you type in the field, never to what the script does, so an && inside a package script runs normally.
package.json
The fields are then simply npm run build and npm run serve, and both pass.

When the app is not at the repository root

Set the environment’s Root directory rather than trying to cd into the subdirectory, which the character rules forbid anyway. Commands then run inside that directory, which also lets the package manager find the workspace’s own manifest and use the package manager it declares.

A worked example

A static site built by a bundler, whose package.json has a build script and a preview server: Three things in it matter:
  • npm ci rather than npm install. It installs exactly what the lockfile pins, which is reproducible and usually faster. It fails outright if the lockfile has drifted from package.json; if the first run errors there, switch to npm install.
  • The bare --. With npm, it passes --host through to the underlying command instead of npm consuming it.
  • The port is the preview server’s, not the dev server’s. They are often different numbers in the same project, and Port must name the one the start command binds.
To find your own values, read the scripts block in package.json. The script that serves built output is usually called preview, serve or start, but check what it does rather than trusting the name: a start script that runs the dev server is common, and it is the wrong choice.

Variables

  • Your app’s own public address isn’t provided. QLANE_SANDBOX_PUBLIC_URL is set only in a Composed sandbox. A variable on a Simple sandbox that references ${QLANE_SANDBOX_PUBLIC_URL} stops the run rather than expanding to nothing.
  • Names starting with QLANE_ are reserved. Saving a variable of your own with that prefix is refused.

Turn on pull-request testing

A new environment starts with only Mention triggers on: comment @qlane-ai test on a pull request to start a run. To test every pull request automatically, open Project settings → PR testing, find the environment under Per environment, and turn on Automatic runs. See Triggers for the details, and Docker Compose for Watch paths, which work the same way here.

What qlane does on each pull request

  1. Provisions a fresh, isolated sandbox.
  2. Clones your repository at the pull request’s head commit.
  3. Runs your install command, then your build command if you set one, then starts your app.
  4. Waits up to 30 seconds for the app to answer on the configured port.
  5. Tries to set up the accounts and data the planned cases need, unless setup is off.
  6. Connects the agent and runs your active test cases plus any drafts generated from the pull request’s diff.
  7. Posts a check and, if anything broke, a structured review.

What the agent can do in this mode

In addition to a real browser, the agent gets:
  • Shell access inside the sandbox (bash, readFile). It can inspect logs, check files it wrote, and re-run commands.
  • Code awareness. It reads the repository to understand failures and write better reproductions.
  • Deep testing. When stuck, the agent can add temporary instrumentation, re-run, and observe. The sandbox is torn down after the session, so changes to its files and processes don’t escape. Anything written to a hosted service your app talks to does.

Common gotchas

  • Native dependencies. Anything that needs a system library should be in your package’s install hooks. The sandbox is Linux-based, so macOS-only build steps fail.
  • A slow server start. The 30-second window covers start-up only, not the build. If your server needs longer to begin serving, move the work into the build step, or serve the prebuilt output with a lighter server.
  • Required variables. Set the auth secret, API URLs and anything else the app needs under Runtime Variables. Without them the app won’t boot, and the run fails before the agent connects.

Checklist

  • The start command serves the production build, not a dev server (why)
  • The server listens on 0.0.0.0, not 127.0.0.1 (why)
  • Port is the port the start command actually binds (fields)
  • Every field starts with an allowed command and contains no rejected characters (rules)
  • Anything that needs chaining lives in a package script, not in a field (rules)
  • The app is at the repository root, or Root directory is set (root directory)
  • The server answers within 30 seconds of the start command launching (fields)
  • The app needs no backing services. If it does, use a Composed sandbox
  • A new sign-up can reach what your cases test, or you added a test account that can (accounts and data)