Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to override type or click? #3838

Closed
ccorcos opened this issue Mar 27, 2019 · 16 comments
Closed

How to override type or click? #3838

ccorcos opened this issue Mar 27, 2019 · 16 comments

Comments

@ccorcos
Copy link

ccorcos commented Mar 27, 2019

Current behavior:

I have a custom command that interacts with the UI and waits for animations to complete called rerender. I want to make sure to invoke rerender before and after clicking and typing. However, I'm not able to figure out the correct way to do this.

I've tried a couple things.

Cypress.Commands.overwrite("click", (originalFn, subject, ...args) => {
	cy.rerender()
	originalFn(subject, ...args)
	cy.rerender()
})
Cypress.Commands.overwrite("click", (originalFn, subject, ...args) => {
	cy.rerender()
	cy.wrap(originalFn(subject, ...args))
	cy.rerender()
})
Cypress.Commands.overwrite("click", (originalFn, subject, ...args) => {
	cy.rerender()
		.then(() => originalFn(subject, ...args))
		.rerender()
})
Cypress.Commands.overwrite("click", (originalFn, subject, ...args) => {
	cy.rerender().then(() => {
		originalFn(subject, ...args).then(() => {
			cy.rerender()
		})
	})
})

They all come up with some sort of error about a promise inside a promise or something...

CypressError: Timed out retrying: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.

The command that returned the promise was:

  > cy.type()

The cy command you invoked inside the promise was:

  > cy.rerender()

Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise.

Cypress will resolve your command with whatever the final Cypress command yields.

The reason this is an error instead of a warning is because Cypress internally queues commands serially whereas Promises execute as soon as they are invoked. Attempting to reconcile this would prevent Cypress from ever resolving.

https://on.cypress.io/returning-promise-and-commands-in-another-command

Any ideas how overwriting these commands is supposed to work?

Versions

Cypress 3.1.5

@jennifer-shehane
Copy link
Member

Can you provide the code for your custom rerender command also?

@ccorcos
Copy link
Author

ccorcos commented Mar 28, 2019

Here's rerender

async function rerender(window) {
	try {
		const RenderQueue = window["__console"]["RenderQueue"]
		await RenderQueue.afterNextFlush()
	} catch (error) {}
}

Cypress.Commands.add("rerender", { prevSubject: "optional" }, subject => {
	return cy
		.wait(10)
		.window()
		.then(rerender)
		.wrap(subject)
})

But I'm also unable to get it to work just with cy.wait(100). I don't get this same error when I use wait, but it doesnt seems to work properly...

@ccorcos
Copy link
Author

ccorcos commented Mar 28, 2019

For example:

Cypress.Commands.overwrite("click", (originalFn, subject, ...args) => {
	cy.wait(100)
	originalFn(subject, ...args)
	cy.wait(100)
	cy.wrap(subject)
})

@ccorcos
Copy link
Author

ccorcos commented Mar 28, 2019

Alright, I've made some progress. I think the problem has to do with the internal abstractions for these commands. The code below works. Now I can properly override those commands.

// If the type command calls the click command, then we have a problem with a promise
// inside a promise. So instead, we'll prevent clicking by always calling focus first.
// https://github.com/cypress-io/cypress/blob/e2e454262bf461f31e947da9f9fdc0a8fa23baf8/packages/driver/src/cy/commands/actions/type.coffee#L348
Cypress.Commands.overwrite("type", (originalFn, subject, ...args) => {
	cy.wrap(subject)
		.focus()
		.then(() => originalFn(subject, ...args))
		.wrap(subject)
})

// Clear calls type inside of and if we don't override it outself, we have another
// promise in a promise issue. So we'll just manually clear it ourselves
// https://github.com/cypress-io/cypress/blob/e2e454262bf461f31e947da9f9fdc0a8fa23baf8/packages/driver/src/cy/commands/actions/type.coffee#L414
Cypress.Commands.overwrite("clear", (originalFn, subject, ...args) => {
	cy.wrap(subject).type("{selectall}{del}")
})

