nightwatchjs / nightwatch

Integrated end-to-end testing framework written in Node.js and using W3C Webdriver API. Developed at @browserstack
https://nightwatchjs.org
MIT License
11.78k stars 1.31k forks source link

Page object commands not working in 3.0.1 typescript projects #3777

Closed reallymello closed 1 year ago

reallymello commented 1 year ago

Description of the bug/issue

When I use a typescript page object command in a Nightwatch test project I expect to be able to explicitly specific the page object's interface as the type and use commands defined in the page object in command chaining, but instead I receive error

Type 'EnhancedPageObject<any, any, any>' is not assignable to type 'EcosiaPage'.
  Type 'EnhancedPageObject<any, any, any>' is not assignable to type 'EnhancedPageObjectSharedFields<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error | null>; }>, Required<{ [name: string]: string; } | { ...; } | { ...; } | undefined>, null, {}, string>'.
    Types of property 'elements' are incompatible.
      Type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<any>, Required<any>, any, {}, string>>; }' is not assignable to type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error | null>; }>, Required<{ [name: string]: string; }>, null, {}, string>>; } | { ...; } | { ...; } | undefined'.
        Type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<any>, Required<any>, any, {}, string>>; }' is not assignable to type '{ [x: 

and

Property 'clickSearch' does not exist on type 'Awaitable<NightwatchAPI, null>'.ts(2339)

image

image

Steps to reproduce

  1. npm init nightwatch@latest

(as of writing that should setup Nightwatch 3.0.1)

  1. Create a folder under /nightwatch called /page-objects
  2. Create ecosiaPage.ts
    
    // nightwatch/page-objects/ecosiaPage.ts

import { EnhancedPageObject, PageObjectModel } from 'nightwatch';

const ecosiaCommands = { clickSearch(this: EcosiaPage) { return this.waitForElementVisible('@submit', 10000) .click('@submit') .waitForElementNotPresent('@submit'); }, };

const ecosiaPage: PageObjectModel = { url: 'http://www.ecosia.com', commands: [ecosiaCommands], elements: { searchInput: { selector: 'input[type=search]', }, submit: { selector: 'button[type=submit]', }, }, };

export default ecosiaPage;

export interface EcosiaPage extends EnhancedPageObject< typeof ecosiaCommands, typeof ecosiaPage.elements, null

{}

  1. In the tests folder create ecosia.ts
import { EcosiaPage } from '../page-objects/ecosiaPage';

describe('Ecosia.org Demo', function () {
  this.tags = ['demo'];

  before((browser) => browser.navigateTo('https://www.ecosia.org/'));

  it('Demo test ecosia.org', function (browser) {
    const ecosia: EcosiaPage = browser.page.ecosiaPage();

    // Can call clickSearch here without IDE errors
    ecosia.clickSearch();

    ecosia
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('@searchInput')
      .setValue('@searchInput', 'nightwatch')
      // Can't use it chained
      .clickSearch()
      .assert.not.textContains('.layout__content', 'Nightwatch.js');
  });

  after((browser) => browser.end());
});
  1. Update nightwatch.conf.js settings
  src_folders: ['nightwatch/tests'],
  page_objects_path: ['nightwatch/page-objects'],
  1. Create ./nightwatch/types/page-objects.d.ts
import { NightwatchCustomPageObjects } from 'nightwatch';
import { EcosiaPage } from '../page-objects/ecosiaPage';

declare module 'nightwatch' {
  interface NightwatchCustomPageObjects {
    ecosiaPage(): EcosiaPage;
  }
}
  1. In the inner nightwatch/tsconfig.json add "include": ["types"] below compilerOptions
  2. In the other ./tsconfig.json add "include": ["./nightwatch/types"]

(I'm not sure if 7 and 8 are necessary, but was following steps from a prior bug ticket)

  1. Run npx nightwatch -t nightwatch\tests\ecosia.ts

Receive error:

  TSError
   ⨯ Unable to compile TypeScript:
nightwatch/tests/ecosia.ts:9:11 - error TS2322: Type 'EnhancedPageObject<any, any, any>' is not assignable to type 'EcosiaPage'.
  Type 'EnhancedPageObject<any, any, any>' is not assignable to type 'EnhancedPageObjectSharedFields<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [name: string]: string; } | { [name: string]: ElementProperties; } | { ...; }>, null, {}, string>'.
    Types of property 'elements' are incompatible.
      Type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<any>, Required<any>, any, {}, string>>; }' is not assignable to type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [name: string]: string; }>, null, {}, string>>; } | { ...; } | { ...; }'.
        Type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<any>, Required<any>, any, {}, string>>; }' is not assignable to type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [x: string]: ElementProperties; }>, null, {}, string>>; }'.
          'string' index signatures are incompatible.
            Type 'EnhancedElementInstance<EnhancedPageObject<Required<any>, Required<any>, any, {}, string>>' is not assignable to type 'EnhancedElementInstance<EnhancedPageObject<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [x: string]: ElementProperties; }>, null, {}, string>>'.
              Type 'EnhancedPageObject<Required<any>, Required<any>, any, {}, string>' is not assignable to type 'EnhancedPageObject<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [x: string]: ElementProperties; }>, null, {}, 
string>'.
                Type 'EnhancedPageObject<Required<any>, Required<any>, any, {}, string>' is not assignable to type 'EnhancedPageObjectSharedFields<Required<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>>, Required<Required<{ [x: string]: 
ElementProperties; }>>, null, {}, string>'.
                  Types of property 'section' are incompatible.
                    Type '{ [x: string]: EnhancedSectionInstance<Required<any>, Required<any>, Required<any>, Required<any>>; }' is not assignable to type 'null'.

