roblox-testing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited roblox-testing (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Official sources:
Start with a reproducible failure and identify whether it occurs on the client, server, or both. Choose a debugging or profiling tool based on that evidence.
Use print, warn, and error deliberately:
print for normal diagnostics.warn for recoverable problems you should notice.error for programming errors that should stop execution.Include context in log messages:
warn(string.format("[DataStore] Save failed for %d: %s", userId, tostring(err)))Avoid logging secrets, player data, or PII.
Wrap fallible calls, especially cloud services, HTTP, DataStores, Marketplace:
local ok, result = pcall(function()
return someService:DoSomething()
end)
if not ok then
warn("DoSomething failed:", result)
endUse assert for internal invariants that should never fail:
assert(config.MaxSpeed > 0, "MaxSpeed must be positive")Open with F9 in-game or in Studio play mode.
Tabs:
Toggle Client/Server views to see which side emitted output.
The standard Roblox testing framework is TestEZ. It supports nested describe/it blocks, lifecycle hooks, async tests, and a rich matcher API.
TestEZ example:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TestEZ = require(ReplicatedStorage.DevPackages.TestEZ)
local MathUtils = require(ReplicatedStorage.MathUtils)
describe("MathUtils", function()
it("clamps values", function()
expect(MathUtils.clamp(5, 0, 10)).to.equal(5)
expect(MathUtils.clamp(-1, 0, 10)).to.equal(0)
expect(MathUtils.clamp(11, 0, 10)).to.equal(10)
end)
end)
TestEZ.TestBootstrap:run({ game.ReplicatedStorage.Tests })For legacy in-Studio tests, TestService:Error(msg) can mark failures visibly in Studio output, but most projects should prefer TestEZ. The scripts folder contains a minimal TestRunner.lua shim only for environments where TestEZ is unavailable.
Use the Luau language server (luau-lsp) or the Luau type checker in Studio to catch errors before runtime.
Best practices:
luau-lsp analyze in CI or before committing.--!strict for new modules.Use Studio's built-in debugger for step-through debugging:
Use xpcall with debug.traceback to capture full stack traces from failures:
local ok, err = xpcall(function()
riskyOperation()
end, debug.traceback)
if not ok then
warn(err)
endThis is especially useful at top-level entry points, scheduled callbacks, and connection handlers where pcall alone discards the stack.
Leaked RBXScriptConnection objects are a common source of memory leaks and stale state. Prefer explicit cleanup patterns:
local Maid = require(path.to.Maid)
local maid = Maid.new()
maid:GiveTask(workspace.ChildAdded:Connect(onChild))
maid:GiveTask(RunService.Heartbeat:Connect(onStep))
maid:Destroy() -- disconnects everything:Once() for one-shot event handlers.Instance.Destroying to trigger cleanup when an instance is removed:obj.Destroying:Connect(function()
cleanup()
end):Connect() with a matching :Disconnect() or destruction.The task library is more predictable than legacy spawn, delay, and wait:
-- good
task.wait(1)
task.delay(1, callback)
task.spawn(coroutineOrFn)
-- avoid
spawn(callback)
delay(1, callback)
wait(1)task.defer is useful for yielding to the next resumption cycle without opening a new thread.
local function sanitize(value)
if typeof(value) == "Instance" and value:IsA("Player") then
return "Player(" .. tostring(value.UserId) .. ")"
end
return value
endlocal lastLog = 0
local function throttledLog(message)
local now = os.clock()
if now - lastLog > 5 then
lastLog = now
warn(message)
end
endIncorrect network ownership causes jittery physics and replication lag. Visualize ownership and assign it explicitly:
-- Server
part:SetNetworkOwner(player)
-- Client-side visualization (debug only)
local ownershipLabel -- create BillboardGui or use DebugDrawUse BasePart:GetNetworkOwner() to inspect ownership. Vehicles and held items should usually be owned by the controlling player.
For retry-safe reads, use bounded retries with exponential backoff and jitter. Do not use this generic loop to replay writes: a failed response can hide a committed write, and independently retried requests can execute out of order. See roblox-datastores for write reconciliation and per-key ordering.
local function readWithRetry(fn, maxAttempts)
maxAttempts = maxAttempts or 5
for attempt = 1, maxAttempts do
local ok, result = pcall(fn)
if ok then
return true, result
elseif attempt == maxAttempts then
return false, result
else
warn("DataStore attempt " .. attempt .. " failed; retrying...")
task.wait(2 ^ attempt * 0.1 + math.random() * 0.5)
end
end
endAlways call DataStores from the server and validate serialization before saving.
Use the Developer Console Memory tab or Scene Analysis to compare snapshots:
Open with Ctrl+F6 (⌘+F6) in Studio or the desktop client.
Use it to:
debug.profilebegin/debug.profileend.Key colors:
Available in Studio under Window → Performance Summary → Scene Analysis.
Views:
Scene Analysis is a Studio UI tool; there is no public SceneAnalysisService API.
Records CPU time per script. Use it when MicroProfiler points to scripts but you need to know which script.
| Symptom | Likely causes |
|---|---|
| Script silently fails | Missing pcall, error swallowed, wrong script context |
| Data not saving | DataStore called from client, non-serializable value, no pcall |
| Remote not working | Wrong side, handler not connected, argument mismatch |
| Lag spikes | Pathfinding every frame, too many particles, unbatched loops |
| Physics jitter | Wrong network ownership, assembly splits, conflicting constraints |
| NPCs stuck | Blocked path not recomputed, bad agent params, streaming issues |
| UI doesn't update | Property not replicated, wrong parent, layout order |
| Memory grows forever | Leaked connections, unparented instances, cached assets |
Shift+F3 in-game for network debug stats.Measure load time:
local start = os.clock()
game.Loaded:Connect(function()
print("Loaded in", os.clock() - start)
end)Run this from a LocalScript in ReplicatedFirst. It starts timing when ReplicatedFirst replicates and stops when game.Loaded fires, so it excludes the time to connect to the server and load the avatar. Load time as players experience it, from clicking Play to the world rendering, is longer. Enable Print Join Size Breakdown in Studio Settings → Network to see the largest replicated instances.
scripts/TestRunner.lua: a minimal TestEZ fallback with nested suites, lifecycle hooks, matchers, async support, and TestService integration.scripts/Logger.lua: a structured logger with level filtering and guarded formatting.scripts/DebugDraw.lua: utility for drawing rays, points, and boxes in 3D for visual debugging.<!-- catalog:references:start -->
<!-- catalog:references:end -->
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.