// Wait for animations before and after clicking.
// https://github.com/cypress-io/cypress/issues/3838
Cypress.Commands.overwrite("click", (originalFn, subject, ...args) => {
	cy.wait(200)
		.then(() => originalFn(subject, ...args))
		.wait(200)
		.wrap(subject)
})

Still seems like a bad abstraction internally though. It means you can't override these commands without substantial hacks.

@ccorcos
Copy link
Author

ccorcos commented Mar 28, 2019

The actions are all pretty bogus now though...

image

No more clicks, etc.

@jennifer-shehane
Copy link
Member

You probably want to extend the commands to add logging: https://on.cypress.io/cypress-log Can you close if this issue is resolved for you?

@Lakitna
Copy link
Contributor

Lakitna commented Apr 1, 2019

Logging is not documented very well yet. It might be good to have some examples. Here I have an example of an overwritten command in javascript. And here from the .type() command in coffeescript.

@jennifer-shehane
Copy link
Member

Yeah, it is very poorly/not documented ☹️cypress-io/cypress-documentation#110

@Lakitna
Copy link
Contributor

Lakitna commented Apr 1, 2019

Is there any progress regarding those improvements Brian wants to make? Maybe something to include in 4.0?

@tracer13
Copy link

tracer13 commented Sep 3, 2019

Hi! I seem to have the same question.
I'm trying to override type command so that I can perform a scrollIntoView command inside of it. I tried the following approach:

Cypress.Commands.overwrite('type', (originalFn: any, subject: any, chars: string, options?: any) => {
        cy.wrap(subject).scrollIntoView().then(() => {
            return originalFn(subject, chars, options);
        });
});

if I try cy.get('.classname').type('text') it works. But in case I call cy.get('.classname').clear().type('text') (i.e. after an action command) it fails, stating that:

The command that returned a promise was > cy.click() and the cy command you invoked inside a promise was cy.wrap().

Which makes sense. However, in this case, how can I call cy command (in this case scrollIntoView) without wrapping a subject inside an overridden function?
Thanks in advance!

@dhair-seva
Copy link

dhair-seva commented Sep 19, 2019

I'm pretty sure you need to return cy.wrap, so:

Cypress.Commands.overwrite('type', (originalFn: any, subject: any, chars: string, options?: any) => {
  return cy.wrap(subject).scrollIntoView().then(() => {
    return originalFn(subject, chars, options);
  });
});

@tracer13
Copy link

I tried that and a couple of other variations. Unfortunately, none of them work

@mcphersonzw
Copy link

any update on this? I'm unable to override type() as well.

@pshynin

This comment has been minimized.

@jennifer-shehane
Copy link
Member

Issues in our GitHub repo are reserved for potential bugs or feature requests. This issue will be closed since it appears to be neither a bug nor a feature request.

We recommend questions relating to how to use Cypress be asked in our community chat. Also try searching our existing GitHub issues, reading through our documentation, or searching Stack Overflow for relevant answers.

@Izhaki
Copy link

Izhaki commented Jul 13, 2020

@jennifer-shehane I argue that this is an issue with the documentation.

@ccorcos Has filed this in Gitter, but never got a reply.

I'm having exactly the same issue:

Cypress.Commands.overwrite('click', (originalFn, subject, ...args) => {
  console.log(args);
  cy.wrap(subject).should($el => {
    expect(Cypress.dom.isDetached($el)).to.be.false; // Ensure the element is attached
    // This won't work in all cases
    // originalFn(subject, ...args);

    // Using Jquery .click() here so no queuing from cypress side and not chance for the element to detach
    $el.click();
  });
});

And all .type() fail with:

Error:       CypressError: Timed out retrying: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.

The command that returned the promise was:

  > `cy.type()`

The cy command you invoked inside the promise was:

  > `cy.wrap()`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants