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

document.execCommand("copy") does not work in cypress #2851

Open
tnrich opened this issue Nov 28, 2018 · 23 comments
Open

document.execCommand("copy") does not work in cypress #2851

tnrich opened this issue Nov 28, 2018 · 23 comments
Labels
prevent-stale mark an issue so it is ignored by stale[bot] stage: ready for work The issue is reproducible and in scope topic: native events type: bug

Comments

@tnrich
Copy link
Contributor

tnrich commented Nov 28, 2018

Hi there,

I'm curious as to why a document.execCommand("copy") fails when being run during a cypress test (as a side effect to a cy.get().click() ). Curiously it works fine once I'm manually clicking the same element after the test has run.

The failing test in question can be reproduced by running the following code:

describe("menuBar", function() {
  beforeEach(() => {
    cy.visit("http://teselagen.github.io/openVectorEditor/#/Editor");
  });
  
  it("select range, copy, cut works", function() {
    cy.clock()
    cy.get('.tg-menu-bar').contains("Edit").click()
    cy.get('.tg-menu-bar-popover').contains("Select").click()
    cy.get(`[label="From:"]`).clear().type("10")
    cy.get(`[label="To:"]`).clear().type("20")
    cy.get(`.dialog-buttons`).contains("OK").click()
    cy.get(".veStatusBar").contains(`10 to 20`)

    cy.get(".veStatusBar").contains(`5299`)
    cy.get('.tg-menu-bar').contains("Edit").click().tick(200)
    cy.get('.tg-menu-bar-popover').contains("Copy").click()
    cy.contains("Selection Copied")
    cy.get('.tg-menu-bar').contains("Edit").click()
    cy.get('.tg-menu-bar-popover').contains("Cut").click()
    cy.contains("Selection Cut")
    cy.get(".veStatusBar").contains(`5288`)
  });
});

If you run the above example, the test will fail to find "Selection Copied". This is because

const worked = document.execCommand(type);

evaluates to false so the "Selection Copied" popover is not shown.

You can add a breakpoint manually to the commands/index.js file and see it not working.
image

Strangely, after the test has run if you manually click "File > Edit > Copy" then the document.execCommand("copy") works just fine. Not sure what is going on here to stop it from working when cypress is running its tests.

image

image

Thanks!

@jennifer-shehane
Copy link
Member

I thought this piece of info from MDN docs looks interesting concerning the return value of document.execCommand()

Note: Only returns true if part of a user interaction. Don't try using the return value to verify browser support before calling a command.

Since Cypress programmatically triggers the 'click' event, the document.execCommand() will always return true in this case. This looks like another reason we are implementing native events. #311

@tnrich
Copy link
Contributor Author

tnrich commented Nov 29, 2018

@jennifer-shehane so in the future I should be able to do cy.get().click({native:true}) to have the test pass as expected? Is there an expected date the native events feature will land?

Thanks!

@jennifer-shehane
Copy link
Member

@tnrich Keep up with the checklist in the main comment of the #311 thread - it is being kept up to date.

@kuceb
Copy link
Contributor

kuceb commented Dec 3, 2018

@tnrich can you see if passing a true/false value to the showDefaultUI (second arg) of document.execCommand makes a difference?

document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)

@tnrich
Copy link
Contributor Author

tnrich commented Dec 3, 2018

@bkucera passing true/false as the second arg doesn't seem to have any effect on the copy command getting triggered from the cypress click.

@jennifer-shehane jennifer-shehane added the stage: ready for work The issue is reproducible and in scope label Dec 4, 2018
@kuceb
Copy link
Contributor

kuceb commented Dec 4, 2018

@jennifer-shehane is correct, the copy command can only happen in response to a trusted user event, which can only be done with native events.
I'll add this to the native events issue

@jennifer-shehane
Copy link
Member

Related to

@asherccohen
Copy link

@jennifer-shehane Any news on copy/pasting feature in Cypress?

@bhellema
Copy link

@jennifer-shehane we're also looking for an update on this. Thanks

@xcao-riffyn
Copy link

xcao-riffyn commented Aug 19, 2019

agreed with @bhellema really hoping this eventually will work. I might be able to offer some help. The biggest barrier that I can see is how cypress sends key combinations(not sure how it works). I came from webdriver.io background so there is .keys cmd there. But I didn't have any luck with cmd-c, cmd-v via that tool. Therefore I'm looking into cypress now. I know an npm module that could copy paste the content from current clipboard: https://www.npmjs.com/package/copy-paste maybe we can incorporate that into cypress. I only have experience using the paste functionality of it. maybe I can look into copy more now.

@dwelle
Copy link

dwelle commented Aug 20, 2019

@xcao-riffyn copying to/from clipboard in cypress isn't a problem. This issue is about document.execCommand("copy") not working and using a lib to manipulate OS clipboard isn't gonna solve it because we need to test AUT behavior --- our app itself uses document.execCommand("copy") so that's why we need to make that DOM command work properly.

@kuceb
Copy link
Contributor

kuceb commented Aug 29, 2019

