Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 51 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ npm run build
Use `AgentRuntime` to add Jest-style assertions to your agent loops. Verify browser state, check task completion, and get clear feedback on what's working:

```typescript
import { SentienceBrowser, AgentRuntime, urlContains, exists, allOf } from 'sentienceapi';
import {
SentienceBrowser,
AgentRuntime,
urlContains,
exists,
allOf,
isEnabled,
isChecked,
valueEquals,
} from 'sentienceapi';
import { createTracer } from 'sentienceapi';
import { Page } from 'playwright';

Expand All @@ -52,6 +61,20 @@ runtime.assert(urlContains('example.com'), 'on_correct_domain');
runtime.assert(exists('role=heading'), 'has_heading');
runtime.assert(allOf([exists('role=button'), exists('role=link')]), 'has_interactive_elements');

// v1: state-aware assertions (when Gateway refinement is enabled)
runtime.assert(isEnabled('role=button'), 'button_enabled');
runtime.assert(isChecked("role=checkbox name~'subscribe'"), 'subscribe_checked_if_present');
runtime.assert(
valueEquals("role=textbox name~'email'", 'user@example.com'),
'email_value_if_present'
);

// v2: retry loop with snapshot confidence gating + exhaustion
const ok = await runtime
.check(exists('role=heading'), 'heading_eventually_visible', true)
.eventually({ timeoutMs: 10_000, pollMs: 250, minConfidence: 0.7, maxSnapshotAttempts: 3 });
console.log('eventually() result:', ok);

// Check task completion
if (runtime.assertDone(exists("text~'Example'"), 'task_complete')) {
console.log('✅ Task completed!');
Expand All @@ -60,7 +83,7 @@ if (runtime.assertDone(exists("text~'Example'"), 'task_complete')) {
console.log(`Task done: ${runtime.isTaskDone}`);
```

**See example:** [examples/agent-runtime-verification.ts](examples/agent-runtime-verification.ts)
**See examples:** [`examples/asserts/`](examples/asserts/)

## 🚀 Quick Start: Choose Your Abstraction Level

Expand Down Expand Up @@ -316,69 +339,41 @@ console.log(`Task done: ${runtime.isTaskDone}`);
---

<details>
<summary><h2>💼 Real-World Example: Amazon Shopping Bot</h2></summary>
<summary><h2>💼 Real-World Example: Assertion-driven navigation</h2></summary>

This example demonstrates navigating Amazon, finding products, and adding items to cart:
This example shows how to use **assertions + `.eventually()`** to make an agent loop resilient:

```typescript
import { SentienceBrowser, snapshot, find, click } from './src';
import { SentienceBrowser, AgentRuntime, urlContains, exists } from 'sentienceapi';
import { createTracer } from 'sentienceapi';

async function main() {
const browser = new SentienceBrowser(undefined, undefined, false);

try {
await browser.start();

// Navigate to Amazon Best Sellers
await browser.goto('https://www.amazon.com/gp/bestsellers/');
await browser.getPage().waitForLoadState('networkidle');
await new Promise(resolve => setTimeout(resolve, 2000));

// Take snapshot and find products
const snap = await snapshot(browser);
console.log(`Found ${snap.elements.length} elements`);

// Find first product in viewport using spatial filtering
const products = snap.elements.filter(
el =>
el.role === 'link' &&
el.visual_cues.is_clickable &&
el.in_viewport &&
!el.is_occluded &&
el.bbox.y < 600 // First row
);

if (products.length > 0) {
// Sort by position (left to right, top to bottom)
products.sort((a, b) => a.bbox.y - b.bbox.y || a.bbox.x - b.bbox.x);
const firstProduct = products[0];

console.log(`Clicking: ${firstProduct.text}`);
const result = await click(browser, firstProduct.id);

// Wait for product page
await browser.getPage().waitForLoadState('networkidle');
await new Promise(resolve => setTimeout(resolve, 2000));

// Find and click "Add to Cart" button
const productSnap = await snapshot(browser);
const addToCart = find(productSnap, 'role=button text~"add to cart"');

if (addToCart) {
const cartResult = await click(browser, addToCart.id);
console.log(`Added to cart: ${cartResult.success}`);
}
}
} finally {
await browser.close();
}
const browser = await SentienceBrowser.create({ apiKey: process.env.SENTIENCE_API_KEY });
const tracer = await createTracer({ runId: 'verified-run', uploadTrace: false });

const adapter = {
snapshot: async (_page: any, options?: Record<string, any>) => {
return await browser.snapshot(options);
},
};
const runtime = new AgentRuntime(adapter as any, browser.getPage() as any, tracer);

await browser.getPage().goto('https://example.com');
runtime.beginStep('Verify we are on the right page');

await runtime
.check(urlContains('example.com'), 'on_domain', true)
.eventually({ timeoutMs: 10_000, pollMs: 250, minConfidence: 0.7, maxSnapshotAttempts: 3 });

runtime.assert(exists('role=heading'), 'heading_present');

await tracer.close();
await browser.close();
}

main();
main().catch(console.error);
```

