Búsqueda de sitios web

javascript - ¿No se puede usar una promesa con valor booleano como constructo thenable en Typescript?


Estoy convirtiendo una biblioteca de objetos de página de Protractor de JavaScript a TypeScript. Tengo un método de utilidad estático que se actualiza hasta que un elemento está presente (o se alcanza el límite de tiempo de espera).

Por alguna razón, no puedo usar una promesa con valor boolean como un constructo thenable y no puedo entender por qué. El mensaje de error es error TS2345: Argument of type '(value: boolean) => Promise<{}> | undefined' is not assignable to parameter of type '((value: boolean) => {} | IThenable<{}>) | undefined'. Type '(value: boolean) => Promise<{}> | undefined' is not assignable to type '(value: boolean) => {} | IThenable<{}>'. Type 'Promise<{}> | undefined' is not assignable to type '{} | IThenable<{}>'. Type 'undefined' is not assignable to type '{} | IThenable<{}>.'

El código fuente está abajo con un comentario que muestra dónde ocurre el error de compilación.

He utilizado este tipo de mecanismo en la versión de JavaScript puro del framework que estoy convirtiendo a TypeScript. ¿Qué necesito hacer para traducir este método a TypeScript de manera que se compile?

import {browser, element, ElementFinder, ExpectedConditions} from 'protractor';
import * as wd from 'selenium-webdriver';
import {By} from "selenium-webdriver";

export class ExtendedExpectedConditions {

    public static refreshUntilElementIsPresent(element: ElementFinder,  numberOfSeconds: number = 30000,
        refreshInterval: number = 5000): void {
        this.refreshElement(element, numberOfSeconds, refreshInterval);
    }

    protected static refreshElement(element: ElementFinder, numberOfSeconds: number = 30000, refreshInterval:
        number = 5000) {


        //This line throws the compile error
        element.isPresent().then(value => {
            if (!value) {
                if (numberOfSeconds <= 0) {
                    return new wd.promise.Promise(function (resolve, reject) {
                        reject('Element cannot be found after expected retry numbers');
                    })
                }
                browser.sleep(refreshInterval).then(() => {
                    browser.refresh().then(() => this.refreshUntilElementIsPresent(element, numberOfSeconds -
                    (refreshInterval / 1000)));
                });
            }
       });
    }
}

Mi 'package.json' está abajo:

{
  "name": "my-project",
  "version": "1.0.0",
  "description": "A test suite",
  "scripts": {
    "tsc": "tsc",
    "pretest": "npm run tsc",
    "test": "node_modules/protractor/bin/protractor tmp/conf.js",
    "start_selenium": "node_modules/protractor/node_modules/webdriver-manager/bin/webdriver-manager start",
    "update_selenium": "node_modules/protractor/node_modules/webdriver-manager/bin/webdriver-manager update"
  },
  "author": [email ,
  "dependencies": {
    "@types/jasmine": "^2.5.52",
    "@types/jasminewd2": "^2.0.2",
    "@types/node": "^7.0.31",
    "any-promise": "^1.3.0",
    "jasmine-reporters": "^2.2.1",
    "protractor": "^5.1.2",
    "typescript": "^2.3.4"
  }
}

Mi tsconfig.json está abajo:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "declaration": false,
    "noImplicitAny": false,
    "outDir": "tmp",
    "types": ["jasmine", "node"],
    "strict": true
  },
  "exclude": [
    "node_modules"
  ]
}

Mi archivo conf.ts está abajo:

import {Config, browser, protractor} from "protractor";

let testsTimeout = 3600000;
let delayBrowserTimeInSeconds = 0;

export let config: Config = {
    framework: 'jasmine2',
    rootElement: 'body',
    seleniumServerJar:'../node_modules/protractor/node_modules/webdriver-manager/selenium/selenium-server-standalone-3.4.0.jar',
    chromeDriver: '../node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.30',
    specs: ['tests/*Spec.js'],
    capabilities: {
        browserName: 'chrome',
        acceptSslCerts: true,
        trustAllSSLCertificates: true,
        chromeOptions: {
            args: ['--no-sandbox']
        },
    },
    baseUrl: 'https://www.someurl.com',

    jasmineNodeOpts: {
        defaultTimeoutInterval: testsTimeout,
        showColors: true,
        isVerbose: true
    },

    onPrepare: () => {

        let origFn = browser.driver.controlFlow().execute;

        browser.driver.controlFlow().execute = function () {
            let args = arguments;

            origFn.call(browser.driver.controlFlow(), function () {
                return protractor.promise.delayed(delayBrowserTimeInSeconds * 100);
            });

            return origFn.apply(browser.driver.controlFlow(), args);
        };

        setTimeout(() => {
            browser.driver.executeScript<[number, number]>(() => {
                return [
                    window.screen.availWidth,
                    window.screen.availHeight
            ];
            }).then((result: [number, number]) => {
                browser.driver.manage().window().setSize(result[0], result[1]);
                browser.driver.manage().window().maximize();
            });
        });

    },

    getPageTimeout: 120000,
    allScriptsTimeout: testsTimeout
};

