Skip to content

Page objects with Nightwatch v3 (TypeScript)

Priyansh Garg edited this page Aug 25, 2023 · 5 revisions

Nightwatch v3 has much better TypeScript support for Page Objects. In order to make full use of it, follow the below steps:

  • For this doc, we'll consider that you have the folder structure for Nightwatch tests as follows:

    my-project/
      |-- nightwatch/
      |   |-- pages/
      |   |   |-- myPage.ts
      |   |-- tsconfig.json
      |-- test/
      |   |--  myTest.ts
      |-- types/
      |   |-- nightwatch.d.ts
      |-- tsconfig.json
      |-- package.json
    
  • Now, let's define a page object named myPage in nightwatch/pages/myPage.ts as below:

    // nightwatch/pages/myPage.ts
    import { PageObjectModel, EnhancedPageObject } from 'nightwatch'
    
    const myPageCommands = {
      assertTitle(this: EnhancedPageObject) {
        return this.assert.titleEquals('MyTitle');
      }
    };
    
    const myPage = {
      url: 'myUrl',
      commands: [myPageCommands],
      elements: {
        myButton: {
          selector: '.buttonClass',
        }
      }
    } satisfies PageObjectModel;
    
    export interface MyPage extends EnhancedPageObject<typeof myPageCommands, typeof myPage.elements> {}
    
    export default myPage;

    In the above page object definition, notice that we've defined it as const myPage = {} satisfies PageObjectModel instead of const myPage: PageObjectModel = {}. Doing so helps preserve the literal type of the page object and all its components when they are passed to ElementPageObject, instead of downleveling them to take the loose types defined in PageObjectModel.

  • Now that we've defined our page object, let's write a test using this page object at test/myTest.ts as below:

    // test/myTest.ts
    describe('myPage Demo', function() {
      it('navigate to page, assert the title and then click on myButton', function() {
        const myPage = browser.page.myPage();
    
        myPage
          .navigate()
          .assertTitle()
          .click('@myButton');
      });
    });

On copying the above example to your IDE, you'll notice some red squiggles under .assertTitle(). This is because even though we are using the page object defined earlier correctly, TypeScript does not know the exact type for myPage and the commands we have added to it.

There are two ways to let TypeScript know of the type for myPage:

  1. The easier way would be to use the MyPage interface exported from the page object definition file directly in the test file:

    // test/myTest.ts
    import { MyPage } from '../nightwatch/pages/myPage';
    
    describe('myPage Demo', function() {
      it('navigate to page, assert the title and then click on myButton', function() {
        const myPage = browser.page.myPage() as MyPage;
    
        // if the above give error (which it ideally shouldn't),
        // first assert the type of `myPage()` to `unknown` and then to `MyPage`.
        const myPage = browser.page.myPage() as unknown as MyPage;
    
        myPage
          .navigate()
          .assertTitle()
          .click('@myButton');
      });
    });
  2. The second and the correct way of achieving this (which wouldn't require you to assert the type of the page object every time you use it) would be the following:

    • Create a types/nightwatch.d.ts file in the root of your project, and declare the types for your page objects in it:

      // types/nightwatch.d.ts
      import 'nightwatch';
      import {MyPage} from '../nightwatch/pages/myPage';
      
      declare module 'nightwatch' {
        interface NightwatchCustomPageObjects {
          myPage(): MyPage;
        }
      }
    • After creating the above file, the type errors in your test file should go away. If they don't, go to tsconfig.json file present in the root of your project, and add "files" and "include" properties to it:

      // tsconfig.json
      {
        "compilerOptions": {
          ...
        },
        "files": ["./types/nightwatch.d.ts"]  // path to the recently created nightwatch.d.ts file.
        "include": ["test", "nightwatch"]  // directories in which your nightwatch tests are present, where the above type declaration file should be applied.
      }
    • Make sure your nightwatch/tsconfig.json extends the root tsconfig.json so that the above added properties are auto-applied in there:

      // nightwatch/tsconfig.json
      {
        "extends": "../tsconfig",
        "compilerOptions": {
          // all configs here
        },
        "include": ["."]
      }

    Following the above 3 steps, the types for your declared page objects should appear automatically when you do browser.page.myPage().

FAQs

1. Can't change the "include" setting in root tsconfig.json

If you're using the root tsconfig.json file to compile your code and can't add test directory to the "include" property, create a new tsconfig.json file in the directory where your tests are present and set it to the following:

// test/tsconfig.json
{
  "extends": "../tsconfig", // path to the root tsconfig
  "include": ["."], // apply the declaration file to the current directory
}

But make sure that the "files" property is still present in the root tsconfig.json.

2. Should I remove the "compilerOptions" from nightwatch/tsconfig.json if they're the same as my root tsconfig.json?

Ideally, you should not remove or modify any config present in "compilerOptions" from nightwatch/tsconfig.json file, unless you absolutely need to. These are the best possible configs that are used by ts-node while running Nightwatch tests and changing these might affect your test runs.