Skip to content

Instantly share code, notes, and snippets.

@nickytonline
Last active January 25, 2024 06:01
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nickytonline/bcdef8ef00211b0faf7c7c0e7777aaf6 to your computer and use it in GitHub Desktop.
Save nickytonline/bcdef8ef00211b0faf7c7c0e7777aaf6 to your computer and use it in GitHub Desktop.
Simulate a paste event in Cypress
/**
* Simulates a paste event.
*
* @param pasteOptions Set of options for a simulated paste event.
* @param pasteOptions.destinationSelector Selector representing the DOM element that the paste event should be dispatched to.
* @param pasteOptions.pastePayload Simulated data that is on the clipboard.
* @param pasteOptions.pasteFormat The format of the simulated paste payload. Default value is 'text'.
*/
function paste({ destinationSelector, pastePayload, pasteType = 'text' }) {
// https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
cy.get(destinationSelector).then($destination => {
const pasteEvent = Object.assign(new Event('paste', { bubbles: true, cancelable: true }), {
clipboardData: {
getData: (type = pasteType) => pastePayload,
},
});
$destination[0].dispatchEvent(pasteEvent);
});
}
@nickytonline
Copy link
Author

nickytonline commented Jun 27, 2019

I tried the key combinations as suggested, e.g.

cy.get(selector).type('{selectall}');
cy.get(selector).type('{cmd}', { release: false });
cy.get(selector).type('C');
cy.get(selector).type('V');

but they didn't work, so I came up with this.

Note that the solution in this gist is faking what's on the clipboard. I still haven't figured out how to get copy to work.

@nickytonline
Copy link
Author

I imagine, a centralized simulated clipboard store could work and then the copy event could be simulated as well.

@nickytonline
Copy link
Author

If you want to make it a child command, you can do the following:

Cypress.Commands.add(
    'paste',
    {
        prevSubject: true,
    },
    paste
);

and modify the function to:

/**
 * Simulates a paste event.
 *
 * @param subject A jQuery context representing a DOM element.
 * @param pasteOptions Set of options for a simulated paste event.
 * @param pasteOptions.pastePayload Simulated data that is on the clipboard.
 * @param pasteOptions.simple Determines whether or not to use a simple paste. Use this when there is no paste event bound to the element
 *                              resolved by the selector.
 * @param pasteOptions.pasteFormat The format of the simulated paste payload. Default value is 'text'.
 *
 * @returns The subject parameter.
 *
 * @example
 * cy.get('some-selector').paste({
 *  pastePayload: 'yolo,
 *  simple: false,
*  });
 */
export function paste(subject, { pastePayload, simple = true, pasteType = 'text' }) {
    if (simple) {
        subject[0].value = pastePayload;
        return;
    }

    // https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
    const pasteEvent = Object.assign(new Event('paste', { bubbles: true, cancelable: true }), {
        clipboardData: {
            getData: (type = pasteType) => pastePayload,
        },
    });
    subject[0].dispatchEvent(pasteEvent);

    return subject;
}

@pzhine
Copy link

pzhine commented Dec 5, 2019

What about cut/copy events? Can those be simulated in the same way?

@nickytonline
Copy link
Author

Probably. Give it a try. 😉

@HussamAlHunaiti
Copy link

Works fine for me
Thank you Nick Taylor

@changhuixu
Copy link

not working for me. dispatchEvent() doesn't change input element value.

@iboelsakka
Copy link

I'm also having trouble getting this to work. Can someone who got it to work post an example?

@forresto
Copy link

forresto commented May 15, 2020

For what it's worth, I think that these changes make a more-precise simulation of a paste event using DataTransfer and ClipboardEvent, if your application is expecting JSON data attached:

