# Functions

<div class="view-markdown-wrapper">
<ViewMarkdown />
</div>

Functions are reusable code components that can be called from your tests, providing powerful capabilities for complex operations and integrations.

## Table of Contents

1. [Creating and Managing Functions](#creating-and-managing-functions)
2. [Available Parameters](#available-parameters)
3. [Function Editor Features](#function-editor-features)
4. [Importing Packages](#importing-packages)
5. [Common Use Cases](#common-use-cases)
   - [API Integrations](#api-integrations)
   - [Complex Operations](#complex-operations)
   - [File Processing](#file-processing)
6. [Using Functions in Tests](#using-functions-in-tests)
7. [Function Testing](#function-testing)
8. [Function Status](#function-status)
9. [Limitations](#limitations)
10. [Best Practices](#best-practices)

## Creating and Managing Functions

Functions are created and managed from the **Functions** sidebar menu (not from within tests):

1. Navigate to **Functions** in the sidebar
2. Click **Create Function** to open the function editor
3. Define your function with:
   - **Name** - Unique identifier for the function
   - **Description** - What the function does
   - **Code** - The function implementation
   - **Test Code** - "Try it out" test to validate the function

## Available Parameters

Every function receives three pre-defined parameters that are automatically passed when the function is called:

| Parameter     | Type                                                                                      | Description                                        |
| ------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `page`        | Playwright [`Page`](https://playwright.dev/docs/api/class-page)                           | The current browser page                           |
| `testContext` | Shiplight variable store                                                                  | Read and write test variables. Aliases: `$`, `ctx` |
| `request`     | Playwright [`APIRequestContext`](https://playwright.dev/docs/api/class-apirequestcontext) | For making HTTP requests                           |

The following are available as globals inside function code (no need to add them to the function signature):

| Variable  | Type                  | Description                                                      |
| --------- | --------------------- | ---------------------------------------------------------------- |
| `agent`   | Shiplight `WebAgent`  | AI-powered actions — assertions, natural language step execution |
| `expect`  | Playwright assertions | For writing assertions                                           |
| `console` | Captured console      | Logs appear in the test run output                               |

Your function signature should always start with the pre-defined parameters, followed by any custom parameters:

```javascript
async function my_function(page, testContext, request, customParam1, customParam2) {
  // Use page for browser interactions
  await page.click("#button");

  // Use request for API calls
  const response = await request.get("https://api.example.com/data");

  // Use testContext to store/retrieve variables
  testContext.myValue = "stored value";
  $.anotherValue = "also stored"; // $ is an alias for testContext

  // Use agent (available as global)
  await agent.assert(page, "The button was clicked successfully");
}
```

### Working with `testContext`

`testContext` (also `$` and `ctx`) is a proxy to the test's variable store. Values set here are available in all subsequent steps — including natural language steps — for the duration of the test run.

```javascript
// Write a variable
testContext.orderId = "12345";

// Read a variable
const id = testContext.orderId;

// Write a sensitive value (masked in logs)
testContext.set("apiToken", "secret-value", true);

// Read explicitly
const token = testContext.get("apiToken");
```

### Using `agent`

The `agent` object gives you access to Shiplight's AI capabilities from inside a function.

**`agent.assert(page, statement)`** — AI-powered assertion. The agent looks at the current page and verifies whether the statement is true.

```javascript
await agent.assert(page, "The shopping cart shows 3 items");
```

**`agent.execute(page, statement)`** — Execute a natural language instruction. The agent reads the page and performs the described action, just like a natural language step.

```javascript
await agent.execute(page, "Fill out the registration form with test data");
```

**`agent.extract(page, description, variableName)`** — Extract a value from the page using AI and store it in `testContext`.

```javascript
await agent.extract(page, "the order confirmation number", "orderNumber");
```

## Function Editor Features

The function editor provides a tabbed interface for managing your functions:

### Editor Tabs

The function editor includes three main tabs:

- **Overview** - The main editing interface where you define your function:
  - **Split-pane interface** - Code editor on left, live browser view on right
  - **Live browser preview** - If your function operates on the page, see real-time results
  - **"Try it out" testing** - Write and run test code that calls your function
  - **Status management** - Draft or Active

- **Usage** - View which of your tests are using this function

- **Settings** - Configure function-specific settings and preferences, such as selecting the environment to run test code

## Importing Packages

You can use `await import()` to import Node.js built-in modules and pre-installed npm packages inside a function.

```javascript
async function read_downloaded_pdf(page, testContext, request) {
  const fs = await import("node:fs");
  let pdfParse = await import("pdf-parse");
  pdfParse = pdfParse.default || pdfParse;

  const filePath = agent.getRecentDownloadedFilePath();
  const buffer = fs.readFileSync(filePath);
  const result = await pdfParse(buffer);
  testContext.pdfText = result.text;
}
```

### Pre-installed Packages

The following third-party packages are available out of the box:

| Package     | Description                                    |
| ----------- | ---------------------------------------------- |
| `pdf-parse` | Extract text from PDF files                    |
| `mammoth`   | Convert Word documents (.docx) to text or HTML |
| `exceljs`   | Read and write Excel files (.xlsx)             |

Node.js built-in modules (`fs`, `path`, `crypto`, etc.) are always available.

::: warning Dynamic import only
Static `import` statements (e.g., `import fs from 'fs'`) are **not supported** — only `await import()` works. This is because function code runs as an inline script, not as an ES module.
:::

::: tip CommonJS packages
Some older packages only expose a `default` export when imported this way. If destructuring doesn't work, access the module via `.default`:

```javascript
const mod = await import("some-package");
const lib = mod.default;
```

:::

## Common Use Cases

### API Integrations

```javascript
async function get_order_details(page, testContext, request, orderId) {
  const response = await request.get(`${testContext.apiUrl}/orders/${orderId}`, {
    headers: { Authorization: `Bearer ${testContext.apiToken}` },
  });
  const data = await response.json();

  // Store results in test context for subsequent steps to access
  $.orderData = data;
  testContext.orderId = data.id;
  $.orderStatus = data.status;
}
```

### Complex Operations

```javascript
async function authenticate_with_mfa(page, testContext, request, username, password) {
  await page.fill("#username", username);
  await page.fill("#password", password);
  await page.click("#login");

  // Wait for MFA prompt
  await page.waitForSelector("#mfa-code");

  // Generate TOTP code from secret key
  const crypto = await import("node:crypto");
  const mfaCode = generateTOTP(testContext.get("otp_secret_key"), crypto);
  await page.fill("#mfa-code", mfaCode);
  await page.click("#verify");

  // Verify login succeeded
  await agent.assert(page, "The user is logged in and on the dashboard");
}
```

### File Processing

```javascript
async function verify_excel_report(page, testContext, request) {
  await agent.waitForDownloadComplete(page, 10);
  const filePath = agent.getRecentDownloadedFilePath();

  let ExcelJS = await import("exceljs");
  ExcelJS = ExcelJS.default || ExcelJS;
  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.readFile(filePath);

  const sheet = workbook.getWorksheet(1);
  testContext.rowCount = sheet.rowCount;
  testContext.headers = sheet.getRow(1).values;
}
```

```javascript
async function verify_word_document(page, testContext, request) {
  await agent.waitForDownloadComplete(page, 10);
  const filePath = agent.getRecentDownloadedFilePath();

  let mammoth = await import("mammoth");
  mammoth = mammoth.default || mammoth;
  const result = await mammoth.extractRawText({ path: filePath });
  testContext.documentText = result.value;
}
```

## Using Functions in Tests

Once created, functions can be called from your tests in two ways:

### Using the Function Statement (Easy)

1. Insert a new statement in your test
2. From the dropdown menu, switch to **Function** (or press `Ctrl + Alt + F`)
3. Pick a function from the list
4. Fill in the parameters

This is the recommended approach for most users.

### Using a Code Block (Programmatic Way)

Create a [Code step](/cloud/test-editor/code-step) and call the function directly by name:

```javascript
await get_order_details(page, testContext, request, $.orderId);
console.log($.orderTotal); // Access stored value
```

### Accessing Function Results

**Important:** Functions cannot directly return values to tests. Instead, store values in `testContext`:

```javascript
// In your function:
async function get_order_details(page, testContext, request, orderId) {
  const response = await request.get(`/api/orders/${orderId}`);
  const data = await response.json();

  // Store results in test context instead of returning
  $.orderTotal = data.total;
  $.orderStatus = data.status;
  ctx.orderItems = data.items;
}
```

## Function Testing

Each function includes a "Try it out" section where you can:

- Write test code that calls your function
- Execute the test to verify function behavior
- See live browser results if the function interacts with pages
- Debug and refine your function implementation

## Function Status

Functions have two status levels:

- **Draft** - In development, can be edited and tested
- **Active** - Ready for use in tests

## Limitations

- **Dynamic import only.** Static `import` statements are not supported. Use `await import()` instead. See [Importing Packages](#importing-packages).
- **Pre-installed packages only.** You can only import packages that are already available in the Shiplight runtime. You cannot install arbitrary npm packages.
- **No return values.** Functions cannot return values to tests. Use `testContext` to pass data between functions and steps.
- **Node.js context only.** Function code runs on the Node.js side, not in the browser. Use `page.evaluate()` to access browser globals like `window`, `document`, or `localStorage`.

## Best Practices

- **Keep functions focused** — each function should do one thing well with a clear input/output contract.
- **Document your functions** — write clear descriptions and document expected parameters.
- **Use `testContext` for output** — store all results in `testContext` so subsequent steps can access them.
- **Test thoroughly** — use the "Try it out" feature to validate, including edge cases and error conditions.
- **Handle errors** — throw meaningful errors so test failures are easy to diagnose.
