> ## Documentation Index
> Fetch the complete documentation index at: https://docs.morphllm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Glance

> Vision model trained to test code changes from a diff

<img src="https://mintcdn.com/morph-555d6c14/6bm3KIDcGh3tlVrb/images/browser-testing.webp?fit=max&auto=format&n=6bm3KIDcGh3tlVrb&q=85&s=3146cd49f725fc53a65e4d8fab1098ae" alt="Glance Browser Testing" width="100%" style={{ borderRadius: 8, marginBottom: 24 }} data-path="images/browser-testing.webp" />

**Glance** is Morph's vision model, trained specifically to test code changes. Give it a diff and a URL—it figures out what to test and returns video, screenshots, errors, and network logs.

## What You Get Back

| Output            | Description                                       |
| ----------------- | ------------------------------------------------- |
| **Video**         | MP4/WebM recording of the entire test session     |
| **Animated WebP** | Embeddable in GitHub PRs, Slack, Notion, and more |
| **Screenshots**   | Per-step snapshots for debugging                  |
| **Errors**        | Console errors, exceptions, and failed assertions |
| **Network logs**  | All HTTP requests/responses during the test       |

Embed test recordings directly into your PRs, or use the [SDK](/sdk/overview) to integrate into your product—post results to your users' PRs, Slack, Linear, or anywhere else.

## Quick Start

```typescript theme={null}
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: "YOUR_API_KEY" });

const result = await morph.browser.execute({
  url: "https://preview-abc.vercel.app",
  diff: prDiff,  // Glance uses the diff to decide what to test
  task: "Test the changes in this PR",
  recordVideo: true
});

// What you get back
console.log(result.success);      // Agent's pass/fail assessment
console.log(result.result);       // What it found

if (result.recordingId) {
  const recording = await morph.browser.getRecording(result.recordingId);
  console.log(recording.videoUrl);    // Full video
  console.log(recording.networkUrl);  // Network logs
  console.log(recording.consoleUrl);  // Console/errors

  // Get embeddable WebP for PRs, Slack, etc.
  const { webpUrl } = await recording.getWebp({ maxSizeMb: 5 });
  console.log(`![Test Recording](${webpUrl})`);  // Markdown-ready
}
```

## Integration Options

<CardGroup cols={2}>
  <Card title="Browser Automation" icon="play" href="/sdk/components/automation/browser/direct">
    Managed browser execution—we run the browser, you get results. Best for most use cases.
  </Card>

  <Card title="Agent Tool" icon="robot" href="/sdk/components/automation/browser/tool">
    Use as a tool in your AI agents (Anthropic, OpenAI, Vercel AI SDK).
  </Card>

  <Card title="GitHub Actions" icon="github" href="/sdk/components/automation/browser/github-actions">
    Automatic PR preview testing with results posted as comments.
  </Card>

  <Card title="Harness API" icon="code" href="#harness-api">
    BYO browser (Playwright, Puppeteer, Browserbase)—Glance provides the decisions.
  </Card>
</CardGroup>

***

## Harness API

Use this when you want to run your own browser and use Glance as the "brain" for step-by-step decisions.

### Endpoints

* **Create session**: `POST /harness/sessions`
* **Next step**: `POST /harness/sessions/{session_id}/step`

### Flow

1. Create a session with `url`, `diff`, `instructions`, and your `tools`
2. Take a screenshot in your browser
3. Send the screenshot to `/step`, get back `tool_calls`
4. Execute the tool calls, take a new screenshot, repeat

```typescript theme={null}
// 1. Create session
const session = await fetch("https://browser.morphllm.com/harness/sessions", {
  method: "POST",
  headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    url: "https://myapp.com",
    diff: prDiff,
    instructions: "Test the login flow",
    tools: [{ name: "click", description: "Click at coordinates", parameters: { /* ... */ } }]
  })
}).then(r => r.json());

// 2. Step loop
let done = false;
while (!done) {
  const screenshot = await page.screenshot({ encoding: "base64" });

  const step = await fetch(`https://browser.morphllm.com/harness/sessions/${session.session_id}/step`, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ screenshot, context: { current_url: page.url() } })
  }).then(r => r.json());

  // Execute tool_calls in your browser
  for (const call of step.tool_calls) {
    if (call.name === "click") await page.mouse.click(call.args.x, call.args.y);
    if (call.name === "type_text") await page.keyboard.type(call.args.text);
  }

  done = step.is_done;
}
```

### Step Response

```json theme={null}
{
  "step_index": 3,
  "tool_calls": [{ "name": "click", "args": { "x": 942, "y": 20 } }],
  "is_done": false,
  "done_reason": null
}
```

<Tip>
  Send `client_step_id` with each step for idempotency—retries won't create duplicate steps.
</Tip>