Respuesta #1

Tu verdadero problema parece ser que los autores de la biblioteca de tipos tienen una definición incorrecta para el parámetro de callback del método then. Escribiste mal el mensaje de error ligeramente en tu pregunta, lo que hace que sea un poco más difícil ver qué está pasando.

src/test.ts(17,34): error TS2345: Argument of type '(value: boolean) => Promise<{}> | undefined' is not assignable to parameter of type '((value: boolean) => {} | IThenable<{}>) | undefined'.
  Type '(value: boolean) => Promise<{}> | undefined' is not assignable to type '(value: boolean) => {} | IThenable<{}>'.
    Type 'Promise<{}> | undefined' is not assignable to type '{} | IThenable<{}>'.
      Type 'undefined' is not assignable to type '{} | IThenable<{}>'.

Entonces, el callback que proporcionaste a .then tiene un tipo de (value: boolean) => Promise<{}> | undefined. Es decir, o devuelve una Promise o devuelve undefined.

Pero .then() espera recibir ((value: boolean) => {} | IThenable<{}>) | undefined (aquí es donde perdiste un paréntesis de cierre ')' en la pregunta), lo que quiere decir que puede recibir una función o ningún callback. Como le diste una función, podemos ignorar el undefined, así que tenemos que darle una función que devuelva algo o un IThenable<algo>. Los autores de este código han decidido que un callback en then no puede devolver undefined.

Así que la solución a corto plazo es simple: cambia tu callback .then para que siempre devuelva un resultado.

La solución a largo plazo sería reportar un problema a los autores de los bindings de TypeScript para Selenium Webdriver y pedirles que permitan que el callback devuelva {} | IThenable<{}> | undefined.

Deberías encontrar que este código se compila para ti:

export class ExtendedExpectedConditions {

    public static refreshUntilElementIsPresent(element: ElementFinder,  numberOfSeconds: number = 30000,
        refreshInterval: number = 5000): void {
        this.refreshElement(element, numberOfSeconds, refreshInterval);
    }

    protected static refreshElement(element: ElementFinder, numberOfSeconds: number = 30000, refreshInterval:
        number = 5000) {


        //This line throws the compile error
        element.isPresent().then(value => {
            if (!value) {
                if (numberOfSeconds <= 0) {
                    return wd.promise.rejected('Element cannot be found after expected retry numbers');
                }
                browser.sleep(refreshInterval).then(() => {
                    browser.refresh().then(() => this.refreshUntilElementIsPresent(element, numberOfSeconds -
                    (refreshInterval / 1000)));
                });
            }
            return 42; // webdriver `.then` callback must return a result.
       });
    }
}

Además, por alguna razón, las definiciones de tipo para la Promesa del webdriver no declaran los métodos .resolve() y .reject() que el propio objeto sí incluye, pero puedes usar la función promise.rejected() como una manera más sencilla de crear una promesa rechazada inmediatamente.

Respuesta #2

El error que obtienes es correcto, si miras las especificaciones verás que Protractor @returns {Promise<boolean>}, mira las especificaciones aquí

¿Alguna vez has oído hablar de los métodos async/await en TypeScript? Te hará la vida más fácil y tu código más limpio. Tu código se vería como el siguiente. Esto también debería eliminar tus errores de tipado.

public static refreshUntilElementIsPresent(element: ElementFinder, numberOfSeconds: number = 30000,
  refreshInterval: number = 5000): void {
  return this.refreshElement(element, numberOfSeconds, refreshInterval);
}

// Add async here
protected static async refreshElement(element: ElementFinder, numberOfSeconds: number = 30000, refreshInterval: number = 5000) {

  // Wait untill the promise is resolved
  const isPresent = await element.isPresent();
  // Use the resolved promise
  if (!isPresent) {
    if (numberOfSeconds <= 0) {
      // You can clean the Promise rejection and create 1 line of code
      return Promise.reject('Element cannot be found after expected retry numbers');
    }
    // Wait for the rest
    await browser.sleep(refreshInterval);
    await browser.refresh();
    return this.refreshUntilElementIsPresent(element, numberOfSeconds - (refreshInterval / 1000));
  }
};

Artículos relacionados