-
-
Save nickytonline/bcdef8ef00211b0faf7c7c0e7777aaf6 to your computer and use it in GitHub Desktop.
/** | |
* 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); | |
}); | |
} |
Your code inspired me to simply try .trigger('paste');
, which works perfectly on its own. Thanks!
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
Thanks for sharing @Sathish787.
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')
@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.
For what it's worth, I think that these changes make a more-precise simulation of a paste event using
DataTransfer
andClipboardEvent
, 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
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! 😎
Your code inspired me to simply try
.trigger('paste');
, which works perfectly on its own. Thanks!
@Abelhawk so this works!!?? 🔥 😄
For what it's worth, I think that these changes make a more-precise simulation of a paste event using
DataTransfer
andClipboardEvent
, 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 🛌
pastePayload, how this value came ??
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)
Nice one @erwinvanlun!
The cypress command did not work for me.
in Cypress version: 13.8.1
I just ended up writing this simple line for my purpose:
const text = 'text I want to paste in the input';
cy.get('input').eq(0).trigger('paste', {
clipboardData: { getData: () => text }
});
It is not very "modular" or reusable, but it worked perfectly for my test case.
I haven’t touched or used this command in years so glad you got something working for your tests @IrvingArmenta!
Hi @IrvingArmenta,
Could you share more about your configuration? I'm using similar setup to yours but it doesn't work.
cy.mount(html`<input />`)
const text = 'text I want to paste in the input';
cy.get('input')
.eq(0)
.click()
.trigger('paste', {
clipboardData: { getData: () => text }
});
cy.get("input")
.should("have.value", text)
@nnaydenow
If you only have one <input />
then no need for eq()
, you should just select that one input, also my inputs have a custom onPaste
also not sure why you need click()
listener:
// function
const handleOnPaste = (
e: React.ClipboardEvent<HTMLInputElement>,
index: number
) => {
e.preventDefault();
const pastedData = e.clipboardData.getData('text');
// do something with the pastedData, like set as a value to the input
}
// jsx
<input
type="text"
onPaste={(e) => handleOnPaste(e, index)}
/>
But in my case is an array of inputs.
Not working for me too. It pastes "internalCopy" as a value.
const text = 'text I want to paste in the input'; cy.get('input').eq(0).trigger('paste', { clipboardData: { getData: () => text } });
Do you have working example ?
I am having Cypress v 13.12.0 and want to simulate the paste event.
Scenarios are like if the user press Ctrl + V after copying any html page content or excel or from image or any text. The pasted text should gets displayed properly in the input or textarea or inside a div.
Any suggestions for that?
tried the solution like
const text = "text I want to paste in the input";
cy.get("selector_id").trigger("paste", {
clipboardData: { getData: () => text }
});
OR
cy.get<Subject>(elementSelector).then((subject: any) => {
cy.window().then((win) => {
const element = subject[0];
const dataTransfer = new win.DataTransfer();
dataTransfer.setData("text/plain", text);
// Create a custom event
const event = new win.Event("paste", { bubbles: true }) as any;
if (event) {
event.clipboardData = dataTransfer;
}
// Dispatch the event
// const element = win.document.querySelector(elementSelector);
if (element) {
element.focus();
element.dispatchEvent(event);
}
});
Cypress version: 13.13.2
What worked for me is more of a workaround but still uses the copied clipboard from cypress itself.
Get the copied value from the cypress window, alias it and then type it or perform some assertions on it.
cy.window().then((win) => {
return cy.wrap(win.navigator.clipboard.readText()).then((clipboardText) => {
const expectedText = 'Your expected text, if known previously for assertion';
expect(clipboardText).to.equal(expectedText);
// Save the clipboard to "paste" it later
cy.wrap(clipboardText).as('copiedText');
});
});
cy.get('@copiedText').then((copiedText) => {
// Use copiedText as needed, use .type() to simulate "paste" if you work with strings
cy.log(copiedText);
});
Nice! Thanks for sharing @forresto.