There is a way we can make this testable. We can overwrite document.execCommand and store the copied text in a "fake" clipboard object that exists during the lifetime of the test. That way you could do something like:

cy.get('button').click().then(()=>{
  expect(cy.clipboard()).to.contain('sometext')
})

This is just one possible version of the API. I would appreciate feedback

@dwelle
Copy link

dwelle commented Aug 30, 2019

@bkucera problem with mocking is you won't know if document.execCommand will succeed in real conditions. As an interim solution, sounds good.

@kuceb
Copy link
Contributor

kuceb commented Aug 30, 2019

@dwelle I believe we can check the stack for a cypress click command, using rules that mimic the rules that the browser follows for user interaction. So when execCommand is called we can return true/false and push to clipboard conditionally

@asherccohen
Copy link

Is there any update on this?

@tnrich
Copy link
Contributor Author

tnrich commented Nov 13, 2019

@bkucera ship it! I'd love to use your suggested

cy.get('button').click().then(()=>{
  expect(cy.clipboard()).to.contain('sometext')
})

@rahul-sharma-uipath
Copy link

any update on this?

@jennifer-shehane
Copy link
Member

This issue is still in the 'ready for work' stage, which means no work has been done on this issue as of today, so we do not have an estimate on when this will be delivered.

@agugut-nexgen
Copy link

Is there any update on this?

@bahmutov
Copy link
Contributor

Initial work to show how to access the clipboard from the test is in this recipe https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/testing-dom__clipboard

In general, applications using navigator.clipboard API seem to work from Cypress in Chrome and Electron browsers. The applications using document.execCommand seem to work in Electron browser only.

In your particular case I ran the test as shown below in Electron using Cypress v7.4.0 and it passed

/// <reference types="cypress" />
describe("menuBar", function() {
  beforeEach(() => {
    cy.visit("/openVectorEditor/#/Editor");
  });

  it("select range, copy, cut works", function() {
    cy.clock()
    cy.get('.tg-menu-bar').contains("Edit").click()
    cy.get('.tg-menu-bar-popover').contains("Select").click()
    cy.get(`[label="From:"]`).clear().type("10")
    cy.get(`[label="To:"]`).clear().type("20")
    cy.get(`.dialog-buttons`).find('[type=submit]').click()
    cy.get(".veStatusBar").contains(`10 to 20`)

    cy.get(".veStatusBar").contains(`5299`)
    cy.get('.tg-menu-bar').contains("Edit").click().tick(200)
    cy.get('.tg-menu-bar-popover').contains("Copy").click()
    cy.contains("Selection Copied")
    cy.get('.tg-menu-bar').contains("Edit").click()
    cy.get('.tg-menu-bar-popover').contains("Cut").click()
    cy.contains("Selection Cut")
    cy.get(".veStatusBar").contains(`5288`)
  });
});

Screen Shot 2021-05-26 at 3 43 39 PM

@RuanECarvalho
Copy link

Any news on it? There is some bypass to this?

@Bjyotika
Copy link

Any update on this as due to this issue unable to test in cypress which require clipboard copy approach

@MattSzCosta
Copy link

MattSzCosta commented Nov 6, 2024

Hey guys, I had the same issue while testing copy/paste behavior on the rich text editor. I managed to work around the problem. It's a bit tricky, but it worked for me.

I add an eventListnner to the copy event and send it to a callback function to manipulate the data.

Cypress.Commands.add('copy', { prevSubject: true }, (subject, onCopy) => {
  cy.window().then((win) => {
    cy.stub(win.document, 'execCommand').callsFake(() => true)
    win.document.addEventListener('copy', (event) => {
      onCopy?.(event)
    })
  })
  const clipboardData = new DataTransfer()
  const copyEvent = new ClipboardEvent('copy', {
    bubbles: true,
    cancelable: true,
    clipboardData,
  })
  subject[0].dispatchEvent(copyEvent)
})

Usage:

const onCopy = (event: ClipboardEvent) => {
  const copiedHtmlText = event.clipboardData?.getData('text/html') || ''
  expect(copiedHtmlText).contains(`input text`)
}

cy.findByLabelText('.input').then(($editable) => {
  const editable = $editable[0]
  const range = document.createRange()
  range.selectNodeContents(editable)
  const selection = window.getSelection()
  selection?.removeAllRanges()
  selection?.addRange(range)

  cy.wrap(editable).copy(onCopy)
})
Cypress.Commands.add('paste', { prevSubject: true }, (subject, payload) => {
  const clipboardData = new DataTransfer()
  Object.keys(payload).forEach((dataType) => {
    clipboardData.setData(dataType, payload[dataType])
  })
  const pasteEvent = new ClipboardEvent('paste', {
    bubbles: true,
    cancelable: true,
    clipboardData,
  })
  subject[0].dispatchEvent(pasteEvent)
  return subject
})

// Usage
cy.findAllByTestId('.input').paste({ 'text/html': "<p>test</p>", 'text/plain': "test" })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
prevent-stale mark an issue so it is ignored by stale[bot] stage: ready for work The issue is reproducible and in scope topic: native events type: bug
Projects
None yet
Development

No branches or pull requests