Skip to main content

Overview

run_script_in_play_mode executes custom Luau code in a temporary Play Mode session, collects logs and errors, then automatically stops and returns the results. This is ideal for:
  • Testing game logic without manual playtesting
  • Validating scripts in runtime environment
  • Debugging server-side behavior
  • Collecting runtime data for analysis
This tool requires StudioTestService, which is only available in Roblox Studio with proper permissions.

Basic Usage

Simple Test

Run a basic script and capture output:
{
  "code": "print('Hello from Play Mode!')\nprint('Current time:', os.clock())",
  "timeout": 5
}

Return Values

Capture return values from your test code:
{
  "code": "local workspace = game:GetService('Workspace')\nlocal partCount = #workspace:GetChildren()\nreturn partCount",
  "timeout": 5
}

Testing Game Behavior

Spawn Player and Test Character

{
  "code": "local Players = game:GetService('Players')\nlocal player = Players:CreateLocalPlayer(0)\nplayer.CharacterAdded:Wait()\nlocal character = player.Character\nlocal humanoid = character:WaitForChild('Humanoid')\nprint('Player spawned with health:', humanoid.Health)\nreturn humanoid.Health",
  "timeout": 10
}

Test Part Collision

{
  "code": "local part1 = Instance.new('Part')\npart1.Size = Vector3.new(10, 1, 10)\npart1.Position = Vector3.new(0, 5, 0)\npart1.Parent = workspace\n\nlocal part2 = Instance.new('Part')\npart2.Size = Vector3.new(5, 5, 5)\npart2.Position = Vector3.new(0, 10, 0)\npart2.Parent = workspace\n\nwait(0.5)\n\nlocal touching = false\nfor _, touchingPart in ipairs(part1:GetTouchingParts()) do\n\tif touchingPart == part2 then\n\t\ttouching = true\n\t\tbreak\n\tend\nend\n\nprint('Parts touching:', touching)\nreturn touching",
  "timeout": 3
}

Testing with Multiple Services

Test DataStore Operations

{
  "code": "local DataStoreService = game:GetService('DataStoreService')\nlocal testStore = DataStoreService:GetDataStore('TestStore')\n\nlocal testData = { coins = 100, level = 5 }\ntestStore:SetAsync('player_123', testData)\n\nlocal retrieved = testStore:GetAsync('player_123')\nprint('Retrieved data:', game:GetService('HttpService'):JSONEncode(retrieved))\n\nreturn retrieved.coins == 100",
  "timeout": 5
}
DataStore operations in Studio may not work exactly as in production. Use mock data stores for testing when possible.

Test Remote Events

{
  "code": "local ReplicatedStorage = game:GetService('ReplicatedStorage')\n\nlocal remoteEvent = Instance.new('RemoteEvent')\nremoteEvent.Name = 'TestEvent'\nremoteEvent.Parent = ReplicatedStorage\n\nlocal eventFired = false\nlocal receivedData = nil\n\nremoteEvent.OnServerEvent:Connect(function(player, data)\n\teventFired = true\n\treceivedData = data\n\tprint('Event received:', data)\nend)\n\n-- Simulate firing the event\nremoteEvent:FireServer('test_data')\n\nwait(0.1)\n\nreturn eventFired",
  "timeout": 3
}

Error Handling and Debugging

Catching Errors

{
  "code": "local part = workspace.NonExistentPart\nprint(part.Size)",
  "timeout": 5
}

Safe Error Testing

{
  "code": "local success, result = pcall(function()\n\tlocal part = workspace.NonExistentPart\n\treturn part.Size\nend)\n\nif not success then\n\tprint('Caught error:', result)\nend\n\nreturn success",
  "timeout": 5
}

Timeout Handling

Configure Timeout

{
  "code": "wait(10)\nprint('This will never print')",
  "timeout": 2
}
Set timeout appropriately for your test. Default is 100 seconds, but most tests should complete in 5-10 seconds.

Long-Running Tests

{
  "code": "local startTime = os.clock()\nlocal results = {}\n\nfor i = 1, 100 do\n\tlocal part = Instance.new('Part')\n\tpart.Parent = workspace\n\ttable.insert(results, part.Name)\n\tif i % 10 == 0 then\n\t\tprint('Created', i, 'parts')\n\tend\nend\n\nlocal duration = os.clock() - startTime\nprint('Test completed in', duration, 'seconds')\n\nreturn #results",
  "timeout": 30
}

Run Modes

You can specify whether to run in Play Mode or Server Mode:
{
  "code": "print('Running in', game:GetService('RunService'):IsServer() and 'Server' or 'Client', 'mode')",
  "mode": "play",
  "timeout": 5
}
  • play: Starts Play Mode (includes client simulation)
  • server: Starts Run Mode (server-only)

Best Practices

Minimize wait() calls and focus on specific behaviors. Faster tests provide quicker feedback.
Test scripts run in a temporary session that gets cleaned up automatically, but avoid creating excessive instances that could slow down the test.
Wrap potentially failing code in pcall to handle errors gracefully and return useful diagnostics.
Return boolean or numeric values that indicate test success/failure for easy automation.
Use print() statements to track test progress, especially in longer tests.

Common Patterns

Test Suite Runner

{
  "code": "local tests = {}\nlocal passed = 0\nlocal failed = 0\n\nlocal function test(name, fn)\n\tlocal success, err = pcall(fn)\n\tif success then\n\t\tpassed = passed + 1\n\t\tprint('✓', name)\n\telse\n\t\tfailed = failed + 1\n\t\tprint('✗', name, ':', err)\n\tend\nend\n\ntest('Workspace exists', function()\n\tassert(game:GetService('Workspace') ~= nil)\nend)\n\ntest('Baseplate exists', function()\n\tassert(workspace:FindFirstChild('Baseplate') ~= nil)\nend)\n\ntest('Player spawns', function()\n\tlocal Players = game:GetService('Players')\n\tassert(#Players:GetPlayers() >= 0)\nend)\n\nprint('\\nResults:', passed, 'passed,', failed, 'failed')\nreturn failed == 0",
  "timeout": 10
}

Next Steps

Basic Operations

Learn basic instance manipulation

Bulk Operations

Efficiently modify multiple instances

Build docs developers (and LLMs) love