﻿---
title: Page objects
description: Page objects wrap UI interactions (navigation, clicking, filling forms) so tests read like user workflows and stay maintainable as the UI evolves. For...
url: https://www.elastic.co/elastic/docs-builder/docs/2900/extend/kibana/scout/page-objects
products:
  - Kibana
---

# Page objects
Page objects wrap UI interactions (navigation, clicking, filling forms) so tests read like user workflows and stay maintainable as the UI evolves.
<tip>
  Keep page objects focused on **UI interactions**. Don’t hide API setup/teardown inside page objects—use [API services](https://www.elastic.co/elastic/docs-builder/docs/2900/extend/kibana/scout/api-services) or [fixtures](https://www.elastic.co/elastic/docs-builder/docs/2900/extend/kibana/scout/fixtures) instead.
</tip>

For practical tips, see the page object guidelines in [UI test best practices](/elastic/docs-builder/docs/2900/extend/kibana/scout/best-practices#use-existing-page-objects-to-interact-with-the-kibana-ui).

## Usage

Page objects are exposed through the `pageObjects` fixture and are lazy-initialized:
```ts
import { tags } from '@kbn/scout';
import { test } from '../fixtures';

test.describe('My suite', { tag: tags.deploymentAgnostic }, () => {
  test.beforeEach(async ({ browserAuth, pageObjects }) => {
    await browserAuth.loginAsViewer();
    await pageObjects.discover.goto();
  });
});
```


## Where they live

- Core page objects: `@kbn/scout` (available as `pageObjects.<name>`)
- Solution Scout packages may provide additional page objects (their internal folder layout varies—search within the package for `page_objects` if you need the source).
- Plugin-local page objects: `<plugin-root>/test/scout/ui/fixtures/page_objects`

To make your page object available as `pageObjects.newPage`, register it in your plugin fixtures.

## Create and register a new page object in your plugin

<stepper>
  <step title="Create a plugin page object">
    Create a class that takes `ScoutPage` and exposes locators + actions:
    ```ts
    import { ScoutPage } from '@kbn/scout';

    export class NewPage {
      constructor(private readonly page: ScoutPage) {}

      async goto() {
        await this.page.gotoApp('myPlugin');
      }
    }
    ```
  </step>

  <step title="Register a plugin page object">
    Register it in `fixtures/page_objects/index.ts`
    ```ts
    import type { PageObjects, ScoutPage } from '@kbn/scout';
    import { createLazyPageObject } from '@kbn/scout';
    import { NewPage } from './new_page';

    export type MyPluginPageObjects = PageObjects & {
      newPage: NewPage;
    };

    export function extendPageObjects(pageObjects: PageObjects, page: ScoutPage): MyPluginPageObjects {
      return {
        ...pageObjects,
        newPage: createLazyPageObject(NewPage, page),
      };
    }
    ```
  </step>

  <step title="Wire it into your plugin `test` fixture">
    In `<plugin-root>/test/scout/ui/fixtures/index.ts`, extend Scout’s `test` so `pageObjects` has your extended type:
    ```ts
    import { test as base } from '@kbn/scout';

    import type { MyPluginPageObjects } from './page_objects';
    import { extendPageObjects } from './page_objects';

    export const test = base.extend<{ pageObjects: MyPluginPageObjects }>({
      pageObjects: async ({ pageObjects, page }, use) => {
        await use(extendPageObjects(pageObjects, page));
      },
    });
    ```
    Now your specs can use `pageObjects.newPage` without importing the page object class directly.
    <note>
      If your page object constructor needs extra arguments, pass them after `page`:`createLazyPageObject(NewPage, page, extraArg1, extraArg2)`.If you use `spaceTest` (parallel UI suites), extend it the same way: import `spaceTest as base` from `@kbn/scout`, then `export const spaceTest = base.extend<{ pageObjects: MyPluginPageObjects }>(...)`.
    </note>
  </step>
</stepper>