Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event listeners not working in custom elements #17531

Closed
jrtcppv opened this issue Jul 29, 2021 · 7 comments · Fixed by #19132
Closed

Event listeners not working in custom elements #17531

jrtcppv opened this issue Jul 29, 2021 · 7 comments · Fixed by #19132
Labels
pkg/driver This is due to an issue in the packages/driver directory topic: cy.type ⌨️ topic: shadow dom Issues when testing shadow dom

Comments

@jrtcppv
Copy link

jrtcppv commented Jul 29, 2021

Current behavior

An event emitted from an element inside of the shadow DOM does not trigger an event listener in a parent custom element. The DOM in my example is as follows:

  • page-element (a custom element)
    • text-input (another custom element)
      • input
    • input

The page-element has an event listener on the text-input for an input event which removes the disabled attribute from the input element in the shadow DOM of the page-element. When tested in Cypress, the disabled attribute is never removed, however strangely if I run the test with cypress open and type in the input field myself after the test completes, the disabled attribute is removed instantly. I put in a 10 second wait after visit to make sure all the components are loaded and connected. Result in my example below is as follows:

  Test event listeners
    1) makes sure event listener works


  0 passing (15s)
  1 failing

  1) Test event listeners
       makes sure event listener works:
     AssertionError: Timed out retrying after 4000ms: expected '[ <input>, 1 more... ]' not to have attribute 'disabled'
      at Context.eval (http://localhost:63324/__cypress/tests?p=cypress\integration\spec.js:103:25)

Desired behavior

The test should cause the disabled attribute in the input element with type=submit to be removed.

Test code to reproduce

Clone this fork:

https://github.com/jrtcppv/cypress-test-tiny

Run the following:

npm install cypress
./node_modules/bin/cypress run

Cypress Version

7.7.0, 8.0.0, 8.1.0

Other

Really want Cypress to work for us, but our application has a ton of nested custom elements like this... hopefully this example will give us some insight into what the problem is. I'm also a Cypress noob so possible I am just doing something wrong.

@sainthkh
Copy link
Contributor

Sorry for the late reply.

It's not working because customElements use shadow DOM.

image

To find elements inside shadow DOM, you need to use cy.shadow and cy.find.

So, when we write code like below, it would work.

it('makes sure event listener works', () => {
  cy.visit('fixtures/a.html')

  cy
  .get('page-element').shadow()
  .find('text-input').shadow()
  .find('input').type('asdf')
  .should('not.have.attr', 'disabled')
})

@sainthkh sainthkh added the stage: awaiting response Potential fix was proposed; awaiting response label Aug 10, 2021
@jrtcppv
Copy link
Author

jrtcppv commented Aug 10, 2021

In my fork's config I included the parameter "includeShadowDom": true which has the documentation:

Whether to traverse shadow DOM boundaries and include elements within the shadow DOM in the results of query commands (e.g. cy.get())

The code for finding the elements does seem to work, since if you clone the fork and run it it does type the text into the input field inside the shadow DOM, without requiring the shadow() call you have above. If it couldn't find the elements wouldn't it raise an exception?

@sainthkh
Copy link
Contributor

I'm sorry that I misunderstood your issue.

It's happening because there are 2 <input>s in the code: text box and submit button.

When cy.get is used, it finds all elements that meet the condition and tries to test the condition on all of them.

In your case, text box is not disabled but the submit button is disabled, so the test failed.

Fixing the code like below would solve the problem:

describe('Test event listeners', () => {
  it('makes sure event listener works', () => {
    cy.visit('./index.html').wait(10000).then(() => {
      cy.get('text-input').find('input').type('asdf').then(() => {
          cy.get('input[type=text]').should('not.have.attr', 'disabled')
        });
      });
  });
});

TIP: when multiple elements are returned, you can see it by the message which shows the returned elements with [].

Screenshot from 2021-08-11 10-22-32

@jrtcppv
Copy link
Author

jrtcppv commented Aug 11, 2021

I went ahead and added a fix which does filter down to the one element of interest (the submit type input):
jrtcppv/cypress-test-tiny@8cfc150

Unfortunately this test still does not pass. As I mentioned before, if you run the test with cypress open you will see that the "Submit" button is never enabled even though the text is being populated into the text input. If, after the test, you type into the field manually via the keyboard, the "Submit" button is enabled as it should be. The behavior of manual typing and the type action done by Cypress is different.

@sainthkh
Copy link
Contributor

Sorry for another misunderstanding.

Cypress emulates the type events by sending them programatically. And I learned that the input message isn't sent to the elements under shadow root.

Confirmed that the bug happens.

it('shadow DOM', () => {
  cy.visit('fixtures/a.html')

  cy
  .get('page-element').shadow()
  .find('text-input').shadow()
  .find('input').type('asdf')

  cy
  .get('page-element').shadow()
  .find('input[type=submit]')
  .should('not.have.attr', 'disabled')
})

it('non-shadow DOM', () => {
  cy.visit('fixtures/aa.html')

  cy.get('#s').should('have.attr', 'disabled')
  cy.get('#a').type('asdf')
  cy.get('#s').should('not.have.attr', 'disabled')
})
<!-- a.html -->
<html>
  <head>
    <script>
      class TextInput extends HTMLElement {
        constructor() {
          super();
          this._shadow = this.attachShadow({mode: "open"});

          this.label = document.createElement("label");
          this.label.setAttribute("class", "d-flex flex-justify-between flex-items-center py-1");
          this._shadow.appendChild(this.label);

          this._name = document.createTextNode("");
          this.label.appendChild(this._name);

          this._input = document.createElement("input");
          this._input.setAttribute("type", "text");
          this.label.appendChild(this._input);
        }

        static get observedAttributes() {
          return ["name"];
        }

        attributeChangedCallback(name, oldValue, newValue) {
          switch (name) {
            case "name":
              this._name.nodeValue = newValue;
              break;
          }
        }
      }

      customElements.define("text-input", TextInput);

      class PageElement extends HTMLElement {
        constructor() {
          super();
          this._shadow = this.attachShadow({mode: "open"});

          const input = document.createElement("text-input");
          input.setAttribute("name", "Label");
          this._shadow.appendChild(input);

          const button = document.createElement("input");
          button.setAttribute("type", "submit");
          button.setAttribute("disabled", "");
          this._shadow.appendChild(button);

          input.addEventListener("input", () => {
            console.log('hi?')
            button.removeAttribute("disabled");
          });
        }
      }

      customElements.define("page-element", PageElement);
    </script>
  </head>
  <body>
    <page-element></page-element>
  </body>
</html>
<!-- aa.html -->
<html>
<head>
</head>
<body>
  <input type="text" id="a">
  <input type="submit" id="s" disabled="disabled">
  <script>
    document.getElementById("a").addEventListener("input", function(e) {
      document.getElementById("s").disabled = ""
    })
  </script>
</body> 
</html>

@sainthkh sainthkh added pkg/driver This is due to an issue in the packages/driver directory stage: ready for work The issue is reproducible and in scope topic: cy.type ⌨️ and removed stage: awaiting response Potential fix was proposed; awaiting response labels Aug 16, 2021
@sainthkh sainthkh added the topic: shadow dom Issues when testing shadow dom label Oct 18, 2021
@cypress-bot cypress-bot bot added stage: needs review The PR code is done & tested, needs review stage: work in progress and removed stage: ready for work The issue is reproducible and in scope stage: work in progress labels Nov 29, 2021
@cypress-bot
Copy link
Contributor

cypress-bot bot commented Dec 2, 2021

The code for this is done in cypress-io/cypress#19132, but has yet to be released.
We'll update this issue and reference the changelog when it's released.

@cypress-bot cypress-bot bot removed the stage: needs review The PR code is done & tested, needs review label Dec 2, 2021
@cypress-bot
Copy link
Contributor

cypress-bot bot commented Dec 4, 2021

Released in 9.1.1.

This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v9.1.1, please open a new issue.

@cypress-bot cypress-bot bot locked as resolved and limited conversation to collaborators Dec 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
pkg/driver This is due to an issue in the packages/driver directory topic: cy.type ⌨️ topic: shadow dom Issues when testing shadow dom
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants