NicholasBoll / cypress-promise

Allow a Cypress chain to resolve to a promise
100 stars 7 forks source link

cypress-promise with Cypress 4.0 / cypress-cucumber-preprocessor #5

Open jhonyasanuma opened 4 years ago

jhonyasanuma commented 4 years ago

Is cypress-promise still working with Cypress 4.0 and cypress-cucumber-preprocessor?

I tried this simple code:

Given('I do simple request', async function () {
  const foo = await promisify(cy.request('https://www.google.com/'));
  console.log(foo);
});

but received a cypress error:

CypressError: cy.then() timed out after waiting '4000ms'.

Your callback function returned a promise which never resolved.

The callback function was:

() => resolveAndRunStepDefinition.call( this, stepDetails, replaceParameterTags, exampleRowData, state.feature.name )

NicholasBoll commented 4 years ago

I upgraded to v4.0.1 and ran the tests in this repository and everything worked. Do you have some reproduction steps? I don't have any experience with the cypress-cucumber-preprocessor. Maybe you could look at the output?

The tests run in this library are here: https://github.com/NicholasBoll/cypress-promise/blob/master/cypress/integration/example_spec.js

jhonyasanuma commented 4 years ago

Hi @NicholasBoll , thank you for your reply.

I created an exemple project for simplify reproduction steps: https://github.com/jhonyasanuma/cypress-promise-exemple

Test scenarios are at: cypress > integration > features > example.feature

Test steps execution (code) are at: cypress > support > step_definitions > example_steps.js

Cypress execution is same, using npx cypress open / run.

This is the full output of stack trace:

Running: features/exemple.feature (1 of 1)

Exemple project to reproduce issue: https://github.com/NicholasBoll/cypress-promise/issues/5 1) Execute simples request

0 passing (4s) 1 failing

1) Exemple project to reproduce issue: https://github.com/NicholasBoll/cypress-promise/issues/5 Execute simples request: CypressError: cy.then() timed out after waiting '4000ms'.

Your callback function returned a promise which never resolved.

The callback function was:

() => resolveAndRunStepDefinition.call( this, stepDetails, replaceParameterTags, exampleRowData, state.feature.name ) at Object.cypressErr (http://localhost:46811/__cypress/runner/cypress_runner.js:86207:11) at Object.throwErr (http://localhost:46811/__cypress/runner/cypress_runner.js:86162:18) at Object.throwErrByPath (http://localhost:46811/__cypress/runner/cypress_runner.js:86194:17) at http://localhost:46811/__cypress/runner/cypress_runner.js:69302:21 at tryCatcher (http://localhost:46811/__cypress/runner/cypress_runner.js:120203:23) at http://localhost:46811/__cypress/runner/cypress_runner.js:115344:41 at tryCatcher (http://localhost:46811/__cypress/runner/cypress_runner.js:120203:23) at Promise._settlePromiseFromHandler (http://localhost:46811/__cypress/runner/cypress_runner.js:118139:31) at Promise._settlePromise (http://localhost:46811/__cypress/runner/cypress_runner.js:118196:18) at Promise._settlePromise0 (http://localhost:46811/__cypress/runner/cypress_runner.js:118241:10) at Promise._settlePromises (http://localhost:46811/__cypress/runner/cypress_runner.js:118316:18) at Async../node_modules/bluebird/js/release/async.js.Async._drainQueue (http://localhost:46811/__cypress/runner/cypress_runner.js:114928:16) at Async../node_modules/bluebird/js/release/async.js.Async._drainQueues (http://localhost:46811/__cypress/runner/cypress_runner.js:114938:10) at Async.drainQueues (http://localhost:46811/__cypress/runner/cypress_runner.js:114812:14)

Just ask me if you need any addictional information.

Kind regards, Jhony

jhonyasanuma commented 4 years ago

I also created a branch with a example without cucumber-preprocessor, it worked!

Just received an error at second scenario using .promisify()

TypeError: cy.request(...).promisify is not a function

Probably I missed any configuration..

Please, if you could make it work with cucumber-preprocessor it will be very appreciated.

Thank you

NicholasBoll commented 4 years ago

I was able to reproduce. I think this is related to every issue filed against this library - there is just a mismatch for how Cypress works and promises. I'm trying to figure out how to work around.

Your example gets transpiled into "regenerator" code instead of actual promises:

(0, _steps.Given)('I do simple request', function _callee() {
  var foo;
  return _regenerator["default"].async(function _callee$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          console.log('first');
          _context.next = 3;
          return _regenerator["default"].awrap((0, _cypressPromise["default"])(cy.request('https://www.google.com/')));

        case 3:
          foo = _context.sent;
          cy.log('foo', foo);

        case 5:
        case "end":
          return _context.stop();
      }
    }
  });
});

There is an issue where beforeEach doesn't work which I'm assuming Given uses. I'd have to test more to see if it is how the default babel config works. I don't have any options to disable that transpilation.

NicholasBoll commented 4 years ago

I tried without any transpilation (using native async/await by the browser). It doesn't seem to make a difference.

NicholasBoll commented 4 years ago

@jhonyasanuma Do you know if you even need this library? Cypress will already wait for previous commands to finish before new commands are run by doing command enqueuing. Updating your example exemple_steps.js to the following works:

import { Given, Then } from 'cypress-cucumber-preprocessor/steps';

Given('I do simple request', function () {
  cy.request('https://www.google.com/').then(foo => {
    cy.log('foo', foo)
  });
});

Then('I check some results', function () {
  cy.request('https://www.google.com/').then(bar => {
    cy.log('bar', bar);
  });
})

await does prevent nesting, but in most cases it isn't too bad. I'm not sure this library will ever work 100% the way one would expect. I'm sure if I had access to more Cypress internals that I could get things to work, but perhaps what would be better is a transpilier than simply rewrote code to be nested .thens instead of runtime hacks? Similar to what https://github.com/MatAtBread/fast-async does...

jhonyasanuma commented 4 years ago

I was looking for some tool to improve our APIs automation suite, built in node / super test.

Cypress come up with cool features, but the way we work here, using extra abstractions for services and validators layers, unfortunately didn't work well with cypress chained commands.

I achieved simple scenarios using cypress alias (.as) command, but in more complex scenarios became unreadable, plus, without async/await, its add more effort for migration from super test.

Thank you

NicholasBoll commented 4 years ago

I think most people don't understand the compositional power of promises and sometimes async/await is a crutch. Code like:

const foo = await getFoo()
const bar = await getBar(foo)
return await getBaz(bar)

Could be written like:

return getFoo()
  .then(getBar)
  .then(getBaz)

Now there has been times I need 2 variables from 2 different promises and await is more convenient for that. I found that doesn't happen as much in Cypress test code as it does when I'm writing scripts.

One of the most powerful features of Cypress chains is the retryability that is lost if you use a .then. A function inside a .then is never retries.

I made an alternative to .then that allows for retryability: https://github.com/NicholasBoll/cypress-pipe

I can understand the migration effort. I'm not sure this library can work 100%. Maybe a code transformer code

FFdhorkin commented 4 years ago

await does prevent nesting, but in most cases it isn't too bad. I'm not sure this library will ever work 100% the way one would expect. I'm sure if I had access to more Cypress internals that I could get things to work, but perhaps what would be better is a transpilier than simply rewrote code to be nested .thens instead of runtime hacks? Similar to what https://github.com/MatAtBread/fast-async does...

If it'd be possible to turn cypress-promise into a transpiler, that'd be awesome (and probably safer than what it's doing now). For example:

const foo = await cy.request(...).its('body.foo').promisify();
const bar = await cy.request(...).its('body.bar').promisify();
cy.request(...)
    .its('body')
    .should(({count}) => {
        // do some stuff
    });

getting converted to the following before Cypress sees it:

cy.request(...)
    .its('body')
    .then(({foo}) => {
        cy.request(...)
        .its('body')
        .then(({bar}) => {
            cy.request(...)
                .its('body')
                .should(({count}) => {
                    // do some stuff
                });
        });
    });

This is obviously a toy example, but it'd reduce our max indent level (currently, 9) significantly.

marcospassos commented 4 years ago

Same problem here working with cucumber. Any workaround to make it work?

kimgysen commented 3 years ago

Why does Cypress need to re-invent Javascript? Adds zero value wrapping native JS into Cypress-specific api that is unintuitive for developers who have been used to how promises work for a decade. Just all that needless decorator stuff, like seriously.

BodhiHu commented 3 years ago

sigh... cypress makes simple things complex