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

Enable access to clipboard in test #2752

Open
zepatrik opened this issue Nov 9, 2018 · 31 comments
Open

Enable access to clipboard in test #2752

zepatrik opened this issue Nov 9, 2018 · 31 comments
Labels
pkg/driver This is due to an issue in the packages/driver directory type: feature New feature that does not currently exist

Comments

@zepatrik
Copy link

zepatrik commented Nov 9, 2018

Current behavior:

There is no way to access the clipboard in a test, as far as I can tell.

Desired behavior:

There should be an easy way to test what the clipboard holds. Useful for websites where text is placed in the clipboard using JS to transfere it somewhere.

Versions

all versions

@jennifer-shehane jennifer-shehane added the type: feature New feature that does not currently exist label Nov 9, 2018
@jennifer-shehane jennifer-shehane added stage: proposal 💡 No work has been done of this issue pkg/driver This is due to an issue in the packages/driver directory labels Jan 30, 2019
@jennifer-shehane
Copy link
Member

jennifer-shehane commented Jan 30, 2019

Related issues

@dwelle
Copy link

dwelle commented Mar 25, 2019

Accessing clipboard can be worked around, but the main issue is that the document.execCommand('copy') doesn't work (as stated above), which I reckon is the primary (and only?) way for your app to programmatically put a text to user's clipboard.

Assuming it happens somehow (or is fixed upstream), the checking the clipboard's contents can be done e.g. by using clipboardy:

npm i -D clipboardy

plugins/index.js:

const clipboardy = require('clipboardy');
module.exports = ( on ) => {
    on('task', {
        getClipboard () {
            return clipboardy.readSync();
        }
    });
};

In your spec:

describe('test', () => {
    it('test', () => {
        cy.document().then( doc => {
            doc.body.innerHTML = '<input id="inp">';
        });
        cy.get('#inp').type('test{selectall}');
        cy.document().then( doc => {
            // this currently doesn't work unless you manually put focus
            //  into the AUT preview window (e.g. by clicking)
            doc.execCommand('copy');
        });
        cy.task('getClipboard').should('contain', 'test');
    });
});

@nickytonline
Copy link

I have a workaround for pasting in the interim. It fits the use case I have for pasting data, so I thought I'd share. See https://gist.github.com/nickytonline/bcdef8ef00211b0faf7c7c0e7777aaf6

@asherccohen
Copy link

See also #2851

@DRiewaldt
Copy link

Is there a reason why Cypress would block a .click() action when clicking a button that is doing a copy action? I keep getting a growl on our site during test runs and it states "Copy to clipboard not compatible with this browser! Try Chrome!" except, i'm running Cypress with Chrome.

