Handling Copy and Paste in Cypress

Filip Hric
author
Filip Hric
abstract illustration of copy and paste
    Posted in

Clipboard or pasting text is not available out of the box in Cypress. But as Gleb says: "It's just JavaScript". And he's right. Cypress is pure JavaScript, so you can do anything that JS allows you to do.

Pasting text

Let's look at the first example. I have a very simple app here that has two input fields. The first input field will check the validity of the input. If inserted text is not an email, it will throw an error. By the way, you can check out these examples in my repo on GitHub.

Copy paste app preview

In the simplest form we can paste a text into our email field with the following code:

it('pastes text to textarea', () => {
const textToPaste = 'this is not a valid email'
cy.visit('index.html')
cy.get("[type='email']").invoke('val', textToPaste)
})

Instead of typing our text, we are using the .invoke() command to change our input's value. This way, we are immediately getting into the final state of what our users would experience. You might notice, though, that something is missing from our app:

Missing validation

We do not see our validation message. This is because our validation was not triggered. The reason for that becomes more apparent when we look into the event listeners panel. There are a couple of event listeners bound to this input field.

Event listeners

These event listeners handle our validation. Our input field is not validated until a user clicks away from it or presses the TAB button. This will trigger a blur event and make our input field invalid.

These event listeners are very commonly used with forms. Different frameworks use different approaches to input fields. It is very useful to look into what kind of event listeners are bound into your app elements. It can help you better understand what happens when the user interacts with your input field. In our case, we need to fire the blur event to our input field:

it('pastes text to textarea', () => {
const textToPaste = 'this is not a valid email'
cy.visit('index.html')
cy.get("[type='email']").invoke('val', textToPaste).trigger('blur')
})

We can either use .trigger() command or use .focus() and .blur() commands from Cypress to trigger our events. I like to prefer .trigger() just because it gives you a wider variety of events you can trigger. E.g., in an app that I test for work, there's a compositionend event that I need to trigger from time to time.

To simulate pasting a text, you often need to mimic a sequence of events that happen over an element you interact with. Fun tip for you: To see your events happen inside your app, open your Chrome console and type in monitorEvents($0). The $0 stands for the active element, so make sure you select it via the elements panel first.

Copying to clipboard

Let's now look into the other side of the story. For our next test, we want to copy a text from our textarea. To make sure this action was successful, we want to test the contents of our clipboard. To do that, we can install a tool called clipboardy and run it as a plugin in Cypress. To setup this plugin, we define a new task in our plugins/index.ts file:

const clipboardy = require('clipboardy')
module.exports = (
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions,
) => {
on('task', {
// Clipboard test plugin
getClipboard: () => {
const clipboard: string = clipboardy.readSync()
return clipboard
},
})
}

Now that we have defined our task, we can use it in our test:

it.only('copies text from textarea', () => {
const textToCopy = 'Wubba lubba dub dub'
cy.visit('index.html')
cy.get('#copyText').invoke('val', textToCopy)
cy.get('button').click()
cy.task('getClipboard').should('eq', textToCopy)
})

Our task will return the content of our clipboard. This enables us to write an assertion, just as we would normally do with any other element.

However, this test will work only if you run your test in Electron. Because our .click() command is fired with JavaScript, it has an isTrusted property set to false. Whenever a click event is dispatched or created by a script, it has this property set to it. Cypress uses JavaScript to fire its events, so they differ from real user interaction.

There is a way of firing real click events in Chrome, using cypress-real-events plugin. This plugin adds some real events to your app (it has a hover command, too!). Firing a real event here will copy the desired text to the clipboard.

it('copies text from textarea', () => {
const textToCopy = 'Wubba lubba dub dub'
cy.visit('index.html')
cy.get('#copyText').invoke('val', textToCopy)
cy.get('#button').realClick()
cy.task('getClipboard').should('eq', textToCopy)
})