Skip to main content
isoldex

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().

scrapingextract
hacker-news.ts
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.

agente-commerce
amazon-agent.ts
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.

parallelscraping
parallel-compare.ts
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.

authsession
github-session.ts
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.

testingworkflow
record-replay.ts
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.

testingplaywright
hn.spec.ts
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.

observabilityotel
instrumentation.ts
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.

playwrightintegration
extend-page.ts
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();