Skip to content

Commit 6fd10ab

Browse files
authored
Merge pull request #1 from SentienceAPI/week2
Week 2 completed
2 parents 5d48f5a + 4920db1 commit 6fd10ab

7 files changed

Lines changed: 126 additions & 30 deletions

File tree

README.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ TypeScript SDK for Sentience AI Agent Browser Automation.
1010
cd sdk-ts
1111
npm install
1212
npm run build
13+
14+
# Install Playwright browsers (required)
15+
npx playwright install chromium
1316
```
1417

1518
## Quick Start
@@ -42,6 +45,29 @@ async function main() {
4245
}
4346
```
4447

48+
## Running Examples
49+
50+
**⚠️ Important**: You cannot use `node` directly to run TypeScript files. Use one of these methods:
51+
52+
**Option 1: Using npm scripts (recommended)**
53+
```bash
54+
npm run example:hello
55+
npm run example:basic
56+
```
57+
58+
**Option 2: Using ts-node directly**
59+
```bash
60+
npx ts-node examples/hello.ts
61+
# or if ts-node is installed globally:
62+
ts-node examples/hello.ts
63+
```
64+
65+
**Option 3: Compile then run (not recommended for examples)**
66+
```bash
67+
npm run build
68+
# Examples would need to be compiled separately
69+
```
70+
4571
## Features
4672

4773
### Day 2: Browser Harness
@@ -78,12 +104,6 @@ See `examples/` directory:
78104
- `query-demo.ts` - Query engine
79105
- `wait-and-click.ts` - Wait and actions
80106

81-
Run examples:
82-
```bash
83-
npm run example:hello
84-
npm run example:basic
85-
```
86-
87107
## Testing
88108

