14

I am trying to write a test (using jest-puppeteer) for an input in my React application that handles autocomplete or copy/pasted strings in a unique way.

I was hoping by using Puppeteer, I could paste text into the input and then validate that the page is updated correctly. Unfortunately, I can't find any working example of how to do this.

I've tried using page.keyboard to simulate CMD+C & CMD+V but it does not appear that these sorts of commands work in Puppeteer.

I've also tried using a library such as clipboardy to write and read to the OS clipboard. While clipboardy does work for write (copy), it seems read (paste) does not affect the page run by Puppeteer.

I have successfully copied the text using a variety of methods but have no way to paste into the input. I've validated this assumption by adding event listeners for "copy" and "paste" to the document. The "copy" events fire, but no method has resulted in the "paste" event firing.

Here are a few approaches I have tried:

await clipboardy.write('1234'); // writes "1234" to clipboard
await page.focus("input");
await clipboardy.read(); // Supposedly pastes from clipboard
// assert input has updated
await clipboardy.write('1234');
await page.focus("input");
await page.keyboard.down('Meta');
await page.keyboard.press('KeyV');
await page.keyboard.up('Meta');
// assert input has updated
await page.evaluate(() => {
  const input = document.createElement('input');
  document.body.appendChild(input);
  input.value = '1234';
  input.focus();
  input.select();
  document.execCommand('copy');
  document.body.removeChild(input);
});
wait page.focus("input");
await page.keyboard.down('Meta');
await page.keyboard.press('KeyV');
await page.keyboard.up('Meta');

I think the only missing piece here is pasting the text; but how do you paste text using Puppeteer?

sandgraham
  • 141
  • 1
  • 1
  • 5

2 Answers2

7

This works for me with clipboardy, but not when I launch it in headless :

await clipboardy.write('foo')

const input= await puppeteerPage.$(inputSelector)
await input.focus()

await puppeteerPage.keyboard.down('Control')
await puppeteerPage.keyboard.press('V')
await puppeteerPage.keyboard.up('Control')

If you make it works in headless tell me.

I tried it the clipBoard API too but I couldn t make it compile:

const browser = await getBrowser()
const context = browser.defaultBrowserContext();
// set clipBoard API permissions
context.clearPermissionOverrides()
context.overridePermissions(config.APPLICATION_URL, ['clipboard-write'])
puppeteerPage = await browser.newPage()

await puppeteerPage.evaluate((textToCopy) =>{
  navigator.clipboard.writeText(textToCopy)
}, 'bar')

const input= await puppeteerPage.$(inputSelector)
await input.focus()

await puppeteerPage.evaluate(() =>{
  navigator.clipboard.readText()
})
3

I came up with a funny workaround how to paste a long text into React component in a way that the change would be registered by the component and it would not take insanely long time to type as it normally does with type command:

For text copying I use approach from Puppeteer docs (assume I want to select text from first 2 paragraphs on a page for example). I assume you already know how to set the permissions for clipboard reading and writing (for example one of the answers above shows how to do it).

const fromJSHandle = await page.evaluateHandle(() =>Array.from(document.querySelectorAll('p'))[0])
const toJSHandle   = await page.evaluateHandle(() =>Array.from(document.querySelectorAll('p'))[1])

// from puppeteer docs
await page.evaluate((from, to) => {
     const selection = from.getRootNode().getSelection();
     const range = document.createRange();
     range.setStartBefore(from);
     range.setEndAfter(to);
     selection.removeAllRanges();
     selection.addRange(range);
}, fromJSHandle, toJSHandle);

await page.bringToFront();
await page.evaluate(() => {
     document.execCommand('copy') // Copy the selected content to the clipboard
     return navigator.clipboard.readText() // Obtain the content of the clipboard as a string
})

This approach does not work for pasting (on Mac at least): document.execCommand('paste')

So for pasting I use this:

await page.$eval('#myInput', (el, value) =>{ el.value = value }, myLongText)
await page.type(`#myInput`,' ') // this assumes your app trims the input value in the end so the whitespace doesn't bother you

Without the last typing step (the white space) React does not register change/input event. So after submitting the form (of which the input is part of for example) the input value would still be "". This is where typing the whitespace comes in - it triggers the change event and we can submit the form.

It seems that one needs to develop quite a bit of ingenuity with Puppeteer to figure out how to work around all the limitations and maintaining some level of developer comfort at the same time.

okram
  • 810
  • 3
  • 11
  • 17