cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
47.08k stars 3.19k forks source link

Select / highlight text #2839

Open ithacasnowman opened 5 years ago

ithacasnowman commented 5 years ago

Current behavior:

I'm having a hard time creating a text selection. Things I've tried:

Desired behavior:

Test highlighting text.

Steps to reproduce:

I can provide sample code for the approaches I tried, but I'll hold off for now in case there's a completely different known way of doing this.

Versions

Cypress 3.1.1, Chrome 70.0.3538.102

lilaconlee commented 5 years ago

What are you trying to test with highlighting text? It'll be easier to determine a fix if we understand what you're trying to do.

ithacasnowman commented 5 years ago

My application keeps a record of highlighted text. I'm trying to test that that works correctly.

jennifer-shehane commented 5 years ago

We've gotten this question several times. We don't have a specific API implemented to handle highlighting text. We do have a generic cy.trigger() where you can trigger any event, so it is theoretically possible to test many of what you'd need, but would require intimate knowledge of how your application works and what events it triggers.

I will update this issue to reflect request for this feature. This issue should now track:

jennifer-shehane commented 5 years ago

This feature also requires work that's already in progress for our Native Events issue: https://github.com/cypress-io/cypress/issues/311

jennifer-shehane commented 5 years ago

Related comment from @tnrich on wanting ability to highlight text with cy.type('{meta}a'):

I would specifically like a way to trigger the exact same thing as a user-invoked "meta+a" on any element. That might happen to select text in some instances, or select everything in the page if not inside an input/textarea, or it might do something totally different if there are hotkeys listening to that specific keypress.

@Bkucera Would this situation be covered in the Native Events work currently?

kuceb commented 5 years ago

This would have to be a new command entirely, and isn't planned for the native events release. However we do plan to support text selection in the future

In the meantime, you can try this:

cy.get('p.mytext')
.trigger('mousedown')
.then(($el) => {
  const el = $el[0]
  const document = el.ownerDocument
  const range = document.createRange()
  range.selectNodeContents(el)
  document.getSelection().removeAllRanges(range)
  document.getSelection().addRange(range)
})
.trigger('mouseup')
cy.document().trigger('selectionchange')
vctormb commented 5 years ago

@Bkucera why this line cy.document().trigger('selectionchange') is needed? Btw, great solution!

erquhart commented 5 years ago

Here are a couple of commands that are working for my team, using Cypress to test a Slate based editor: https://gist.github.com/erquhart/37bf2d938ab594058e0572ed17d3837a

Includes the ability to naively select based on text matching:

cy.get('p').setSelection('foo')

Or set both the start and end points of the selection:

cy.get('p').setSelection('foo', 'baz')

Also setCursorBefore/After with the same text matching interface, plus some other lower level commands.

kuceb commented 5 years ago

@vctormb it's what the browser fires if you were to select text manually, but it won't make a difference if your app code doesn't listen for it

samtsai commented 4 years ago

