
Company News
Jerod Santo Joins Socket as Head of Media
Allow myself to introduce... myself.
@shiplightai/sdk
Advanced tools
A companion SDK for Playwright that makes your tests resilient to UI changes like dynamic IDs, layout rearrangements, and styling updates.
npm install @shiplightai/sdk playwright
import { chromium } from 'playwright';
import { createAgent, configureSdk } from '@shiplightai/sdk';
// Configure SDK with API key (call once at startup)
configureSdk({
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
// Create an agent
const agent = createAgent({
model: 'claude-haiku-4-5',
});
// Use with Playwright
const browser = await chromium.launch();
const page = await browser.newPage();
// Login using the Sauce Labs demo site (public test site)
await agent.login(page, {
url: 'https://www.saucedemo.com/',
username: 'standard_user',
password: 'secret_sauce',
});
// Verify login succeeded
await agent.assert(page, 'Products page is visible');
// Extract data from the page
await agent.extract(page, 'the first product name', 'productName');
console.log('First product:', agent.getVariable('productName'));
await browser.close();
Extend the agent with custom actions for your specific use case:
import { createAgent, z } from '@shiplightai/sdk';
const agent = createAgent({ model: 'claude-haiku-4-5' });
// Register a custom action
agent.registerAction({
name: 'extract_email_code',
description: 'Extract verification code from email inbox',
schema: z.object({
email_address: z.string().describe('The email address to check'),
code_type: z.enum(['verification', 'reset']).describe('Type of code'),
}),
async execute(args, ctx) {
// Your custom logic here
const code = await myEmailService.getCode(args.email_address, args.code_type);
// Store the result for later use
ctx.variableStore.set('verification_code', code);
return { success: true, message: `Found code: ${code}` };
},
});
// The agent will automatically use your action when needed
await agent.act(page, 'Get the verification code from email');
await agent.act(page, 'Enter {{ verification_code }} in the input field');
createAgent(options)Create a new agent instance.
const agent = createAgent({
// Required: LLM model to use
model: 'claude-haiku-4-5',
// Optional: Initial variables
variables: { username: 'test@example.com' },
// Optional: Keys to mark as sensitive (masked in logs)
sensitiveKeys: ['password', 'apiKey'],
// Optional: Directory for test data files
testDataDir: './test-data',
// Optional: Directory for downloads
downloadDir: './downloads',
});
The SDK supports any model from the following providers. Pass the model name directly or use provider:model syntax for explicit routing.
| Provider | Model pattern | Example models | API key |
|---|---|---|---|
gemini-* | gemini-2.5-pro, gemini-2.5-flash | GOOGLE_API_KEY | |
| Anthropic | claude-* | claude-haiku-4-5, claude-sonnet-4-6, claude-opus-4-6 | ANTHROPIC_API_KEY |
| OpenAI | gpt-*, o1*, o3*, o4*, chatgpt-* | gpt-4o, o4-mini | OPENAI_API_KEY |
Explicit provider routing — use provider:model to bypass auto-detection:
// Route through specific provider
const agent = createAgent({ model: 'openai:gpt-4o' });
const agent = createAgent({ model: 'anthropic:claude-sonnet-4-6' });
// Useful for fine-tuned models or cloud-hosted variants
const agent = createAgent({ model: 'openai:ft:gpt-4o:my-org' });
agent.registerAction(action)Register a custom action.
agent.registerAction({
// Unique action name (snake_case recommended)
name: 'my_action',
// Description for the agent
description: 'What this action does and when to use it',
// Zod schema for parameters
schema: z.object({
param1: z.string().describe('Description of the parameter'),
param2: z.number().optional(),
}),
// Execute function
async execute(args, ctx) {
// args: validated parameters
// ctx.page: Playwright page
// ctx.variableStore: access to variables
return { success: true, message: 'Optional status message' };
},
});
agent.act(page, instruction)Perform a single action on the page.
await agent.act(page, 'Click the login button');
await agent.act(page, 'Fill the email field with test@example.com');
await agent.act(page, 'Select "Express" from shipping dropdown');
agent.run(page, instruction, options?)Run a multi-step instruction until the goal is achieved.
await agent.run(page, 'Complete the checkout process');
await agent.run(page, 'Fill out the entire registration form');
await agent.run(page, 'Add 3 items to cart', { maxSteps: 10 });
agent.assert(page, statement)Assert a condition (throws on failure).
await agent.assert(page, 'Login button is visible');
await agent.assert(page, 'Cart contains 3 items');
agent.evaluate(page, statement)Evaluate a condition (returns boolean, doesn't throw).
const isLoggedIn = await agent.evaluate(page, 'User is logged in');
if (!isLoggedIn) {
await agent.run(page, 'Click the login button');
}
agent.extract(page, description, variableName)Extract data from the page and store in a variable.
await agent.extract(page, 'the order total', 'orderTotal');
await agent.run(page, 'Verify {{ orderTotal }} is displayed on receipt');
agent.step(page, action, description, options?)Wrap Playwright code with self-healing. If action throws, the agent analyzes the page and attempts to accomplish the goal described in description.
// Single action with self-healing
await agent.step(
page,
async () => await page.click('#submit-btn'),
'Click the submit button'
);
// Code block with multiple actions
await agent.step(
page,
async () => {
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'secret');
await page.click('#login');
},
'Fill login form and submit'
);
// Control recovery depth with maxSteps (default: 5)
await agent.step(
page,
async () => await page.click('.dynamic-button'),
'Click the dynamic button that appears after loading',
{ maxSteps: 3 }
);
agent.setVariable(name, value, sensitive?)Set a variable. Variables can be referenced in instructions using $variableName.
agent.setVariable('couponCode', 'SAVE20');
await agent.act(page, 'Enter $couponCode in the promo field');
// Sensitive values are masked in logs
agent.setVariable('apiKey', 'secret123', true);
agent.getVariable(name)Get a variable value.
await agent.extract(page, 'the order total', 'orderTotal');
console.log('Order total:', agent.getVariable('orderTotal'));
agent.waitUntil(page, condition, timeoutSeconds?)Wait until a condition becomes true.
await agent.waitUntil(page, 'Loading spinner is no longer visible');
const appeared = await agent.waitUntil(page, 'Table shows at least 5 rows', 30);
if (!appeared) {
throw new Error('Data did not load in time');
}
The execute function receives a context object:
interface IActionExecutionContext {
// Playwright page instance
page: Page;
// Variable store for reading/writing variables
variableStore: VariableStore;
}
async execute(args, ctx) {
// Read a variable
const email = ctx.variableStore.get('email');
// Set a variable
ctx.variableStore.set('result', 'some value');
// Set a sensitive variable (masked in logs)
ctx.variableStore.set('token', secretToken, true);
return { success: true };
}
Configure SDK-wide settings before creating agents:
import { configureSdk, getSdkConfig, LogLevel } from '@shiplightai/sdk';
configureSdk({
// Log level: DEBUG, INFO, WARN, ERROR
logLevel: LogLevel.INFO,
// Enable detailed agent logging
debugAgent: false,
// Environment variables (API keys — provide the key for your chosen model's provider)
env: {
GOOGLE_API_KEY: 'your-google-api-key', // for gemini-* models
// ANTHROPIC_API_KEY: 'your-anthropic-key', // for claude-* models
// OPENAI_API_KEY: 'sk-...', // for gpt-*, o1*, o3*, o4* models
},
// Optional: paths for logs and results
agentLogPath: './logs/agent.log',
testResultsJsonPath: './results.json',
});
// Read current config
const config = getSdkConfig();
API keys must be passed via configureSdk({ env }) — the SDK does not read process.env directly.
| Key | Required | Description |
|---|---|---|
GOOGLE_API_KEY | Yes* | Google AI API key for gemini-* models |
ANTHROPIC_API_KEY | Yes* | Anthropic API key for claude-* models |
OPENAI_API_KEY | Yes* | OpenAI API key for gpt-*, o1*, o3*, o4* models |
*Provide the key for the provider matching your chosen model.
MIT
FAQs
Shiplight SDK with pluggable custom actions
We found that @shiplightai/sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards users’ live OAuth session tokens through proxies controlled by a Russian bot service.

Security News
Anthropic found biased reasoning and recklessness drove Claude Mythos 5 to publish malware on PyPI and compromise a security vendor.