HTML (The formatting might look off, that's just to fit it on screen without a scrollbar):

<a message-element="" class="btn btn-test btn-block btn-sm print-hide ng-isolate-scope" href="javascript:void(0)" 
ng-click="copyLink()" title="" data-toggle="tooltip" data-container="body" data-placement="top" 
data-original-title="Click to copy">
<i class="fa fa-clipboard"></i> <span class="notranslate" data-cy="detailsLinkButton">Link< /span>
</a>

This fails every time I do a cy.get('[data-cy=detailsLinkButton]').click(); Which prevents me from using clipboardy to verify results.

After the test has run in the Test Runner I can click the button and I'll get the proper green growl that states it's been copied to my clipboard, but it won't work while the test is running. Any ideas?

I originally thought this was an issue with clipboardy, but it works elsewhere on our site, and I eventually stripped clipboardy out of the test entirely just to see what would happen with the click. That's when i found Cypress seems to be blocking the action.

@DerMolly
Copy link

We're currently experiencing the same: You can't copy to clipboard in cypress… in Firefox. In Chrome it seems to work fine.
This could be related to the special handling of copying in Firefox mentioned on can I use.

Writing to the clipboard is available without permission in secure contexts and browser extensions, but only from user-initiated event callbacks. Browser extensions with the "clipboardWrite" permission can write to the clipboard at any time.

As nobody want's to introduce https to the e2e testing stage (I think), maybe cypress could use the mentioned "clipboardWrite" permission in Firefox?

@nicholaschiang
Copy link

@zepatrik Here's my current approach:

  1. Spy or stub out the navigator.clipboard.writeText method that your application calls to copy text.
  2. Assert that the method was called with the correct content.

This is what that looks like when implemented in Typescript:

describe('My website page', () => {
  it('copies signup links to clipboard', () => {
    cy.visit(`/${school.id}/users`, {
      onBeforeLoad(win: Window): void {
        cy.spy(win.navigator.clipboard, 'writeText').as('copy');
      },
    });
    cy.contains('button', 'Share signup link').click();
    cy.get('@copy').should('be.calledWithExactly', `http://localhost:3000/${school.id}/signup`);
  });
});

Note that this approach doesn't solve every problem. For example, if you use the invisible textarea workaround (for older browsers), I still don't know of any way to assert about the actual content of the system clipboard.

@jwboulet
Copy link

At least for apps using the Clipboard api to write to the clipboard, this worked to check the contents of the clipboard.

cy.window().then((win) => {
        win.navigator.clipboard.readText().then((text) => {
          assert.equal(
            text,
            "copied text"
          );
        });
      });

@sammacorpy
Copy link

Is there a reason why Cypress would block a .click() action when clicking a button that is doing a copy action? I keep getting a growl on our site during test runs and it states "Copy to clipboard not compatible with this browser! Try Chrome!" except, i'm running Cypress with Chrome.

HTML (The formatting might look off, that's just to fit it on screen without a scrollbar):

<a message-element="" class="btn btn-test btn-block btn-sm print-hide ng-isolate-scope" href="javascript:void(0)" 
ng-click="copyLink()" title="" data-toggle="tooltip" data-container="body" data-placement="top" 
data-original-title="Click to copy">
<i class="fa fa-clipboard"></i> <span class="notranslate" data-cy="detailsLinkButton">Link< /span>
</a>

This fails every time I do a cy.get('[data-cy=detailsLinkButton]').click(); Which prevents me from using clipboardy to verify results.

After the test has run in the Test Runner I can click the button and I'll get the proper green growl that states it's been copied to my clipboard, but it won't work while the test is running. Any ideas?

I originally thought this was an issue with clipboardy, but it works elsewhere on our site, and I eventually stripped clipboardy out of the test entirely just to see what would happen with the click. That's when i found Cypress seems to be blocking the action.

@DRiewaldt I am facing the exact same issue, I am using chrome Version 87.0.4280.141 (Official Build) (x86_64)
did you found any workaround or solution for this?

@agugut-nexgen
Copy link

hello @sammacorpy @DRiewaldt I have the same issue.
I have an app that click on a button that is doing the copy action, but when I try to recover the clipboard, it is empty, neither using the clipboardy plugin that @dwelle suggest.
Could you solve it?

@agugut-nexgen
Copy link

Accessing clipboard can be worked around, but the main issue is that the document.execCommand('copy') doesn't work (as stated above), which I reckon is the primary (and only?) way for your app to programmatically put a text to user's clipboard.

Assuming it happens somehow (or is fixed upstream), the checking the clipboard's contents can be done e.g. by using clipboardy:

npm i -D clipboardy

plugins/index.js:

const clipboardy = require('clipboardy');
module.exports = ( on ) => {
    on('task', {
        getClipboard () {
            return clipboardy.readSync();
        }
    });
};

In your spec:

describe('test', () => {
    it('test', () => {
        cy.document().then( doc => {
            doc.body.innerHTML = '<input id="inp">';
        });
        cy.get('#inp').type('test{selectall}');
        cy.document().then( doc => {
            // this currently doesn't work unless you manually put focus
            //  into the AUT preview window (e.g. by clicking)
            doc.execCommand('copy');
        });
        cy.task('getClipboard').should('contain', 'test');
    });
});

Hi @dwelle , What do you refer with manually put focus? In which element?

@DRiewaldt
Copy link

Is there a reason why Cypress would block a .click() action when clicking a button that is doing a copy action? I keep getting a growl on our site during test runs and it states "Copy to clipboard not compatible with this browser! Try Chrome!" except, i'm running Cypress with Chrome.
HTML (The formatting might look off, that's just to fit it on screen without a scrollbar):

<a message-element="" class="btn btn-test btn-block btn-sm print-hide ng-isolate-scope" href="javascript:void(0)" 
ng-click="copyLink()" title="" data-toggle="tooltip" data-container="body" data-placement="top" 
data-original-title="Click to copy">
<i class="fa fa-clipboard"></i> <span class="notranslate" data-cy="detailsLinkButton">Link< /span>
</a>

This fails every time I do a cy.get('[data-cy=detailsLinkButton]').click(); Which prevents me from using clipboardy to verify results.
After the test has run in the Test Runner I can click the button and I'll get the proper green growl that states it's been copied to my clipboard, but it won't work while the test is running. Any ideas?
I originally thought this was an issue with clipboardy, but it works elsewhere on our site, and I eventually stripped clipboardy out of the test entirely just to see what would happen with the click. That's when i found Cypress seems to be blocking the action.

@DRiewaldt I am facing the exact same issue, I am using chrome Version 87.0.4280.141 (Official Build) (x86_64)
did you found any workaround or solution for this?

I have not, I'm actually back here to see if there was any movement on this front. I'm hoping the Cypress team adds functionality for clipboard access, that'd be crazy handy.

@bahmutov
Copy link
Contributor

@DRiewaldt can you provide a small repo with the app and test code - as simple as possible? So we can take a look at 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

@DRiewaldt
Copy link

@DRiewaldt can you provide a small repo with the app and test code - as simple as possible? So we can take a look at this.

I can't, company code. And I don't have the skill to create an example. Though, it seems you already whipped one up. I'll look through your example, and see if helps me. It seems clipboardy just doesn't work anywhere now.

I'll have to go through and make sure I didn't somehow bork something, and see what's different than your example. Thanks for the response.

@bogusua
Copy link

bogusua commented Aug 13, 2021

clipboardy works in version 7.7.0 and completely refuses to work since version 8.0.0 (browser chrome)

I don’t know what it’s connected with, but I suffer from a lack of this feature

@Kanapriya
Copy link

clipboardy is not working in chrome browser with headless mode, but it's working with interactive mode. Is it kind of limitation with headless mode?

@a-chatterjee
Copy link

clipboardy is not working in chrome browser with headless mode, but it's working with interactive mode. Is it kind of limitation with headless mode?

Cypress default events are simulated. That means that all events like cy.click or cy.type are fired from javascript. That's why these events will be untrusted (event.isTrusted will be false) and they can behave a little different from real native events.

@Kanapriya check this out https://github.com/dmtrKovalenko/cypress-real-events . this works with clipboardy in all modes

@lipingguo0
Copy link

@Kanapriya @a-chatterjee I am in Cypress Version: 7.7.0. Here are the test results I observed:

  1. [chrome][headed] cy.click--->Pass

  2. [chrome][headless] cy.click--->Failed

  3. [chrome][headed] cy.realClick--->Pass

  4. [chrome][headless] cy.realClick--->Failed
    To summarize, headless chrome will always fail for clipboard scenario regardless of using cypress own click or cypress-real-events.realclick

  5. [electron][headed] cy.click--->Pass

  6. [electron][headless] cy.click--->Pass

  7. [electron][headed] cy.realClick--->Pass

  8. [electron][headless] cy.realClick--->Pass
    To summarize, electron will always pass for clipboard no matter it is in headed or headless module and triggered by cypress own click or cypress-real-events.realclick.

@jennifer-shehane is it limitation we can't run clipboard related cases in chrome headless module?

@jennifer-shehane
Copy link
Member

@lipingguo0 This is a limitation. That is why this issue is still open. Accessing the clipboard data is not consistent and we don't have a clear API built into Cypress to do this.

marabesi added a commit to marabesi/json-tool that referenced this issue Oct 29, 2021
@ch-liuzhide
Copy link

try this code?
cy.wrap( Cypress.automation('remote:debugger:protocol', { command: 'Browser.grantPermissions', params: { permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'], }, }) );

@nileshgulia1
Copy link

For me adding a custom command and just triggering a custom "paste" event works.

Cypress.Commands.add(
  'pasteClipboard',
  { prevSubject: true },
  (query, htmlContent) => {
    return cy
      .wrap(query)
      .trigger('paste', createHtmlPasteEvent(htmlContent));
  },
const createHtmlPasteEvent = (htmlContent) =>
Object.assign(new Event('paste', { bubbles: true, cancelable: true }), {
  clipboardData: {
    getData: (type = 'text/html') => htmlContent,
    types: ['text/html'],
  },
});

@WeamAdel
Copy link

Try using cy.window() and you can use the navigator.clipboard as normal for pasting and copying.

it("Paste cliboard numbers", () => {
    cy.window()
      .then((win) => {
        return win.navigator.clipboard.writeText("234");
      })
      .then(() => {
        cy.get("[data-test=paste]").click();
        cy.get("[data-test=calc-input]").should("have.text", 234);
      });
  });

@Bopzor
Copy link

Bopzor commented Feb 14, 2022

Hello!

None of the previous proposal worked for me.

My baseUrl is not localhost and can be http instead of https. This mean that window.navigator.clipboard doesn't exist.

I manage to test my feature that was using the clipboard adding the following in my support/index.js file:

Cypress.on('window:before:load', (win) => {
  let copyText;

  if (!win.navigator.clipboard) {
    win.navigator.clipboard = {
      __proto__: {},
    };
  }

  win.navigator.clipboard.__proto__.writeText = (text) => (copyText = text);
  win.navigator.clipboard.__proto__.readText = () => copyText;
});

At first, I was "overriding" the win.navigator.clipboard methods, (without __proto__ usage) and this worked for http baseUrl.
But with https baseUrl, I got an error because win.navigator.clipbord exist and is readonly. Using __proto__ worked in both context (http and https, chrome and firefox browsers)

It doesn't feel like a clean solution, but I do not see any possible issue at this time.

@owenbendavies
Copy link

cy.wrap( Cypress.automation('remote:debugger:protocol', { command: 'Browser.grantPermissions', params: { permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'], }, }) );

This worked for us. Thank you.

@zepatrik Here's my current approach:

  1. Spy or stub out the navigator.clipboard.writeText method that your application calls to copy text.
  2. Assert that the method was called with the correct content.

This is what that looks like when implemented in Typescript:

describe('My website page', () => {
  it('copies signup links to clipboard', () => {
    cy.visit(`/${school.id}/users`, {
      onBeforeLoad(win: Window): void {
        cy.spy(win.navigator.clipboard, 'writeText').as('copy');
      },
    });
    cy.contains('button', 'Share signup link').click();
    cy.get('@copy').should('be.calledWithExactly', `http://localhost:3000/${school.id}/signup`);
  });
});

Note that this approach doesn't solve every problem. For example, if you use the invisible textarea workaround (for older browsers), I still don't know of any way to assert about the actual content of the system clipboard.

This didn't appear to work for me.

@alice42
Copy link

alice42 commented Jun 28, 2022

My test to check the contents of the clipboard was flaky on Chrome, but it seems to do the trick.

 cy.window().then(win =>
      new Cypress.Promise((resolve, reject) => win.navigator.clipboard.readText().then(resolve).catch(reject)).then(
        text => {
        // ...check
        }
      )

from this answser

@gyurielf
Copy link

@Kanapriya @a-chatterjee I am in Cypress Version: 7.7.0. Here are the test results I observed:

  1. [chrome][headed] cy.click--->Pass
  2. [chrome][headless] cy.click--->Failed
  3. [chrome][headed] cy.realClick--->Pass
  4. [chrome][headless] cy.realClick--->Failed
    To summarize, headless chrome will always fail for clipboard scenario regardless of using cypress own click or cypress-real-events.realclick
  5. [electron][headed] cy.click--->Pass
  6. [electron][headless] cy.click--->Pass
  7. [electron][headed] cy.realClick--->Pass
  8. [electron][headless] cy.realClick--->Pass
    To summarize, electron will always pass for clipboard no matter it is in headed or headless module and triggered by cypress own click or cypress-real-events.realclick.

@jennifer-shehane is it limitation we can't run clipboard related cases in chrome headless module?

I spent a lot of time to resolve and I ended up on same. :)...Thanks.

@SalahAdDin
Copy link

@Kanapriya @a-chatterjee I am in Cypress Version: 7.7.0. Here are the test results I observed:

  1. [chrome][headed] cy.click--->Pass
  2. [chrome][headless] cy.click--->Failed
  3. [chrome][headed] cy.realClick--->Pass
  4. [chrome][headless] cy.realClick--->Failed
    To summarize, headless chrome will always fail for clipboard scenario regardless of using cypress own click or cypress-real-events.realclick
  5. [electron][headed] cy.click--->Pass
  6. [electron][headless] cy.click--->Pass
  7. [electron][headed] cy.realClick--->Pass
  8. [electron][headless] cy.realClick--->Pass
    To summarize, electron will always pass for clipboard no matter it is in headed or headless module and triggered by cypress own click or cypress-real-events.realclick.

@jennifer-shehane is it limitation we can't run clipboard related cases in chrome headless module?

I spent a lot of time to resolve and I ended up on same. :)...Thanks.

It seems this feature is not available in Cypress in any way.

marabesi added a commit to marabesi/json-tool that referenced this issue Dec 9, 2022
It also disables one test that writes in the clipboard due lack of support in the clipboard api

refs cypress-io/cypress#2752 w3c/clipboard-apis#182 w3c/clipboard-apis#52
@faltfe
Copy link

faltfe commented Dec 19, 2022

const clipboardy = require('clipboardy');
module.exports = ( on ) => {
    on('task', {
        getClipboard () {
            return clipboardy.readSync();
        }
    });
};

This approach is working for clipboardy < 3.0.0. With version 3.0.0 of clipboardy they changed to type: module and Cypress reject with

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for C:\...\cypress.config.ts
    at new NodeError (node:internal/errors:371:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:87:11)
    at defaultGetFormat (node:internal/modules/esm/get_format:102:38)
    at defaultLoad (node:internal/modules/esm/load:21:14)
    at ESMLoader.load (node:internal/modules/esm/loader:359:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:280:58)
    at new ModuleJob (node:internal/modules/esm/module_job:66:26)
    at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:297:17)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:261:34)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:337:24)
    at async importModuleDynamicallyWrapper (node:internal/vm/module:437:15)
    at async loadFile (C:\Users\...\AppData\Local\Cypress\Cache\11.2.0\Cypress\resources\app\node_modules\@packages\server\lib\plugins\child\run_require_async_child.js:106:14)
    at async EventEmitter. (C:\Users\...\AppData\Local\Cypress\Cache\11.2.0\Cypress\resources\app\node_modules\@packages\server\lib\plugins\child\run_require_async_child.js:116:32)

It still does not work for me when using import clipboard from 'clipboardy' instead of the require statement.

@cellog
Copy link

cellog commented Mar 25, 2023

note that in linux ARM64, clipboardy crashes with default ubuntu packages. You need to install the xsel package through apt-get and then it works great. What's really sinister is that if you're using e2e tests, there is no error message even when DEBUG=cypress:*. I only figured this out by running it in a component test. Then the error message was in the command log. Took 3 days to figure this one out. I hope it helps someone else!

CharlieBrownCharacter pushed a commit to devqaly/devqaly that referenced this issue Aug 26, 2023
- before loading the window, we are overwritting `win.navigator.clipboard.__proto__.writeText` and `win.navigator.clipboard.__proto__.readText`
	- @see cypress-io/cypress#2752 (comment)
@em-twines
Copy link

Hello!

None of the previous proposal worked for me.

My baseUrl is not localhost and can be http instead of https. This mean that window.navigator.clipboard doesn't exist.

I manage to test my feature that was using the clipboard adding the following in my support/index.js file:

Cypress.on('window:before:load', (win) => {
  let copyText;

  if (!win.navigator.clipboard) {
    win.navigator.clipboard = {
      __proto__: {},
    };
  }

  win.navigator.clipboard.__proto__.writeText = (text) => (copyText = text);
  win.navigator.clipboard.__proto__.readText = () => copyText;
});

At first, I was "overriding" the win.navigator.clipboard methods, (without __proto__ usage) and this worked for http baseUrl. But with https baseUrl, I got an error because win.navigator.clipbord exist and is readonly. Using __proto__ worked in both context (http and https, chrome and firefox browsers)

It doesn't feel like a clean solution, but I do not see any possible issue at this time.

I just wanted to add on to this -- thank you so much, this is eventually what worked for me, but updated to go in cypress.config I've got it written as:


module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {

      on('before:spec', (win) => {
        let copyText;
      
        if (!win.navigator.clipboard) {
          win.navigator.clipboard = {
            __proto__: {},
          };
        }
      
        win.navigator.clipboard.__proto__.writeText = (text) => (copyText = text);
        win.navigator.clipboard.__proto__.readText = () => copyText;
      });
     }
   }
});

Thanks again for the solution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg/driver This is due to an issue in the packages/driver directory type: feature New feature that does not currently exist
Projects
None yet
Development

No branches or pull requests