Webdriverio: o comportamento de sincronização/assíncrona não está documentado

Criado em 5 out. 2016  ·  34Comentários  ·  Fonte: webdriverio/webdriverio

O problema

Há exemplos de sincronização e assíncrona nos documentos, mas nenhuma explicação de como chamar de forma síncrona versus assíncrona. Isso é terrivelmente confuso. Por acidente, encontrei o código do Timer que faz uma correspondência de string no nome da função anônima passada para waitUntil e, portanto, se comporta de maneira diferente wrt sync/async. Então, nomes de funções semânticas mágicas e não documentadas? Uau, isso é impossível de saber, a menos que você já saiba.

Reproducible Example Missing

Comentários muito úteis

Uma espera perdida pode estragar um dia de trabalho inteiro, por isso acho que deve ser tratado como sincronia em todos os lugares.

É por isso que usamos TypeScript :)

Todos 34 comentários

Esta funcionalidade não está documentada! A diferença por que temos exemplos de sincronização e sincronização é indicada aqui: http://webdriver.io/guide/getstarted/modes.html

O fato de você poder voltar ao comportamento assíncrono também está documentado aqui:
http://webdriver.io/guide/getstarted/upgrade.html

Além disso, a página da API informa:

Cada documentação de comando geralmente vem com um exemplo que demonstra o uso dele usando WebdriverIO de forma assíncrona no modo autônomo e de forma síncrona usando o executor de teste wdio.

Estamos tentando trabalhar nos documentos à medida que lançamos nossas versões. Nunca hesite em entrar em contato com nossoGitter e nunca assuma que não nos importaríamos!

Obrigado. :) Eu nunca teria encontrado os documentos para funções chamadas 'async', já que eles estão na página 'upgrade'. Eu também não esperava encontrá-lo em 'Standalone vs. WDIO'. Depois de ler, ainda não entendi. ;) Comecei com chimp, por razões históricas, mas minhas funções de teste se parecem com o exemplo wdio, não com o exemplo autônomo: as chamadas são síncronas.

Então, 'cliente' e 'navegador' são objetos diferentes, implementando versões assíncronas e sincronizadas da API, respectivamente? Parece que não, porque os documentos de 'atualização' mostram que você pode escolher sincronização/assíncrona em tempo de execução. Então, a API alterna o modo com base no nome da função de especificação da qual foi invocada?

Mas waitUntil alterna independentemente o modo com base no nome da função de condição?

Eu realmente acho que sync/async poderia usar uma seção doc própria.

Olhando para o wdio agora, estou imaginando se meu projeto seria melhor movido para ele, em vez do chimp, pois parece fornecer muito da mesma integração.

Concordo que poderia haver documentos mais claros em torno do nome da função assíncrona, mas foi um recurso introduzido para facilitar os testes de portabilidade para a v4 (na v3 todos os comandos eram assíncronos) e será obsoleto/removido nas próximas versões.

O Chimp tem sua própria implementação de sincronização para comandos. Eles basicamente usam o WebdriverIO no modo autônomo (portanto, no modo assíncrono), mas criam um wrapper em torno dos comandos para torná-los sincronizados.

Então, 'cliente' e 'navegador' são objetos diferentes

Não, eles são basicamente os mesmos. Refiro-me a client para instâncias iniciadas usando o pacote autônomo e browser para instâncias iniciadas pelo wdio testrunner.

Mas waitUntil alterna independentemente o modo com base no nome da função de condição?

Sim, para fins convenientes, desde que você queira criar um comando personalizado que execute uma biblioteca de terceiros que retorne uma promessa.

Se você mudar para o wdio, ainda poderá executar comandos de forma síncrona e também aproveitar todos os repórteres e serviços que o wdio fornece.

Espero que isso deixe claro.
Junte-se ao nossoGitter se você tiver mais perguntas.

@christian-bromann , quais comandos serão preteridos/removidos em versões futuras? Você vai abandonar o suporte para a versão assíncrona no wdio test runner? Ou largar o modo autônomo wdio?

Estou planejando usar o wdio test runner, mas escrevendo todos os testes no modo assíncrono (sync: false) ...

quais comandos serão preteridos/removidos em versões futuras?

Todos os comandos de protocolo que não fazem parte da especificação do W3 Webdriver.

Você vai abandonar o suporte para a versão assíncrona no wdio test runner? Ou largar o modo autônomo wdio?

Mesmo que eu quisesse, não conseguiria. O projeto sempre suportará o modo autônomo porque é assim que funciona naturalmente. Recomendo ainda considerar o uso do modo de sincronização porque ele fornece vários recursos de sintaxe que você não tem com o modo autônomo.

Eu quero interceder pelo modo assíncrono. Veja meu exemplo:

import AccountManager, { Credentials } from '@qa/account-manager';

