# Playwright F# DSL — Agent Instructions

This document gives an agent everything needed to write F# Playwright tests using a computation-expression DSL and a type-safe page locator record.

---

## Step 0 — Confirm intent and target

Read the existing context before asking anything. Check whether the following are already clear:

1. **Intent** — does the user want to write end-to-end Playwright tests using this custom F# DSL?
2. **Target URL** — is there a URL (or a codebase with a startable server) to test against?

If intent is not clear from context, ask:
> *"Do you want to write F# Playwright tests using the custom computation-expression DSL against a web application?"*

If the URL is missing, ask:
> *"What is the URL of the application to test? (or share the codebase so I can start the server)"*

If intent is not clear abort.

---

## Step 1 — Check prerequisites

The Playwright CLI (`playwright-cli`) must be installed globally via npm or similar. Check with:

```bash
playwright-cli --version
```

If missing, ask if you are allowed to install it (`npm install -g @playwright/cli@latest`) or similar with `pnpm` or `yarn`. Ask user for guidance.
Use `playwright-cli --help` to familiarize yourself with the available commands.

---

## Step 2 — Set up the F# test project

Identify whether an F# test project exists or where a new test project should be created (ask if unclear).

### If an F# test project already exists

Add the Playwright NuGet package to it:

```bash
dotnet add  package Microsoft.Playwright
```

### If no F# test project exists

Change working directory to the folder where the new test project should be created and run:

```bash
dotnet new console -lang F# -n Tests.E2E -o Tests.E2E
cd Tests.E2E
dotnet add package Microsoft.Playwright
dotnet add package Expecto
dotnet add package FsToolkit.ErrorHandling
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package YoloDev.Expecto.TestSdk
```

Add the project to the solution if one exists, `using dotnet sln add Tests.E2E/Tests.E2E.fsproj`

### Download the DSL

Download `Dsl.fs`

```bash
curl -fsSLiH "Accept: text/plain"  https://jannikbuschke.de/examples/playwright-dsl/definition.fs -o Tests.E2E/Dsl.fs
```

Register it first in `Tests.E2E.fsproj`:

```xml


```

---

## Step 3 — Explore the site

Use the Playwright CLI to inspect the target before writing any code. Use the playwright-cli, don't use any other method of exploring the site. If that doesn't work abort.
The accessible names you observe map directly to the `Locators` helpers used in the DSL (`Button "Sign in"`, `Label "Username"`, `Heading "Game lobby"`, etc.).

---

## Step 5 — Model the pages (PLR)

Create one record type per logical page or modal. Fields are either `Locator` (non-navigating) or `Transition<'next>` (navigating to another page).

```fsharp
open type Locators

type LoginPage = {
    UsernameField: Locator                  // IPage -> ILocator
    PasswordField: Locator
    SigninButton:  Transition     // (IPage -> ILocator) * (unit -> HomePage)
}
and HomePage = {
    CreateButton: Transition
    ItemList:     Locator
}
and CreateModal = {
    NameField:    Locator
    SubmitButton: Transition
}

let rec login () : LoginPage = {
    UsernameField = Label "Username"
    PasswordField = Label "Password"
    SigninButton  = Button "Sign in", home
}
and home () : HomePage = {
    CreateButton = Button "Create", createModal
    ItemList     = Locator "ul.items"
}
and createModal () : CreateModal = {
    NameField    = Label "Name"
    SubmitButton = Button "Submit", home
}

let plr = {| Login = login; Home = home; CreateModal = createModal |}
```

**Rules:**
- `Locator = IPage -> ILocator` — every `Pw` helper returns this.
- `Transition<'t> = Locator * (unit -> 't)` — tuple of locator and thunk to next PLR.
- Use `let rec … and …` so page types can reference each other.
- For chained/filtered locators use `||>` (`Locator -> (ILocator -> ILocator) -> Locator`):
  ```fsharp
  Locator "div.list"
  ||> _.Filter(LocatorFilterOptions(HasText = "Alice"))
  ||> _.GetByRole(AriaRole.Button)
  ||> _.First
  ```

---

## Step 6 — Write the tests

```fsharp
open Dsl

[]
let myTests =
  testCaseTask "user can create an item" <| fun () ->
    task {
      use! pw      = Playwright.CreateAsync()
      use! browser = pw.Firefox.LaunchAsync()
      use! ctx     = browser.NewContextAsync()
      let! page    = ctx.NewPageAsync()

      do! page.GotoAsync("here-goes-the-initial-url")
      do!
        pageTest (page, plr.Home()) {
          click _.SigninLink                      // Transition — PLR becomes LoginPage
          fill  _.UsernameField "alice"
          fill  _.PasswordField "secret"
          click _.SigninButton                    // Transition — PLR becomes HomePage
          expectVisible _.CreateButton 5000.0f
          click _.CreateButton                   // Transition — PLR becomes CreateModal
          fill  _.NameField "my item"
          click _.SubmitButton                   // Transition — PLR becomes HomePage
          waitFor _.ItemList 8000.0f
          expectContainsText _.ItemList "my item"
        }
    }
```

Each `click` on a `Transition` advances the PLR automatically. The compiler prevents accessing fields that don't exist on the current page.