Start typing to search packages!
checkpoint
By @jannik-ritter
Roblox
Mirrored from WallyCheckpoint
End-to-end tests for Rojo-synced Luau games. Scenarios are Luau modules that run inside a real Roblox Studio playtest; a Lune CLI drives Studio over the Studio MCP, reports each step, and saves a screenshot and the console log when one fails.
$ lune run checkpoint run
running 2 scenario(s) in MyGame
game ready
core/boot Server and client finish loading, and no module fails to require.
✓ server loaded (server, 4ms)
✓ client loaded (client, 9ms)
shop/buy Buying an item spends coins once.
✓ open shop (client, 120ms)
✗ coins spent (server, 10003ms)
expected coins to be 50 within 10s, last 100
.checkpoint/shop-buy--coins-spent.jpg
.checkpoint/shop-buy--coins-spent.log
1 passed, 1 failed
One Wally package holds both halves: the in-game harness (src/) and the Lune runner (runner/). They always come from the same version.
Setup
Requirements: Rokit with Wally, Rojo and Lune, and Roblox Studio with its MCP server enabled (%LOCALAPPDATA%\Roblox\mcp.bat). The runner starts the MCP through cmd.exe, so it works from Windows and WSL.
-
Add the package as a dev dependency and install it:
# wally.toml [dev-dependencies] checkpoint = "jannik-ritter/checkpoint@0.1.1"rokit add lune-org/lune wally installDev dependencies install into
DevPackages/, apart from the game'sPackages/. -
Copy the templates from
DevPackages/_Index/jannik-ritter_checkpoint@<version>/checkpoint/templates/into the project root:.lune/checkpoint.luau: the shim behindlune run checkpoint. It finds the installed version, so it never needs editing.tests/adapter.luauandtests/core/boot.luau: a starting adapter and scenario.
-
For type checking, map
DevPackagesindefault.project.jsonso luau-lsp can follow Wally's link files. This syncs the package into the place's ServerStorage, where clients never see it and nothing runs unless the runner starts it:"ServerStorage": { "DevPackages": { "$path": { "optional": "DevPackages" } } }optionalkeeps Rojo working when dev dependencies aren't installed. -
Optionally copy
skill/to.agents/skills/checkpoint/so coding agents know how to write and run scenarios.
Running
Open the game in Studio, sync it with rojo serve, and stay in Edit mode. Then:
lune run checkpoint list # scenarios, grouped by domain
lune run checkpoint run # every scenario
lune run checkpoint run shop # a domain, a path (shop/buy) or a name (buy)
lune run checkpoint run --studio MyGame # pick a Studio when several are connected (or set CHECKPOINT_STUDIO)
lune run checkpoint run --json # one JSON event per line, for agents and CI
Exit codes: 0 all passed, 1 a scenario failed, 2 the run could not complete (no Studio, already playing, timeout). Failure screenshots and console logs go to .checkpoint/; add it to .gitignore.
How a run works
- The runner injects the project's
DevPackages/andtests/into the Edit data model atReplicatedStorage.Checkpoint, mirroring the project layout, plus aScriptand aLocalScriptthat start the harness. Every injected instance has theCheckpointInjectedattribute. - It starts a playtest. The server waits for the test player and for the adapter's
readyhook on both sides, then runs the selected scenarios in order and writes progress events toServerStorage.CheckpointStatus. - The runner polls those events and prints them. A failed step holds the game state until the runner has saved its screenshot and the console.
- The runner stops the playtest and removes everything it injected, pass or fail.
Game code can tell it is under test with RunService:IsStudio() and ReplicatedStorage:FindFirstChild("Checkpoint"), for example to use mock persistence.
Writing tests
tests/
adapter.luau hooks: ready, reset
<domain>/<name>.luau one scenario per module
_<anything>/ helper modules (not scenarios)
Adapter
local Checkpoint = require("../DevPackages/checkpoint")
return Checkpoint.adapter({
-- Polled on the server and the client until both return true.
ready = function(side)
return workspace:GetAttribute(if side == "server" then "ServerLoaded" else "ClientLoaded") == true
end,
-- Runs on the server before every scenario: restore a neutral state and apply the scenario's fixture.
reset = function(player, fixture) end,
})
Scenarios
--!strict
local Checkpoint = require("../../DevPackages/checkpoint")
local PlayerData = require("@game/ReplicatedStorage/Modules/Shared/PlayerData")
local RewardData = require("@game/ReplicatedStorage/Modules/Data/RewardData")
local t = Checkpoint.t
return Checkpoint.scenario({
description = "Claiming the daily reward grants coins once.",
fixture = { Coins = 0 },
steps = {
Checkpoint.client("claim", function(ctx)
-- Act the way a player would: call the controller, fire the remote, trigger the prompt.
end),
Checkpoint.server("coins granted", function(ctx)
ctx.poll(function()
return PlayerData.Server.Get(ctx.player).Coins.Get()
end, "coins"):toBe(100)
ctx.expect(RewardData.Daily, "daily reward"):toMatch(t.interface({ Coins = t.numberPositive }))
end),
},
})
- The relative
requireresolves on disk (luau-lsp) and in Studio (the injected tree mirrors the project), so scenarios are type-checked against Checkpoint and the game. - Steps run in order on their side. After a failure the remaining steps are skipped. Each step times out after 30s; pass a third argument to change it.
- Steps receive a context, named
ctxby convention sotstays free for the type checker. It hasplayer,side,expect(value, label?),poll(read, label?, timeout?),waitFor(read, label?, timeout?)andonCleanup(fn). There is no sleep: wait for an observable condition withpollorwaitFor(10s by default). - Matchers:
toBe,toEqual,toBeTruthy,toBeFalsy,toBeNil,toBeGreaterThan,toBeLessThan,toContain, andtoMatch(check)for anytcheck (Checkpoint.t). - Scenarios and the adapter are validated when they load, so a malformed one fails with a message instead of mid-run.
- Keep scenarios independent: use
fixtureand the adapter'sreset, andonCleanupfor anything else a scenario changes.
Limits
- Scripts cannot press a GuiButton or send real keyboard and mouse input. Drive the code a button or key would call (a controller method, a remote, a prompt's handler) or give the game a Studio-only test hook.
- One player per run. Multiplayer would use
StudioTestService. - Runs need a running Studio, so they cannot run on hosted CI.
Development
See AGENTS.md. scripts/install-local.sh <project> installs the working copy into a game's DevPackages to try changes before publishing.
License
MIT
Package Details
Install command (Click to copy)
Version
0.1.1
License
MIT
Safe for commercial use
Automated license review — not legal advice.