class Account {
    async session (type: string = 'basic', options: Credentials = {}): Credentials {
        let { type = 'oauth',  host = browser.options.baseUrl } = options;           
        let account = new AccountManager.Session();

        let { cookie, credentials }  = browser.waitForPromise<Credentials>(() => {
            return account.session({ ...options, type, service });
        }, timeout, 'Could not get user session');

        this.setCookie(cookie);

        return credentials;
    }
}

waitForPromise :

let { waitforTimeout = 30 * 1000 } = browser.options;
let lastMessage = '';

browser.addCommand('waitForPromise', <T>(promise: Promise<T> | (() => Promise<T>), timeout = waitforTimeout, message = ''): T => {
    let result = null;

    browser.waitUntil(function async () {
        return new Promise((resolve, reject) => {
            global.setTimeout(() => {
                console.error('Rejection error:', lastMessage);
            }, waitforTimeout);

            if (typeof promise === 'function') {
                promise = promise();
            }

            promise.then((response: T) => {
                result = response;
                resolve(true);
            },
            (error: string) => {
                lastMessage = error;
                resolve(false);
            });
        });
    }, waitforTimeout, `Timeout of ${waitforTimeout}ms exceeded. ${message}`);

    return result;
});

A situação atual:

import AccountManager, { Credentials  } from '@qa/account-manager';

class Account {
    async session (type: string = 'basic', options: Credentials = {}): Credentials {
        let { type = 'oauth',  host = browser.options.baseUrl } = options;           
        let account = new AccountManager.Session();

        let { cookie, credentials } = await account.session<Credentials>({ ...options, type, service });

        this.setCookie(cookie);

        return credentials;
    }
}

No meu caso, o método account.session tem seu próprio timeout interno e boas exceções. Portanto, o modo assíncrono com async/await é a maneira mais preferida de escrever um código ainda mais claro sem nenhum wrapper estranho em waitUntil.

No meu caso, o método account.session tem seu próprio tempo limite interno e boas exceções. Portanto, o modo assíncrono com async/await é a maneira mais preferida de escrever um código ainda mais claro sem nenhum wrapper estranho em waitUntil.

Claro que você pode abstrair qualquer coisa para que fique mais claro para o seu caso de uso. Infelizmente nem todo mundo é um programador avançado e às vezes apenas gosta de uma solução simples e rápida. Async vs sync é apenas uma questão de como ele é executado. Você também pode fazer isso com o comportamento de sincronização.

Recomendo ainda considerar o uso do modo de sincronização porque ele fornece vários recursos de sintaxe que você não tem com o modo autônomo.

Estou curioso para saber mais sobre isso. Quais são essas características? Com as informações que tenho, eu definitivamente escolheria a sintaxe async/await em vez de outra abstração de sincronização.

Por exemplo, aprimoramos o protótipo da resposta do elemento para que você possa fazer coisas como:

var expect = require('chai').expect;
describe('webdriver.io api page', function() {
    it('should be able to filter for commands', function () {
        browser.url('http://webdriver.io/api.html');
        // filtering property commands
        $('.searchbar input').setValue('getT');
        // get all results that are displayed
        var results = $$('.commands.property a').filter(function (link) {
            return link.isVisible();
        });
        // assert number of results
        expect(results.length).to.be.equal(3);
        // check out second result
        results[1].click();
        expect($('.doc h1').getText()).to.be.equal('GETTEXT');
    });
});

Não consegui encontrar documentação sobre a natureza exata das "Promessas" do modo autônomo/assíncrono. Eu tive que verificar este exemplo , executar e modificar os testes para descobrir que

it('gets the title of MDN toppage', () => {
  return browser
    .url('https://developer.mozilla.org/en-US/')
    .getTitle().then(title => {
       assert.equal(title, 'Mozilla Developer Network')
    })
})

não é equivalente a

it('gets the title of MDN toppage', async() => {
  browser.url('https://developer.mozilla.org/en-US/')
  const title = await browser.getTitle()
  assert.equal(title, 'Mozilla Developer Network')
})

mesmo que pareça que deveria ser. No segundo exemplo usando async / await , title acaba sendo uma string vazia.

Existe algum plano para apoiar async / await ? O modo testrunner/síncrono meio que me assusta.

Eu entendo que não é trivial oferecer suporte a promessas reais aqui (porque estou assumindo que uma fila de comandos está sendo construída ala Nightwatch )

Existe algum plano para suportar async/await?

Isso é suportado!

O modo testrunner/síncrono meio que me assusta.

Por que é que?

porque estou assumindo que uma fila de comandos está sendo construída ala Nightwatch

Sem fila de comandos. O objeto cliente é uma Mônada. Ele suporta os dois tipos: a promessa e a mônada ajax.

Junte-se ao nossoGitter para este tipo de perguntas.

Por que é que?