89109
```bash

examples/basic-agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Day 3 Example: Basic snapshot functionality
33
*/
44

5-
import { SentienceBrowser, snapshot } from '../src';
5+
import { SentienceBrowser, snapshot } from '../src/index';
66
import * as fs from 'fs';
77

88
async function main() {

examples/hello.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,59 @@
22
* Day 2 Example: Verify extension bridge is loaded
33
*/
44

5-
import { SentienceBrowser } from '../src';
5+
import { SentienceBrowser } from '../src/index';
66

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

1010
try {
1111
await browser.start();
1212

13+
// Browser.start() already navigates to example.com, but we can navigate elsewhere if needed
14+
// The extension should already be loaded at this point
15+
1316
// Check if extension API is available
1417
const bridgeOk = await browser.getPage().evaluate(
15-
() => typeof (window as any).sentience !== 'undefined'
18+
() => {
19+
const win = window as any;
20+
return typeof win.sentience !== 'undefined' &&
21+
typeof win.sentience.snapshot === 'function';
22+
}
1623
);
1724

1825
console.log(`bridge_ok=${bridgeOk}`);
1926

2027
if (bridgeOk) {
2128
console.log('✅ Extension loaded successfully!');
29+
// Try a quick snapshot to verify it works
30+
try {
31+
const result = await browser.getPage().evaluate(
32+
() => (window as any).sentience.snapshot({ limit: 1 })
33+
);
34+
if (result.status === 'success') {
35+
console.log(`✅ Snapshot test: Found ${result.elements?.length || 0} elements`);
36+
} else {
37+
console.log(`⚠️ Snapshot returned: ${result.status}`);
38+
}
39+
} catch (e: any) {
40+
console.log(`⚠️ Snapshot test failed: ${e.message}`);
41+
}
2242
} else {
2343
console.log('❌ Extension not loaded');
44+
// Debug info
45+
const debugInfo = await browser.getPage().evaluate(() => {
46+
const win = window as any;
47+
return {
48+
sentience_defined: typeof win.sentience !== 'undefined',
49+
registry_defined: typeof win.sentience_registry !== 'undefined',
50+
snapshot_defined: typeof win.sentience?.snapshot !== 'undefined'
51+
};
52+
});
53+
console.log(`Debug info: ${JSON.stringify(debugInfo, null, 2)}`);
2454
}
55+
} catch (e: any) {
56+
console.error(`❌ Error: ${e.message}`);
57+
console.error(e.stack);
2558
} finally {
2659
await browser.close();
2760
}

examples/query-demo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Day 4 Example: Query engine demonstration
33
*/
44

5-
import { SentienceBrowser, snapshot, query, find } from '../src';
5+
import { SentienceBrowser, snapshot, query, find } from '../src/index';
66

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

examples/wait-and-click.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Day 5-6 Example: Wait for element and click
33
*/
44

5-
import { SentienceBrowser, snapshot, find, waitFor, click, expect } from '../src';
5+
import { SentienceBrowser, snapshot, find, waitFor, click, expect } from '../src/index';
66

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

src/browser.ts

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,16 @@ export class SentienceBrowser {
1919
) {}
2020

2121
async start(): Promise<void> {
22-
// Get extension source path
23-
const repoRoot = path.resolve(__dirname, '../../..');
22+
// Get extension source path (relative to project root)
23+
// Handle both ts-node (src/) and compiled (dist/src/) cases
24+
let repoRoot: string;
25+
if (__dirname.includes('dist')) {
26+
// Compiled: dist/src/ -> go up 3 levels to project root (Sentience/)
27+
repoRoot = path.resolve(__dirname, '../../..');
28+
} else {
29+
// ts-node: src/ -> go up 2 levels to project root (Sentience/)
30+
repoRoot = path.resolve(__dirname, '../..');
31+
}
2432
const extensionSource = path.join(repoRoot, 'sentience-chrome');
2533

2634
if (!fs.existsSync(extensionSource)) {
@@ -32,7 +40,7 @@ export class SentienceBrowser {
3240

3341
// Create temporary extension bundle
3442
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sentience-ext-'));
35-
this.extensionPath = tempDir;
43+
this.extensionPath = tempDir; // tempDir is already a string
3644

3745
// Copy extension files
3846
const filesToCopy = [
@@ -61,8 +69,8 @@ export class SentienceBrowser {
6169
this.browser = await chromium.launch({
6270
headless: this.headless,
6371
args: [
64-
`--load-extension=${tempDir.name}`,
65-
`--disable-extensions-except=${tempDir.name}`,
72+
`--load-extension=${tempDir}`,
73+
`--disable-extensions-except=${tempDir}`,
6674
],
6775
});
6876

@@ -74,8 +82,28 @@ export class SentienceBrowser {
7482
// Create page
7583
this.page = await this.context.newPage();
7684

77-
// Wait for extension
78-
await this.waitForExtension();
85+
// Navigate to a real page so extension can inject
86+
// Extension content scripts only run on actual pages (not about:blank)
87+
// Use a simple page that loads quickly
88+
await this.page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
89+
90+
// Give extension time to initialize (WASM loading is async)
91+
await this.page.waitForTimeout(1000);
92+
93+
// Wait for extension to load
94+
if (!(await this.waitForExtension())) {
95+
// Extension might need more time, try waiting a bit longer
96+
await this.page.waitForTimeout(2000);
97+
if (!(await this.waitForExtension())) {
98+
throw new Error(
99+
'Extension failed to load after navigation. Make sure:\n' +
100+
'1. Extension is built (cd sentience-chrome && ./build.sh)\n' +
101+
'2. All files are present (manifest.json, content.js, injected_api.js, pkg/)\n' +
102+
'3. Check browser console for errors\n' +
103+
`4. Extension path: ${tempDir}`
104+
);
105+
}
106+
}
79107
}
80108

81109
private copyDirectory(src: string, dest: string): void {
@@ -97,27 +125,36 @@ export class SentienceBrowser {
97125
}
98126
}
99127

100-
private async waitForExtension(timeout: number = 10000): Promise<boolean> {
128+
private async waitForExtension(timeout: number = 15000): Promise<boolean> {
101129
if (!this.page) return false;
102130

103131
const start = Date.now();
104132
while (Date.now() - start < timeout) {
105133
try {
106134
const result = await this.page.evaluate(() => {
107-
return (
108-
typeof (window as any).sentience !== 'undefined' &&
109-
typeof (window as any).sentience.snapshot === 'function'
110-
);
135+
// Check if sentience API exists
136+
if (typeof (window as any).sentience === 'undefined') {
137+
return { ready: false, reason: 'window.sentience not defined' };
138+
}
139+
// Check if snapshot function exists
140+
if (typeof (window as any).sentience.snapshot !== 'function') {
141+
return { ready: false, reason: 'snapshot function not available' };
142+
}
143+
// Check if WASM module is loaded
144+
if ((window as any).sentience_registry === undefined) {
145+
return { ready: false, reason: 'registry not initialized' };
146+
}
147+
return { ready: true };
111148
});
112149

113-
if (result) {
150+
if (result && (result as any).ready) {
114151
return true;
115152
}
116153
} catch (e) {
117-
// Ignore errors during initialization
154+
// Continue waiting on errors
118155
}
119156

120-
await new Promise((resolve) => setTimeout(resolve, 100));
157+
await new Promise((resolve) => setTimeout(resolve, 200));
121158
}
122159

123160
return false;

tsconfig.json

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"compilerOptions": {
33
"target": "ES2020",
44
"module": "commonjs",
5-
"lib": ["ES2020"],
5+
"lib": ["ES2020", "DOM"],
66
"outDir": "./dist",
77
"rootDir": "./src",
88
"strict": true,
@@ -11,9 +11,15 @@
1111
"forceConsistentCasingInFileNames": true,
1212
"declaration": true,
1313
"declarationMap": true,
14-
"sourceMap": true
14+
"sourceMap": true,
15+
"types": ["node"]
1516
},
16-
"include": ["src/**/*"],
17-
"exclude": ["node_modules", "dist", "tests"]
17+
"include": ["src/**/*", "examples/**/*"],
18+
"exclude": ["node_modules", "dist", "tests"],
19+
"ts-node": {
20+
"compilerOptions": {
21+
"lib": ["ES2020", "DOM"]
22+
}
23+
}
1824
}
1925

0 commit comments

Comments
 (0)