**📖 See the complete tutorial:** [Amazon Shopping Guide](../docs/AMAZON_SHOPPING_GUIDE.md)

</details>

---
Expand Down
16 changes: 16 additions & 0 deletions examples/asserts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Assertions examples (v1 + v2)

These examples focus on **AgentRuntime assertions**:

- **v1**: deterministic, state-aware assertions (enabled/checked/value/expanded) + failure intelligence
- **v2**: `.check(...).eventually(...)` retry loops with `minConfidence` gating + snapshot exhaustion

Run examples:

```bash
cd sdk-ts
npm run build
node dist/examples/asserts/v1-state-assertions.js
node dist/examples/asserts/v2-eventually-min-confidence.js
```

46 changes: 46 additions & 0 deletions examples/asserts/eventually-min-confidence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* v2: `.check(...).eventually(...)` with snapshot confidence gating + exhaustion.
*/

import { SentienceBrowser } from '../../src/browser';
import { AgentRuntime } from '../../src/agent-runtime';
import { createTracer } from '../../src/tracing/tracer-factory';
import { exists } from '../../src/verification';

async function main(): Promise<void> {
const browser = new SentienceBrowser(process.env.SENTIENCE_API_KEY);
await browser.start();

const tracer = await createTracer({ runId: 'asserts-v2', uploadTrace: false });
const adapter = {
snapshot: async (_page: any, options?: Record<string, any>) => {
return await browser.snapshot(options);
},
};
const runtime = new AgentRuntime(adapter as any, browser.getPage() as any, tracer);

await browser.getPage().goto('https://example.com');
runtime.beginStep('Assert v2 eventually');

const ok = await runtime
.check(exists("text~'Example Domain'"), 'example_domain_text', true)
.eventually({
timeoutMs: 10_000,
pollMs: 250,
minConfidence: 0.7,
maxSnapshotAttempts: 3,
snapshotOptions: { use_api: true },
});

console.log('eventually() result:', ok);
console.log('Final assertions:', runtime.getAssertionsForStepEnd().assertions);

await tracer.close();
await browser.close();
}

main().catch(err => {
console.error(err);
process.exit(1);
});

51 changes: 51 additions & 0 deletions examples/asserts/state-assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* v1: State-aware assertions with AgentRuntime.
*
* This example is meant to be run with a Pro/Enterprise API key so the Gateway
* can refine raw elements into SmartElements with state fields (enabled/checked/value/etc).
*
* Env vars:
* - SENTIENCE_API_KEY (optional but recommended for v1 state assertions)
*/

import { SentienceBrowser } from '../../src/browser';
import { AgentRuntime } from '../../src/agent-runtime';
import { createTracer } from '../../src/tracing/tracer-factory';
import { exists, isChecked, isDisabled, isEnabled, isExpanded, valueContains } from '../../src/verification';

async function main(): Promise<void> {
const browser = new SentienceBrowser(process.env.SENTIENCE_API_KEY);
await browser.start();

const tracer = await createTracer({ runId: 'asserts-v1', uploadTrace: false });

// AgentRuntime in TS expects a minimal adapter with snapshot(page, options).
const adapter = {
snapshot: async (_page: any, options?: Record<string, any>) => {
return await browser.snapshot(options);
},
};

const runtime = new AgentRuntime(adapter as any, browser.getPage() as any, tracer);

await browser.getPage().goto('https://example.com');
runtime.beginStep('Assert v1 state');
await runtime.snapshot({ use_api: true }); // Pro tier (Gateway refinement) if api key is present

runtime.assert(exists('role=heading'), 'has_heading');
runtime.assert(isEnabled('role=link'), 'some_link_enabled');
runtime.assert(isDisabled("role=button text~'continue'"), 'continue_disabled_if_present');
runtime.assert(isChecked("role=checkbox name~'subscribe'"), 'subscribe_checked_if_present');
runtime.assert(isExpanded("role=button name~'more'"), 'more_is_expanded_if_present');
runtime.assert(valueContains("role=textbox name~'email'", '@'), 'email_has_at_if_present');

console.log('Assertions recorded:', runtime.getAssertionsForStepEnd().assertions);
await tracer.close();
await browser.close();
}

main().catch(err => {
console.error(err);
process.exit(1);
});

Loading
Loading