@christian-bromann Eu adoraria ver uma API apenas assíncrona. com async/await roll out. Não acho que a API síncrona seja mais necessária, poderia simplificar os internos do webdriverio, menos propenso a erros.

poderia simplificar os internos do webdriverio, menos propensos a erros.

De que tipo de erros você está falando?

adoraria ver uma API somente assíncrona. com async/await roll out.

Novamente, isso é suportado, você pode usar async/await com WebdriverIO

Se você alegar que algo não é suportado, forneça um exemplo ou algum tipo de prova de que realmente não é suportado.

Eu apenas assumi porque não consegui encontrar um exemplo em nenhum lugar de async/await sendo usado com webdriver.io, e porque tentei e não funcionou (veja o exemplo acima), que async / await não foi suportado! Você poderia, por favor, me apontar na direção de alguns exemplos de funcionamento ou documentação?

A razão pela qual a API síncrona parece estranha para mim é que fui treinado para esperar que operações não triviais em javascript sejam assíncronas (especialmente rede!). É a mesma razão pela qual não gosto da maneira como os retornos de chamada do Nightwatch são executados em um escopo diferente, quebra as expectativas que você tem da linguagem.

Eu tentei e não funcionou (veja o exemplo acima)

Você precisa ser claro porque não estava funcionando, alguma mensagem de erro?

Você poderia, por favor, me apontar na direção de alguns exemplos de funcionamento ou documentação?

Aqui:

var client = require('webdriverio').remote({ desiredCapabilities: { browserName: 'chrome' } });

(async () => {
    await client.init();
    await client.url("...");
    // ....
})()

A razão pela qual a API síncrona parece estranha para mim é que fui treinado para esperar que operações não triviais em javascript sejam assíncronas (especialmente de rede!)

Entendi. Muitas pessoas não têm muita experiência em JS e acham difícil lidar com código assíncrono em seus testes. Ao abstrair tudo isso, fica muito mais fácil escrever testes menos detalhados e propensos a erros. Eu prefiro que meu teste fique assim:

var expect = require('chai').expect;
var DynamicPage = require('../pageobjects/dynamic.page');

describe('dynamic loading', function () {
    it('should be an button on the page', function () {
        DynamicPage.open();
        expect(DynamicPage.loadedPage.isExisting()).to.be.equal(false);

        DynamicPage.btnStart.click();
        DynamicPage.loadedPage.waitForExist();
        expect(DynamicPage.loadedPage.isExisting()).to.be.equal(true);
    });
});

.. do que ter um await na frente de cada linha. Como todos os comandos são assíncronos, para mim não faz sentido deixar o usuário lidar com isso. No entanto, você ainda pode voltar para assíncrono mesmo usando wdio, mas a sintaxe é muito melhor, por exemplo, sendo capaz de fazer isso:

var results = $$('.commands.property a').filter(function (link) {
    return link.isVisible();
});

é incrível e não funcionaria com async/await.

Passos para reproduzir:

  1. Confira este repositório
  2. Executar teste (passa, ou pelo menos quase passa)
  3. Altere test/e2e/first.js para usar async / await (conforme meu exemplo acima)
  4. Executar teste (não passa, title é uma string vazia)

Posso montar um repositório de amostra e abrir um novo problema, se for mais fácil.

Posso montar um repositório de amostra e abrir um novo problema, se for mais fácil.

https://github.com/cognitom/webdriverio-examples/tree/master/standalone-mode já não é um repositório de amostra?

.. do que ter um wait na frente de cada linha. Como todos os comandos são assíncronos, para mim não faz sentido deixar o usuário lidar com isso.

Encontrei meu erro, estava faltando um await antes browser.url() , o que acho que prova seu ponto.

Eu estava cauteloso sobre onde a magia estava escondida, mas agora me sinto confiante de que o modo assíncrono não tem magia oculta. E saber que o modo de sincronização é (presumivelmente) construído diretamente sobre o modo assíncrono também me deixa menos cauteloso com a sincronização.

E saber que o modo de sincronização é (presumivelmente) construído diretamente sobre o modo assíncrono também me deixa menos cauteloso com a sincronização.

👍

Uma espera perdida pode estragar um dia de trabalho inteiro, por isso acho que deve ser tratado como sincronia em todos os lugares.

Uma espera perdida pode estragar um dia de trabalho inteiro, por isso acho que deve ser tratado como sincronia em todos os lugares.

É por isso que usamos TypeScript :)

Devo dizer que apenas discordo do argumento de sincronização por padrão em um nível filosófico. As promessas e a natureza assíncrona do Javascript são conceitos fundamentais da linguagem e eu diria que se você não as entende, não deveria estar escrevendo código javascript, porque está em toda parte. Pelo menos se não escondermos, então a dor fará com que você aprenda.