9     const ecosia: EcosiaPage = browser.page.ecosiaPage();
            ~~~~~~
nightwatch/tests/ecosia.ts:16:8 - error TS2339: Property 'clickSearch' does not exist on type 'Awaitable<NightwatchAPI, null>'.

16       .clickSearch()
          ~~~~~~~~~~~

Might also be worth noting if you don't specific the test and choose to run all you get no output at all (which seems like another issue)

npx nightwatch
Running   default: ecosia.ts  
 Wrote HTML report file to: C:\Users\Mr\Desktop\New folder\tests_output\nightwatch-html-report\index.html

Sample test

import { EcosiaPage } from '../page-objects/ecosiaPage';

describe('Ecosia.org Demo', function () {
  this.tags = ['demo'];

  before((browser) => browser.navigateTo('https://www.ecosia.org/'));

  it('Demo test ecosia.org', function (browser) {
    const ecosia: EcosiaPage = browser.page.ecosiaPage();

    // Can call clickSearch here without IDE errors
    ecosia.clickSearch();

    ecosia
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('@searchInput')
      .setValue('@searchInput', 'nightwatch')
      // Can't use it chained
      .clickSearch()
      .assert.not.textContains('.layout__content', 'Nightwatch.js');
  });

  after((browser) => browser.end());
});

Command to run

npx nightwatch -t nightwatch\tests\ecosia.ts

Verbose Output

npx nightwatch -t nightwatch\tests\ecosia.ts --verbose
 Wrote HTML report file to: C:\Users\Mr\Desktop\New folder\tests_output\nightwatch-html-report\index.html

 Wrote Rerun Json report file to: C:\Users\Mr\Desktop\New folder\tests_output\minimal_report.json
  TSError
   ⨯ Unable to compile TypeScript:
nightwatch/tests/ecosia.ts:9:11 - error TS2322: Type 'EnhancedPageObject<any, any, any>' is not assignable to type 'EcosiaPage'.
  Type 'EnhancedPageObject<any, any, any>' is not assignable to type 'EnhancedPageObjectSharedFields<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [name: string]: string; } | { [name: string]: ElementProperties; } | { ...; }>, null, {}, string>'.
    Types of property 'elements' are incompatible.
      Type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<any>, Required<any>, any, {}, string>>; }' is not assignable to type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [name: string]: string; }>, null, {}, string>>; } | { ...; } | { ...; }'.
        Type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<any>, Required<any>, any, {}, string>>; }' is not assignable to type '{ [x: string]: EnhancedElementInstance<EnhancedPageObject<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [x: string]: ElementProperties; }>, null, {}, string>>; }'.
          'string' index signatures are incompatible.
            Type 'EnhancedElementInstance<EnhancedPageObject<Required<any>, Required<any>, any, {}, string>>' is not assignable to type 'EnhancedElementInstance<EnhancedPageObject<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [x: string]: ElementProperties; }>, null, {}, string>>'.
              Type 'EnhancedPageObject<Required<any>, Required<any>, any, {}, string>' is not assignable to type 'EnhancedPageObject<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>, Required<{ [x: string]: ElementProperties; }>, null, {}, 
