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

# GUI Testing

> Testing Minecraft inventory GUIs is a core feature of the framework.

The API uses live handles and locators, inspired by Playwright, for reactive and reliable GUI interactions.

## Quick Start

```javascript theme={null}
test('interact with shop GUI', async ({ player }) => {
  player.chat('/shop');
  
  // Get a live handle to the GUI
  const gui = await player.gui({ title: /Shop/ });
  
  // Create a locator for an item
  const diamondItem = gui.locator(item => item.name.includes('diamond'));
  
  // Click the item (with automatic retry)
  await diamondItem.click();
  
  await expect(player).toHaveReceivedMessage('Purchased');
});
```

## Core Concepts

### Live Handles vs Snapshots

**Live Handle** (`LiveGuiHandle`) - Always reflects the player's current GUI state:

* Returned by `player.gui({ title })`
* Automatically updates when GUI changes
* Use for dynamic, reactive testing

**Snapshot** (`GuiWrapper`) - Captures GUI state at a specific moment:

* Legacy API, now deprecated
* Use live handles for new code

### Locators

Locators are queries that re-evaluate each time they're used:

```javascript theme={null}
const item = gui.locator(i => i.name === 'diamond');
// Locator doesn't fetch immediately - it's a query definition
```

## Modern API

### `player.gui({ title, timeout? })`

Gets a live handle to a GUI matching the title pattern.

<ParamField path="title" type="string | RegExp" required>
  Pattern to match GUI title.
</ParamField>

<ParamField path="timeout" type="number">
  Max wait time in ms (default: 5000).
</ParamField>

**Returns:** `LiveGuiHandle`

**Examples:**

```javascript theme={null}
// String match
const guiStr = await player.gui({ title: 'Shop' });

// RegEx match
const guiRegex = await player.gui({ title: /Staff Activity/i });

// Custom timeout
const guiTimeout = await player.gui({ title: 'Admin', timeout: 10000 });
```

### `gui.locator(predicate)`

Creates a locator for items matching the predicate.

<ParamField path="predicate" type="function" required>
  `(item: ItemWrapper) => boolean`
</ParamField>

**Returns:** `GuiItemLocator`

**Examples:**

```javascript theme={null}
// By item name
const compass = gui.locator(i => i.name === 'compass');

// By display name
const itemByName = gui.locator(i => i.getDisplayName().includes('Session'));

// By lore
const itemByLore = gui.locator(i => i.hasLore('Click to view'));

// Multiple conditions
const complexItem = gui.locator(i => 
  i.name === 'paper' && i.count > 1
);
```

### `locator.click(options?)`

Clicks the located item with automatic retry.

<ParamField path="options.timeout" type="number">
  Max wait time in ms (default: 5000ms).
</ParamField>

**Examples:**

```javascript theme={null}
const item = gui.locator(i => i.name === 'diamond');
await item.click();

// Custom timeout
await item.click({ timeout: 10000 });
```

### `locator.displayName()`

Gets the display name of the located item (re-queries each call).

```javascript theme={null}
const item = gui.locator(i => i.name === 'paper');
const name = item.displayName();
console.log(name); // "Session Log"
```

### `locator.loreText()`

Gets the lore text of the located item (re-queries each call).

```javascript theme={null}
const item = gui.locator(i => i.name === 'clock');
const lore = item.loreText();
console.log(lore); // "From: 2024-01-01 To: 2024-01-02"
```

### `expect(locator).toHaveLore(text, options?)`

Asserts that a locator's item contains specific lore text, with automatic retry.

<ParamField path="text" type="string" required>
  Text that should appear in lore.
</ParamField>

<ParamField path="options.timeout" type="number">
  Max wait time in ms (default: 5000ms).
</ParamField>

<ParamField path="options.pollingRate" type="number">
  Check interval in ms (default: 100ms).
</ParamField>

**Examples:**