/**
 * Simulates a paste event.
 * Modified from https://gist.github.com/nickytonline/bcdef8ef00211b0faf7c7c0e7777aaf6
 *
 * @param subject A jQuery context representing a DOM element.
 * @param pasteOptions Set of options for a simulated paste event.
 * @param pasteOptions.pastePayload Simulated data that is on the clipboard.
 * @param pasteOptions.pasteFormat The format of the simulated paste payload. Default value is 'text'.
 *
 * @returns The subject parameter.
 *
 * @example
 * cy.get('body').paste({
 *   pasteType: 'application/json',
 *   pastePayload: {hello: 'yolo'},
 * });
*/
Cypress.Commands.add(
  'paste',
  {prevSubject: true},
  function (subject, pasteOptions) {
    const {pastePayload, pasteType} = pasteOptions;
    const data = pasteType === 'application/json' ? JSON.stringify(pastePayload) : pastePayload;
    // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
    const clipboardData = new DataTransfer();
    clipboardData.setData(pasteType, data);
    // https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
    const pasteEvent = new ClipboardEvent('paste', {
      bubbles: true,
      cancelable: true,
      dataType: pasteType,
      data,
      clipboardData,
    });
    subject[0].dispatchEvent(pasteEvent);

    return subject;
  }
);

@nickytonline
Copy link
Author

Nice! Thanks for sharing @forresto.

@Abelhawk
Copy link

Your code inspired me to simply try .trigger('paste');, which works perfectly on its own. Thanks!

@Sathish787
Copy link

If you want to make it a child command, you can do the following:

Cypress.Commands.add(
    'paste',
    {
        prevSubject: true,
    },
    paste
);

and modify the function to:

/**
 * Simulates a paste event.
 *
 * @param subject A jQuery context representing a DOM element.
 * @param pasteOptions Set of options for a simulated paste event.
 * @param pasteOptions.pastePayload Simulated data that is on the clipboard.
 * @param pasteOptions.simple Determines whether or not to use a simple paste. Use this when there is no paste event bound to the element
 *                              resolved by the selector.
 * @param pasteOptions.pasteFormat The format of the simulated paste payload. Default value is 'text'.
 *
 * @returns The subject parameter.
 *
 * @example
 * cy.get('some-selector').paste({
 *  pastePayload: 'yolo,
 *  simple: false,
*  });
 */
export function paste(subject, { pastePayload, simple = true, pasteType = 'text' }) {
    if (simple) {
        subject[0].value = pastePayload;
        return;
    }

    // https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
    const pasteEvent = Object.assign(new Event('paste', { bubbles: true, cancelable: true }), {
        clipboardData: {
            getData: (type = pasteType) => pastePayload,
        },
    });
    subject[0].dispatchEvent(pasteEvent);

    return subject;
}

Above solution works for Input/textarea html tag but it is not work for div tag even after attribute contenteditable="true". Please let me know any other possible solutions

@nickytonline
Copy link
Author

Thanks for sharing @Sathish787.

@danday74
Copy link

danday74 commented Nov 6, 2020

Simplified version of answer by @Sathish787

This lives in /cypress/support/commands.js

const paste = (subject, text) => {
  subject[0].value = text
  return cy.get(subject).type(' {backspace}') // the use of type to type a space and delete it after changing the value ensures that change detection kicks in
}

Cypress.Commands.add(
  'paste',
  {prevSubject: 'element'},
  paste
)

and then use as follows:

cy.get('textarea.my-class')
  // @ts-ignore
  .paste('hello')

@diondiondion
Copy link

diondiondion commented Feb 11, 2021

@danday74 Unfortunately, this method of triggering a change (by typing a space and deleting it afterwards) makes this method unsuitable for simulating a real paste event, which would only fire a single change event.

I'm trying to write a test for a bug that's only caused when text is pasted into an input (single event), but not when it's typed (one event per keystroke), and the bug doesn't occur when paste is simulated using your method.

Unfortunately I also wasn't lucky with the other methods described in this gist (those that actually dispatch a 'paste' event) – they don't seem to do anything.

@OriMarron
Copy link

For what it's worth, I think that these changes make a more-precise simulation of a paste event using DataTransfer and ClipboardEvent, if your application is expecting JSON data attached:

