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

# Write a Factory suite

> Define the steps of a Factory suite in Python, prompt the operator, and report pass or fail.

A Factory suite is Python code that runs in the Lager container on a box. The entry point defines a class for each step. It lists the steps in order, and it passes the list to `run()`.

A step can drive hardware with the [Lager Python API](https://docs.lagerdata.com/source/reference/python/overview). It can also ask the operator for input, and it reports pass or fail.

## A small suite

This suite has three steps. It also needs the file `images/fixture.jpg` in the suite.

```python theme={null}
from factory import Step, run


class PowerOn(Step):
    DisplayName = "Power on the board"
    Description = "Put the board in the fixture. Click Pass when the LED is on."
    Image = "images/fixture.jpg"

    def run(self):
        self.log("Checking the power LED")
        return self.present_pass_fail_buttons(timeout=120)


class ScanSerial(Step):
    DisplayName = "Scan the serial number"

    def run(self):
        serial = self.present_text_input("Scan the label", placeholder="SN-0000", size=16)
        if not serial:
            return False
        self.state["serial"] = serial
        self.update_heading(f"Board {serial}")


class Record(Step):
    DisplayName = "Record the result"

    def run(self):
        self.log(f"Serial {self.state['serial']} passed")


STEPS = [
    PowerOn,
    ScanSerial,
    Record,
]

run(STEPS)
```

## Import the module

The suite can import the Factory module in any of these ways:

```python theme={null}
from factory import Step, run
from lager.factory import Step, get_secret
from lager import factory
```

## How Stout runs the entry point

* Stout copies all suite files to a temporary folder on the box. It deletes the folder when the run ends.
* Stout runs the entry point as `__main__`, with the suite folder as the working directory.
* The folder of the entry point is first on `sys.path`. The entry point can import the other Python files in the suite.
* The Factory module reads the answers of the operator from standard input. Your code must not read standard input.

## Steps

A step is a class that extends `Step` and has a `run()` method. These class attributes control how the step shows in the dashboard.

| Attribute     | Default        | Meaning                                                                                             |
| ------------- | -------------- | --------------------------------------------------------------------------------------------------- |
| `DisplayName` | The class name | The name of the step on the suite page and the run page                                             |
| `Description` | Empty          | The instructions for the operator                                                                   |
| `Image`       | None           | An image for the step: the path of a file in the suite, such as `images/fixture.jpg`, or a full URL |
| `Link`        | None           | A URL that shows as a link on the run page                                                          |
| `StopOnFail`  | `True`         | If `True`, the run stops after this step fails                                                      |

Always set `DisplayName`. Without it, the suite page and the run page can show different names for the step.

### Rules for the step list

The dashboard reads the step list from the entry point without running it. For the dashboard to find your steps, follow these rules:

* Define each step class at the top level of the entry point, as `class Name(Step):` or `class Name(factory.Step):`.
* Write `DisplayName`, `Description`, `Image`, and `Link` as string literals. A string in parentheses can continue across lines.
* List the steps in a top-level `STEPS = [...]` list, with one class name on each line.
* Pass the same list to `run()`.

If the entry point has no `STEPS` list, the dashboard uses every step class in file order.

## Pass and fail

* A step fails when its `run()` method raises an exception or returns `False`.
* Any other return value counts as a pass. This includes `None`.
* After a step fails, the run stops. To continue after a failure, set `StopOnFail = False` on the step.
* An exception outside a step ends the whole run with an error.

## Share data between steps

`self.state` is a dictionary that all steps in one run share. A step can store a value, such as a serial number, and a later step can read it.

## Clean up after the run

`run()` accepts a second argument, a finalizer class. Stout runs the finalizer after the steps, also when a step fails. Use a finalizer to turn off power or to close connections.

```python theme={null}
class PowerOff(Step):
    def run(self):
        self.log("Turning the fixture power off")


run(STEPS, PowerOff)
```

If the finalizer raises an exception, the console shows the error. The error does not change the result of the run.

## Prompt the operator

Each of these methods shows a prompt and waits for the answer.

| Method                                                                        | Shows                                         | Returns                           |
| ----------------------------------------------------------------------------- | --------------------------------------------- | --------------------------------- |
| `self.present_buttons(*labels, timeout=None)`                                 | One button for each label                     | The label of the selected button  |
| `self.present_pass_fail_buttons(*, timeout=None)`                             | **Pass** and **Fail** buttons                 | `True` for Pass, `False` for Fail |
| `self.present_text_input(prompt, *, placeholder='', size=None, timeout=None)` | A text field. `size` sets the maximum length. | The text                          |
| `self.present_radios(prompt, options, *, timeout=None)`                       | Radio buttons                                 | The selected option               |
| `self.present_checkboxes(prompt, options, *, timeout=None)`                   | Checkboxes                                    | A list of the selected options    |
| `self.present_select(prompt, options, *, timeout=None)`                       | A drop-down list                              | The selected option               |

The text field does not accept an empty answer.

### Time limits

`timeout` is a time limit in seconds. If nobody answers in time, Stout answers for the operator, and the console shows a message.

* `present_pass_fail_buttons()` returns `False`.
* `present_buttons()` returns the `Fail` label if there is one. Otherwise it returns the last label.
* `present_text_input()`, `present_radios()`, and `present_select()` return `None`.
* `present_checkboxes()` returns `[None]`.

## Show information

| Method                               | Result                                                                             |
| ------------------------------------ | ---------------------------------------------------------------------------------- |
| `self.log(message)`                  | Adds a line to the console. The line stays on the run page after the run ends.     |
| `self.update_heading(heading)`       | Replaces the heading of the current step.                                          |
| `self.present_link(label, url)`      | Shows a link under the current step.                                               |
| `self.present_image(url, *, alt='')` | Shows an image under the current step. The path of a file in the suite also works. |

Output from `print()` also shows in the console during the run. After the run ends, the run page does not keep that output. To keep a message, use `self.log()`.

## Read variables and secrets

Before each run, Stout sends the variables and secrets for the box. See [Variables and secrets](/source/organizations/variables-and-secrets).

In a Factory suite, read a value with `get_secret()`:

```python theme={null}
from factory import get_secret

api_token = get_secret("DUT_API_TOKEN")
```

`get_secret(name)` returns the value from the environment. If the environment does not have the name, it reads the secrets file on the box. If neither one has the name, it returns an empty string.

<CardGroup cols={2}>
  <Card title="Factory" href="/source/factory/overview">
    Import a suite, run it, and read the results.
  </Card>

  <Card title="Lager Python API" href="https://docs.lagerdata.com/source/reference/python/overview">
    Drive instruments from the steps of your suite.
  </Card>
</CardGroup>