FYI, I couldn't get the gist above working with input or textarea's and it looks like its due to lack of support: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects (it also wasn't working in Chrome 80 for me), so I created a fork of the gist with a different path for those elements: https://gist.github.com/samtsai/5cf901ba61fd8d44c8dd7eaa728cac49

VicLim4 commented 4 years ago

@erquhart Thanks you so much !!! The highlighting works !!!

I feel maybe this can be made into an official way of highlighting text instead of just being labelled as a workaround !!! hehe

Anahkiasen commented 4 years ago

Yeah I used this Gist successfully on a selection-heavy project test suite and encountered no real issue, I'd be fine if this was the code used for official support despite its potential limitations, it's still better than no support

erquhart commented 4 years ago

Glad it worked for you! It's super naive, Eg., duplicates of the targeted string can trip it up easily. We've avoided this by not using duplicate strings in our tests (they're generally short strings anyway). Probably not a viable candidate for a first class solution, but a good stopgap.

For anyone running into issues w/ the gist, our implementation has evolved a bit and may work better. You'll just have to dig a bit in the source file to find the relevant code. Here's the source as of today: https://github.com/netlify/netlify-cms/blob/a4b7481a99f58b9abe85ab5712d27593cde20096/cypress/support/commands.js#L180

armaaar commented 4 years ago

@erquhart Thanks a lot! You saved us hours of work.

I really recommend you to create a separate package with all selection related commands so people can just install it via npm in the future and use it directly. I'd be the first one to star it!

dzschille commented 4 years ago

For future readers: if @erquhart solution is not working for you, try to trigger a 'selectstart' after the triggered 'mousedown' in the 'selection' command. That seems to fake better a real mouse interaction. In my case i had a popup that is triggered when text was selected.

manuelmeister commented 3 years ago

@jennifer-shehane how can we move this issue forward?

NZhlebinkov commented 3 years ago

If the previous workarounds didn't work for some people (like me), you could try cy.realPress(["Shift", "ArrowLeft"]); from https://github.com/dmtrKovalenko/cypress-real-events

Wolsten commented 3 years ago

@erquhart What a great solution and many thanks for sharing. I have similar issues starting out testing my own rich text editor app and this saved me a lot of time.

Kname80 commented 2 years ago

Any other suggestions for handling selecting text inside iframe ?

thednp commented 2 years ago

@erquhart thanks for your solution!! Here's a simple fork to select anything within a Node. No need to change Cypress.command or anything, just a simple fixed utility function.

Code

/**
 * Returns an `Array` with all `#text` nodes in a Node. 
 * @param {Node} target root node
 * @returns {Node[]} the requested nodes
 */
function getTextNodes(target) {
  if (target.nodeType === 3) {
    return [target];
  }

  let nodes = [];
  const doc = target.ownerDocument;
  const win = doc.defaultView;
  const walk = doc.createTreeWalker(target, win.NodeFilter.SHOW_TEXT, null);
  let node;

  while (node = walk.nextNode()) {
    if (node.nodeType === 3) {
      nodes = [...nodes, node];
    }
  }
  return nodes;
}

/**
 * Select all the text of the first textNode of a given element.
 * @param {Node} target target element
 */
export default function selectText(target) {
  const textNodes = getTextNodes(target);
  const [startNode, endNode] = [...textNodes.slice(0,1), ...textNodes.slice(-1)];
  console.log(startNode,endNode)
  const doc = target.ownerDocument;
  const win = doc.defaultView;

  const range = new win.Range();
  range.setStart(startNode , 0);
  range.setEnd(endNode, endNode.textContent.length);
  win.getSelection().removeAllRanges();
  win.getSelection().addRange(range);
}

How to

it('selects all text in a Node', 90 => {
  cy.get('SELECTOR').then((target) => {
    selectText($target[0]);
    // now do something with all that selected text
    expect(target[0].ownerDocument.getSelection().toString().length).to.be.above(0);
  })
})

Last but not least: have fun!

blabute commented 1 year ago

If the previous workarounds didn't work for some people (like me), you could try cy.realPress(["Shift", "ArrowLeft"]); from https://github.com/dmtrKovalenko/cypress-real-events

This worked like a charm! Thanks @NZhlebinkov

rahulworks-git commented 1 year ago

I want to select and highlight span all the elements present under the

element. After all the element under the mentioned tag are selected, we get a word "Santa" that is highlighted. None of the workarounds mentioned above seem to work. Anyone is aware how can this be done?

<div class="section-text">
    <span sid="0" ost="0">S</span>
    <span sid="0" ost="1">a</span>
    <span sid="0" ost="2">n</span>
    <span sid="0" ost="3">t</span>
    <span sid="0" ost="4">a</span>
alex-reinfoce commented 10 months ago
Cypress.Commands.add('selectText', function(text) {
  cy.contains(text).then(($el) => {
    const el = $el[0];
    const document = el.ownerDocument;

    const range = document.createRange();
    range.selectNodeContents(el);

    const fullText = el.textContent || "";
    const startIndex = fullText.indexOf(text);
    const endIndex = startIndex + text.length;

    if (startIndex !== -1 && endIndex !== -1) {
      range.setStart(el.firstChild, startIndex);
      range.setEnd(el.firstChild, endIndex);

      const selection = document.getSelection();
      selection.removeAllRanges();
      selection.addRange(range);

      $el.trigger('mouseup');
      cy.document().trigger('selectionchange');
    } else {
      throw new Error(`The text "${text}" was not found in the element`);
    }
  });
});