Skip to content

Testing

Fable.Ripple.Dom.Test runs component tests in Chromium through Playwright. Each test mounts one component on a fresh page and asserts against the rendered DOM.

Tests are written with Scriptorium.Quill and asserted with Scriptorium.Nib.Browser. Both come with the package.

Requirements

  • Fable installed as a local tool. See Getting started.
  • Node.js 20.19 or later.

Set up a test project

  1. Create the project and add the package

    Terminal
    dotnet new console -lang F# -o MyApp.Tests
    cd MyApp.Tests
    dotnet add package Fable.Ripple.Dom.Test --prerelease
    

    Add a reference to the project that holds your components.

    Terminal
    dotnet add reference ../MyApp/MyApp.fsproj
    
  2. Install Playwright, esbuild and Chromium

    Terminal
    npm init -y
    npm install --save-dev playwright esbuild
    npx playwright install chromium
    

    Set the package type to module.

    package.json
    {
        "type": "module"
    }
    
  3. Register the components in Components.fs

    Components.fs
    module MyApp.Tests.Components
    
    open Fable.Ripple
    open Fable.Ripple.Dom
    open type Fable.Ripple.Dom.Test.RippleRegistry
    
    let counter () : DomItem =
        let count = Var.create 0
    
        Html.div
            [
                Html.div [ Html.text count ]
                Html.button
                    [
                        on.click (fun _ -> count.Value <- count.Value + 1)
                        Html.text "Count"
                    ]
            ]
    
    register ("Counter", counter)
    
  4. Write the tests in Main.fs

    Main.fs
    module MyApp.Tests.Main
    
    open Scriptorium.Nib.Browser
    open type Scriptorium.Nib.Browser.UserEvents
    open type Fable.Ripple.Dom.Test.RippleDomTest
    open type Scriptorium.Quill.Runner
    open type Scriptorium.Quill.Test
    
    do setup "Components.fs"
    
    [<EntryPoint>]
    let main _ =
        runTests (
            testList (
                "Counter",
                [
                    testComponent (
                        "increments when Count is clicked",
                        "Counter",
                        fun root ->
                            promise {
                                do! click (root.locator "button")
                                do! assertLocator (root.locator "div > div") (haveText "1")
                            }
                    )
                ]
            )
        )
    
  5. Update the project file

    Replace Program.fs with Components.fs and Main.fs, in that order.

    MyApp.Tests.fsproj
    <ItemGroup>
        <Compile Include="Components.fs" />
        <Compile Include="Main.fs" />
    </ItemGroup>
    
  6. Run the tests

    From the test project folder.

    Terminal
    dotnet fable --runScript
    

    The process exits with a non-zero code when a test fails.

Components.fs runs in the browser and Main.fs runs in Node.js. Refer to components by name only: code in Main.fs that uses Components.fs loads it into Node.js, where there is no window.

Registering components

register takes a name and a unit -> DomItem factory. The factory runs once per test, so every test starts from new state.

Writing tests

testComponent mounts the component inside <div id="root"> and passes that element's locator to the test. The second overload also passes the Playwright Page. page.evaluate needs open Glutinum.Playwright.

testComponent (
    "reads the title",
    "Counter",
    fun root page ->
        promise {
            let! title = page.evaluate "document.title"
            ()
        }
)

Assertions retry until they pass or 5 seconds elapse. That is also the test timeout, so a failing assertion is reported as Test timed out after 5000ms.

Assertions

Chain assertions with >>. and pass them to assertLocator. not' inverts one.

AssertionPasses when the element
haveText texthas exactly this text
containText textcontains this text
haveValue valueis a field with this value
haveAttribute name valuehas this attribute value
haveClass classeshas exactly this class attribute
containClass classeshas all of these classes
haveCSS property valuehas this computed style
haveCount nmatches exactly n elements
toBeVisible / beHiddenis visible / hidden
beCheckedis a checked checkbox or radio
beEnabled / beDisabledis enabled / disabled
beFocusedhas focus
beEditableis editable
beEmptyhas no content

User events

UserEvents acts on a locator: click, fill, press, check, uncheck, selectOption, focus, blur and hover.

Reference

RippleDomTest.setup

type: componentsFile: string -> unit

Bundles the compiled componentsFile with esbuild into fable_modules/fable-ripple-dom-test-bundle.iife.js. Call it once, before the tests run. Fable must already have compiled the file next to its source.

RippleDomTest.testComponent

type: name: string * componentName: string * body: (Locator -> Promise<unit>) -> TestCase

Runs body against the component registered as componentName, in a new headless Chromium page.

RippleDomTest.ftestComponent

Focused variant of testComponent. When any focused test exists, only focused tests run.

RippleDomTest.xtestComponent

Skipped variant of testComponent.

RippleDomTest.dtestComponent

Debug variant of testComponent. Pauses the test in the Playwright inspector.

RippleRegistry.register

type: name: string * view: (unit -> DomItem) -> unit

Registers a component under name.

ComponentLoader.createWith

type: componentsFile: string -> bundlePath: string -> (string -> Page -> Promise<Locator>)

Bundles componentsFile to bundlePath and returns the loader that mounts a component by name. setup calls it with the default bundle path.

Edit this page