Examples
Ready-to-run scripts covering the most common Sentinel use cases. Copy, paste, and adapt.
Hacker News scraper
Extract top stories with title, points, and comment count using extract().
import { Sentinel, z } from '@isoldex/sentinel';
const sentinel = new Sentinel({ apiKey: process.env.GEMINI_API_KEY });
await sentinel.init();
await sentinel.goto('https://news.ycombinator.com');
const { stories } = await sentinel.extract(
'Get the top 10 stories with title, points, and comment count',
z.object({
stories: z.array(z.object({
title: z.string(),
points: z.number(),
comments: z.number(),
url: z.string().optional(),
}))
})
);
console.table(stories);
await sentinel.close();Amazon autonomous agent
Full agent loop: search, sort, extract top 5 products. Exports stable CSS selectors for Playwright tests.
import { Sentinel, z } from '@isoldex/sentinel';
const sentinel = new Sentinel({
apiKey: process.env.GEMINI_API_KEY,
locatorCache: './cache/amazon-locators.json',
promptCache: './cache/amazon-prompts.json',
verbose: 1,
});
await sentinel.init();
await sentinel.goto('https://amazon.de');
const result = await sentinel.run(
'Search for "mechanical keyboard under 100 euros", ' +
'sort by price ascending, extract the top 5 results',
{
maxSteps: 20,
onStep: ({ stepNumber, type, instruction }) =>
console.log(`[${stepNumber}] ${type}: ${instruction}`),
}
);
console.log('Goal achieved:', result.goalAchieved);
console.log('Products:', result.data);
// Paste these directly into your Playwright tests
console.log('Selectors:', result.selectors);
await sentinel.close();Parallel price comparison
Scrape 4 shops simultaneously with a concurrency-limited worker pool. One failure never affects others.
import { Sentinel, z } from '@isoldex/sentinel';
const productSchema = z.object({
name: z.string(),
price: z.string(),
url: z.string().optional(),
});
const results = await Sentinel.parallel(
[
{ url: 'https://amazon.de', goal: 'Find the cheapest laptop, extract name and price' },
{ url: 'https://ebay.de', goal: 'Find the cheapest laptop, extract name and price' },
{ url: 'https://otto.de', goal: 'Find the cheapest laptop, extract name and price' },
{ url: 'https://idealo.de', goal: 'Find the cheapest laptop, extract name and price' },
],
{
apiKey: process.env.GEMINI_API_KEY,
concurrency: 4,
locatorCache: './cache/parallel-locators.json',
onProgress: (done, total, result) => {
const status = result.success ? '✓' : '✗';
console.log(`${status} [${done}/${total}] ${result.url}`);
},
}
);
for (const result of results) {
console.log(result.url, result.success ? result.data : result.error);
}Session persistence
Log in once, save the session, skip login on all subsequent runs.
import { Sentinel } from '@isoldex/sentinel';
const sentinel = new Sentinel({ apiKey: process.env.GEMINI_API_KEY });
await sentinel.init();
// Step 1: log in once and save the session
await sentinel.goto('https://github.com/login');
if (await sentinel.hasLoginForm()) {
await sentinel.act('Fill %user% into the username field', {
variables: { user: process.env.GITHUB_USER! },
});
await sentinel.act('Fill %pass% into the password field', {
variables: { pass: process.env.GITHUB_PASS! },
});
await sentinel.act('Click the sign in button');
await sentinel.saveSession('./sessions/github.json');
}
// Now do authenticated work
await sentinel.goto('https://github.com/notifications');
const { notifications } = await sentinel.extract(
'Get unread notification titles',
z.object({ notifications: z.array(z.string()) })
);
console.log(notifications);
await sentinel.close();
// Step 2: subsequent runs — just use sessionPath
// const sentinel = new Sentinel({
// apiKey: process.env.GEMINI_API_KEY,
// sessionPath: './sessions/github.json',
// });Record & Replay
Capture a workflow, export it as TypeScript, and replay it on demand.
import { Sentinel } from '@isoldex/sentinel';
import { writeFileSync } from 'fs';
const sentinel = new Sentinel({ apiKey: process.env.GEMINI_API_KEY });
await sentinel.init();
// Start recording
sentinel.startRecording('checkout-flow');
await sentinel.goto('https://shop.example.com');
await sentinel.act('Click the first product');
await sentinel.act('Click Add to Cart');
await sentinel.act('Proceed to checkout');
await sentinel.act('Fill "Test User" into the name field');
await sentinel.act('Click Place Order');
const workflow = sentinel.stopRecording();
// Export as reusable TypeScript
const code = sentinel.exportWorkflowAsCode(workflow);
writeFileSync('./workflows/checkout.ts', code);
console.log('Workflow saved. Steps:', workflow.steps.length);
// Replay anytime
await sentinel.replay(workflow);
await sentinel.close();Playwright Test integration
Drop-in ai fixture for existing test suites. Mix AI actions with standard Playwright assertions.
import { test, expect } from '@isoldex/sentinel/test';
import { z } from 'zod';
// All standard Playwright fixtures (page, browser, context) still work
test('search and extract HN stories', async ({ ai }) => {
await ai.goto('https://news.ycombinator.com');
const { stories } = await ai.extract(
'Get the top 5 story titles and points',
z.object({
stories: z.array(z.object({
title: z.string(),
points: z.number(),
}))
})
);
expect(stories.length).toBeGreaterThan(0);
expect(stories[0].points).toBeGreaterThan(0);
// Token cost per test run
console.log('Cost:', ai.getTokenUsage().estimatedCostUsd.toFixed(4), 'USD');
});
test('login flow', async ({ ai, page }) => {
await ai.goto('https://example.com/login');
await ai.act('Fill "admin@example.com" into the email field');
await ai.act('Fill "password123" into the password field');
await ai.act('Click the login button');
// Mix AI actions with standard Playwright assertions
await expect(page).toHaveURL(/dashboard/);
});OpenTelemetry + Jaeger
Wire up traces and metrics in 10 lines. Every act() and run() call emits spans automatically.
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { Sentinel } from '@isoldex/sentinel';
// 1. Start OTel SDK BEFORE creating Sentinel
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces', // Jaeger / Grafana Tempo / etc.
}),
metricReader: new PrometheusExporter({ port: 9464 }),
});
sdk.start();
// 2. All Sentinel calls now emit spans and metrics automatically
const sentinel = new Sentinel({ apiKey: process.env.GEMINI_API_KEY });
await sentinel.init();
await sentinel.goto('https://example.com');
const result = await sentinel.run('Extract all product names');
// Spans emitted:
// sentinel.agent
// └─ sentinel.agent.step (×N)
// └─ sentinel.act / sentinel.extract
// └─ sentinel.llm ← tokens, cost, latency
await sentinel.close();
await sdk.shutdown();Extend an existing Playwright page
Add AI capabilities to any existing Page object — no test restructuring needed.
import { chromium } from 'playwright';
import { Sentinel, z } from '@isoldex/sentinel';
// Existing Playwright project — just add Sentinel on top
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
const sentinel = new Sentinel({
apiKey: process.env.GEMINI_API_KEY,
locatorCache: true,
});
// Extend the existing page object
await sentinel.extend(page);
await page.goto('https://news.ycombinator.com');
// Standard Playwright API still works
await expect(page.locator('body')).toBeVisible();
// AI methods now available directly on the page
await page.act('Click on the first story title');
const data = await page.extract(
'Get the article title and author',
z.object({ title: z.string(), author: z.string().optional() })
);
console.log(data);
await browser.close();