Devo dizer que concordo totalmente que há uma enorme falta de documentação sobre este tópico. Recentemente, assumi uma enorme base de código (sem handover) que usa webdriver com appuium, nenhum dos quais usei antes.

Li tudo o que pude encontrar, mas não havia explicação de por que eu deveria escolher um sobre o outro. E isso tornou o resto dos documentos bastante confusos para ser honesto. Demorou um pouco antes que eu percebesse que não era uma escolha função por função.

Este tópico finalmente esclareceu para mim, mas teria salvado uma enorme dor de cabeça se o encontrasse alguns dias atrás. Mesmo um link para isso dos documentos seria útil.

ps citação do ano
"se você não entende [promessas], então você não deveria estar escrevendo código javascript" 🤣 🤣 🤣
Por acaso, ressaca do dia de ano novo? 😉

Pergunta rápida - no modo assíncrono, sei que todas as funções do navegador retornam uma promessa. Isso também é verdade para todas as funções de elemento? por exemplo, getText() etc.

EDIT: Desculpe que o exemplo exato é mostrado aqui: https://webdriver.io/docs/api.html
A resposta é sim.

Considere o seguinte caso de uso.

Existe uma função assíncrona searchMessages que busca mensagens de um serviço externo.
Leva algum tempo para o serviço realmente ter essas mensagens, então a promessa resolvida conterá uma lista vazia por alguns X milissegundos. Mas mais tarde, ele conterá algumas mensagens.

Este é o comportamento esperado correto.

Precisamos continuar tentando até que haja conteúdo na lista.

Algo como o seguinte parece NÃO ser possível no wdio:

    let messagesIds;

    // can not pass async here cuz' it is expecting sync function
    await browser.waitUntil(async () => {
      messagesIds = await searchMessages(searchfilter);
      return messagesIds.length >= 1;
    }, TIMEOUT);

   // do something with messagesId

Por quê? É por design? Se sim, existe uma maneira melhor de fazer isso?

===
EDITAR:

Acabou com isso. Funciona, mas fica um pouco feio...

    let messagesIds;

    await browser.waitUntil(() => {
      browser.call(async () => {
        const oAuth2Client = await authorize(credentials, token);

        messagesIds = await searchMessages(oAuth2Client, policyNumber);
      });

      return messagesIds.length >= 1;
    }, TIMEOUT);

   // do something with messagesId

Como isso está relacionado a esse problema? Não há logs, nem etapas para reproduzir, não sei por que não funciona.

Se você conseguir descobrir, por favor, levante um ticket, ficaremos felizes em ajudar.

Obrigada

@mgrybyk não está documentado que waitUntil só pode receber uma função de sincronização.
Eu forneci um exemplo de código que falha.

Acabei de editar meu comentário com uma solução .... mas ainda parece um pouco complicado :)

Dos documentos, eu esperaria que tanto a sincronização quanto a assíncrona sejam válidas aqui.

não está documentado que waitUntil só pode receber uma função de sincronização.
Eu forneci um exemplo de código que falha.

waitUntil respeita as promessas e espera por elas. Forneça um exemplo reproduzível e podemos analisá-lo.

Não foi isso que observei.

Estou usando "webdriverio": "^5.9.6" no typescript e os seguintes plugins/módulos:

    "@wdio/allure-reporter": "^5.9.3",
    "@wdio/cli": "^5.9.4",
    "@wdio/local-runner": "^5.9.6",
    "@wdio/mocha-framework": "^5.9.4",
    "@wdio/selenium-standalone-service": "^5.9.3",
    "@wdio/spec-reporter": "^5.9.3",
    "@wdio/sync": "^5.9.4",

Eu invoco o runner com yarn wdio .

E o compilador falha porque (como explicado no exemplo que postei acima) waitUntil não pode receber uma função assíncrona.

0-0 worker error { name: 'TSError',
  message:
   "⨯ Unable to compile TypeScript:\ntest/specs/basicPolicyOrderHappy.ts(121,7): error TS2345: Argument of type '() => Promise<boolean>' is not assignable to parameter of type '() => boolean'.\n  Type 'Promise<boolean>' is not assignable to type 'boolean'.\n",

waitUntil não pode receber uma função assíncrona.

Pode, as digitações simplesmente não são precisas nessa frente. PRs são bem vindos!

@sombreroEnPuntas , atualize para a versão mais recente, o problema foi corrigido há algum tempo

@sombreroEnPuntas talvez você possa usar uma biblioteca como p-wait-for em vez de browser.waitUntil , supondo que você não esteja usando o testrunner de sincronização

@diachedelic waitUntil espera por promessas, é apenas um problema de digitação que foi corrigido algumas versões atrás.

@sombreroEnPuntas por favor nos avise se este erro persistir após atualizar as versões do webdriverio. Obrigado!

Esta página foi útil?
0 / 5 - 0 avaliações