string>'.
                Type 'EnhancedPageObject<Required<any>, Required<any>, any, {}, string>' is not assignable to type 'EnhancedPageObjectSharedFields<Required<Required<{ clickSearch(this: EcosiaPage): Awaitable<EcosiaPage, Error>; }>>, Required<Required<{ [x: string]: 
ElementProperties; }>>, null, {}, string>'.
                  Types of property 'section' are incompatible.
                    Type '{ [x: string]: EnhancedSectionInstance<Required<any>, Required<any>, Required<any>, Required<any>>; }' is not assignable to type 'null'.

9     const ecosia: EcosiaPage = browser.page.ecosiaPage();
            ~~~~~~
nightwatch/tests/ecosia.ts:16:8 - error TS2339: Property 'clickSearch' does not exist on type 'Awaitable<NightwatchAPI, null>'.

16       .clickSearch()
          ~~~~~~~~~~~

Nightwatch Configuration

// Refer to the online docs for more details:
// https://nightwatchjs.org/gettingstarted/configuration/
//

//  _   _  _         _      _                     _          _
// | \ | |(_)       | |    | |                   | |        | |
// |  \| | _   __ _ | |__  | |_ __      __  __ _ | |_   ___ | |__
// | . ` || | / _` || '_ \ | __|\ \ /\ / / / _` || __| / __|| '_ \
// | |\  || || (_| || | | || |_  \ V  V / | (_| || |_ | (__ | | | |
// \_| \_/|_| \__, ||_| |_| \__|  \_/\_/   \__,_| \__| \___||_| |_|
//             __/ |
//            |___/

module.exports = {
  // An array of folders (excluding subfolders) where your tests are located;
  // if this is not specified, the test source must be passed as the second argument to the test runner.
  src_folders: ['nightwatch/tests'],
  page_objects_path: ['nightwatch/page-objects'],

  // See https://nightwatchjs.org/guide/extending-nightwatch/adding-custom-commands.html
  custom_commands_path: [],

  // See https://nightwatchjs.org/guide/extending-nightwatch/adding-custom-assertions.html
  custom_assertions_path: [],

  // See https://nightwatchjs.org/guide/extending-nightwatch/adding-plugins.html
  plugins: [],

  // See https://nightwatchjs.org/guide/concepts/test-globals.html
  globals_path: '',

  webdriver: {},

  test_workers: {
    enabled: true,
  },

  test_settings: {
    default: {
      disable_error_log: false,
      launch_url: 'http://localhost',

      screenshots: {
        enabled: false,
        path: 'screens',
        on_failure: true,
      },

      desiredCapabilities: {
        browserName: 'chrome',
      },

      webdriver: {
        start_process: true,
        server_path: '',
      },
    },

    chrome: {
      desiredCapabilities: {
        browserName: 'chrome',
        'goog:chromeOptions': {
          // More info on Chromedriver: https://sites.google.com/a/chromium.org/chromedriver/
          //
          // w3c:false tells Chromedriver to run using the legacy JSONWire protocol (not required in Chrome 78)
          w3c: true,
          args: [
            //'--no-sandbox',
            //'--ignore-certificate-errors',
            //'--allow-insecure-localhost',
            //'--headless'
          ],
        },
      },

      webdriver: {
        start_process: true,
        server_path: '',
        cli_args: [
          // --verbose
        ],
      },
    },
  },
};

Nightwatch.js Version

3.0.1

Node Version

18.16.0

Browser

Chrome 114.0.5735.134

Operating System

Windows 10

Additional Information

nightwatch.zip

garg3133 commented 1 year ago

PR #3778 resolves the issue with clickSearch() in TypeScript. You can try it by running npm i -D "garg3133/nightwatch#fix-pageObjects-types" in your project.

As for the first issue, Step 7 and 8 stated above are not correct. These should be as follows:

  1. In the inner nightwatch/tsconfig.json add "extends": "../tsconfig" below compilerOptions.
  2. In the other ./tsconfig.json add:
    "compilerOptions": {
      ...
    },
    // files should contain the path to type declaration files
    "files": ["./nightwatch/types/page-objects.d.ts"],
    // include should contain the path to directory where your tests are present (where these tsconfigs should be applied)
    "include": ["./nightwatch"]

We are working on creating a separate doc page for TypeScript where all these steps will be clearly mentioned.

reallymello commented 1 year ago

Tested with https://github.com/nightwatchjs/nightwatch/pull/3778 and it appears to be working