/**
 * Simulates a paste event.
 * Modified from https://gist.github.com/nickytonline/bcdef8ef00211b0faf7c7c0e7777aaf6
 *
 * @param subject A jQuery context representing a DOM element.
 * @param pasteOptions Set of options for a simulated paste event.
 * @param pasteOptions.pastePayload Simulated data that is on the clipboard.
 * @param pasteOptions.pasteFormat The format of the simulated paste payload. Default value is 'text'.
 *
 * @returns The subject parameter.
 *
 * @example
 * cy.get('body').paste({
 *   pasteType: 'application/json',
 *   pastePayload: {hello: 'yolo'},
 * });
*/
Cypress.Commands.add(
  'paste',
  {prevSubject: true},
  function (subject, pasteOptions) {
    const {pastePayload, pasteType} = pasteOptions;
    const data = pasteType === 'application/json' ? JSON.stringify(pastePayload) : pastePayload;
    // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
    const clipboardData = new DataTransfer();
    clipboardData.setData(pasteType, data);
    // https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
    const pasteEvent = new ClipboardEvent('paste', {
      bubbles: true,
      cancelable: true,
      dataType: pasteType,
      data,
      clipboardData,
    });
    subject[0].dispatchEvent(pasteEvent);

    return subject;
  }
);

Thanks for sharing, worked for me 🤘
This should be considered the right way to simulate a paste event in the browser, both in cypress and puppeteer

@nickytonline
Copy link
Author

Thanks for the share. When I originally did this snippet it fit my use case, so thanks so much for all the folks who have continually provided improvements and suggestions! 😎

@tigerabrodi
Copy link

Your code inspired me to simply try .trigger('paste');, which works perfectly on its own. Thanks!

@Abelhawk so this works!!?? 🔥 😄

@tigerabrodi
Copy link

For what it's worth, I think that these changes make a more-precise simulation of a paste event using DataTransfer and ClipboardEvent, if your application is expecting JSON data attached:

/**
 * Simulates a paste event.
 * Modified from https://gist.github.com/nickytonline/bcdef8ef00211b0faf7c7c0e7777aaf6
 *
 * @param subject A jQuery context representing a DOM element.
 * @param pasteOptions Set of options for a simulated paste event.
 * @param pasteOptions.pastePayload Simulated data that is on the clipboard.
 * @param pasteOptions.pasteFormat The format of the simulated paste payload. Default value is 'text'.
 *
 * @returns The subject parameter.
 *
 * @example
 * cy.get('body').paste({
 *   pasteType: 'application/json',
 *   pastePayload: {hello: 'yolo'},
 * });
*/
Cypress.Commands.add(
  'paste',
  {prevSubject: true},
  function (subject, pasteOptions) {
    const {pastePayload, pasteType} = pasteOptions;
    const data = pasteType === 'application/json' ? JSON.stringify(pastePayload) : pastePayload;
    // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
    const clipboardData = new DataTransfer();
    clipboardData.setData(pasteType, data);
    // https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
    const pasteEvent = new ClipboardEvent('paste', {
      bubbles: true,
      cancelable: true,
      dataType: pasteType,
      data,
      clipboardData,
    });
    subject[0].dispatchEvent(pasteEvent);

    return subject;
  }
);

Thanks for sharing, worked for me metal This should be considered the right way to simulate a paste event in the browser, both in cypress and puppeteer

@OriMarron Imma try trigger('paste'), if it doesn't work, I'm going with this manual command, a'ight

lets hope it works tomorrow lul 🛌

@NaserYasar
Copy link

pastePayload, how this value came ??

@erwinvanlun
Copy link

erwinvanlun commented Oct 12, 2023

Inspired by above, this what I have added:

declaration:
paste(input: string | { pastePayload: string; pasteType: string }): Chainable<JQuery<HTMLElement>>;

definition:

/**
 * usages:
 * cy.paste('text to paste') OR:
 * cy.paste({ pastePayload: 'text to paste', pasteType: 'text/plain' })
 */
Cypress.Commands.add('paste', { prevSubject: true }, (subject, pasteInput) => {
  const isSimpleText = typeof pasteInput === 'string';

  const pastePayload = isSimpleText ? pasteInput : pasteInput.pastePayload;
  const pasteType = isSimpleText ? 'text/plain' : pasteInput.pasteType || 'text/plain';

  const data = pasteType === 'application/json' ? JSON.stringify(pastePayload) : pastePayload;

  const clipboardData = new DataTransfer();
  clipboardData.setData(pasteType, data);

  const pasteEvent = new ClipboardEvent('paste', {
    clipboardData
  });
  subject[0].dispatchEvent(pasteEvent);

  return subject;
});

usage
cy.paste('github is great!')
OR

cy.paste({pastePayload: 'text to paste', pasteType: 'text/plain' })

(other types work as well)

@nickytonline
Copy link
Author

Nice one @erwinvanlun!

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