```javascript theme={null}
const item = gui.locator(i => i.name === 'clock');

// Basic assertion
await expect(item).toHaveLore('Session');

// With timeout
await expect(item).toHaveLore('messages', { timeout: 10000 });

// Negative assertion
await expect(item).not.toHaveLore('error');
```

### `gui.title`

Gets the current GUI title (or undefined if closed).

```javascript theme={null}
const gui = await player.gui({ title: 'Shop' });
console.log(gui.title); // "Shop Menu"
```

## Complete Examples

### Permission Test

```javascript theme={null}
test('command requires permission', async ({ player }) => {
  player.chat('/admin');
  await expect(player).toHaveReceivedMessage('You don\'t have permission');
});

test('admin can open admin panel', async ({ player }) => {
  await player.makeOp();
  player.chat('/admin');
  
  const gui = await player.gui({ title: 'Admin' });
  expect(gui.title).toContain('Admin');
});
```

### Item Interaction

```javascript theme={null}
test('clicking GUI item triggers callback', async ({ player }) => {
  await player.makeOp();
  player.chat('/example gui-settings');
  
  const gui = await player.gui({ title: 'guiSettings' });
  const item = gui.locator(item => item.getDisplayName().includes('guiItemInfo'));
  
  await item.click();
  await expect(player).toHaveReceivedMessage('You clicked on item');
});
```

### Dynamic Lore Testing

```javascript theme={null}
test('activity log shows correct data', async ({ player }) => {
  await player.makeOp();
  player.chat('/staffactivity view');
  
  const gui = await player.gui({ title: /Staff activity/ });
  
  // Locator for message counter
  const messageItem = gui.locator(i => i.hasLore('messages'));
  
  // Wait for lore to update
  await expect(messageItem).toHaveLore('messages');
  
  // Verify lore contains expected data
  const lore = messageItem.loreText();
  expect(lore).toContain('Session');
});
```

### Paginated GUIs

```javascript theme={null}
test('navigate through pages', async ({ player }) => {
  player.chat('/warps');
  const gui = await player.gui({ title: 'Warps' });
  
  // Check first page
  const firstItem = gui.locator(i => i.getDisplayName().includes('Spawn'));
  await firstItem.click();
  
  // Reopen and check next page
  player.chat('/warps');
  const nextButton = gui.locator(i => i.name === 'arrow');
  await nextButton.click();
  
  const secondItem = gui.locator(i => i.getDisplayName().includes('Arena'));
  await expect.poll(() => secondItem.displayName()).toContain('Arena');
});
```

## Player Helper Methods

### `player.makeOp()`

Grants operator status to the player.

```javascript theme={null}
await player.makeOp();
player.chat('/admin'); // Now has permission
```

### `player.deOp()`

Removes operator status from the player.

```javascript theme={null}
await player.deOp();
```

### `player.setGameMode(mode)`

Changes the player's game mode.

<ParamField path="mode" type="string" required>
  'survival' | 'creative' | 'adventure' | 'spectator'
</ParamField>

```javascript theme={null}
await player.setGameMode('creative');
```

### `player.teleport(x, y, z)`

Teleports the player to coordinates.

```javascript theme={null}
await player.teleport(0, 100, 0);
```

### `player.giveItem(item, count?)`

Gives items to the player.

```javascript theme={null}
await player.giveItem('diamond', 64);
```

## Legacy API (Deprecated)

<Warning>
  The following methods are deprecated. Use the modern API instead.

  * `player.waitForGui(guiMatcher, options?)` -> Use `player.gui({ title })`
  * `gui.findItem(predicate)` -> Use `gui.locator(predicate)`
  * `gui.clickItem(predicate)` -> Use `gui.locator(predicate).click()`
</Warning>

## Tips

<Tip>
  * GUI operations are async - always use `await`
  * Locators are reactive - they re-query on each use
  * Use `expect(locator).toHaveLore()` for dynamic content
  * Custom display names differ from Minecraft item names
  * Check logs if GUI doesn't open - the command might have failed
</Tip>
