0

Im using Turbo an a page w a simple select.

= turbo_frame_tag "widget-index", 'data-controller': 'turbo-frame-select' do
  .widget-card-header
    select 
     data-test-id="Widget-year-select" 
     data-action="turbo-frame-select#onChange"
     type="select"
     name="filter"
       - @widget_years.each do |year|
        option[selected=("selected" if (year == @search_year)) value="#{widget_index_path(company_id: @company.abbreviation, year: year)}"] #{year}

  #widgets_results data-test-id="widgets-results"
      = render WidgetCardComponent.with_collection(@widgets)

The AC is:

When the user selects a year, the results for the year should appear.

I've got a super simple stimulus controller I added to try to make sure caching is not an issue:

// turbo_frame_select_controller.js

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  onChange (event) {
    const frame = this.element;
    frame.src = event.target.value;
  )}
}

Ive written an overwrite for select to work with Turbo events in command.js.

Cypress.Commands.overwrite("select", (originalFn, subject, ...args) => {
  // Check if turbo is active for this select event
  const turboFrame = subject[0].closest('[data-controller="turbo-frame-select"]');
  const isSelect = subject[0].tagName === 'SELECT';

  if (isSelect && turboFrame) {
    // Wait for turbo to finish loading the page before proceeding with the next Cypress instructions.
    // First, get the document
    cy.document({ log: false }).then($document => {
      console.log('mutating observer last child');
      Cypress.log({
        $el: subject,
        name: 'select',
        displayName: 'select',
        message: 'select and wait for page to load',
        consoleProps: () => ({ subject: subject })
      });

      // Make Cypress wait for this promise which waits for the turbFrame to cause a mutation
      return new Cypress.Promise(resolve => {
        // Once we receive the mutuation,
        const onTurboFrameMutate = function(mutationList, observer) {
          for(const mutation of mutationList) {
            const isChild = mutation.type === 'childList';
            const lastChild = mutationList.length -1 !== mutationList.indexOf(mutation)

            if (isChild  && lastChild) {
              console.log('A child node has been added or removed.');
              turboFrameObserver.disconnect();

              // signal to Cypress that we're done
              resolve()
            }
          }
        }

        // createa new observer
        const turboFrameObserver = new MutationObserver(onTurboFrameMutate);

        // Options for the observer (which mutations to observe)
        const config = { attributes: true, childList: true, subtree: true };

        // Add our logic as observer
        turboFrameObserver.observe(turboFrame, config);

        // Finally, we are ready to perform the actual select operation
        originalFn(subject, ...args);
      })
    });
  } else {
    // Not a normal select on an <select> tag, turbo will not interfere here
    return originalFn(subject, ...args);
  }
});

I am calling the select method in this context:

cy.visit(widgetIndexPath, { timeout: 10000 });

// Make sure header is loaded
cy.contains("h1", "Cash management");

// Current year selected
cy.get("@aYearSelector").find("option:selected").contains(currentYear);

// Select last year
   cy.get("@aYearSelector")
     .find("option")
     .its('length')
     .then((length) => {
       cy.get("@aYearSelector")
         .select(length - 1, {force: true})
         .then(()=> {
           // Triggers after year selector changes
           cy.wait('@previousYearRequest').then(({response}) => {
             expect(response.statusCode).to.eq(200);

             cy.get("[data-test-id='widget-year-select']").as('newYearSelector');
             cy.get("[data-test-id='widgets-results']").as('newResults');

             cy.get("@newYearSelector").find("option:selected").contains(previousYear);

             // Note: We have 13 months of data in the data layer from the factories.
             // The months count backwards from the current month which will be spread out
             // over 2 years.
             cy.get('@newResults')

The test passes 100/100 times locally, but is only sucesful 1/4 - 1/3 runs on Cypress Dashboard...

Giving the following error:


cy.then() timed out after waiting 6000ms.

Your callback function returned a promise that never resolved.

The callback function was:

$document => {

  console.log('mutating observer last child');

Ive tried just about everyhting I can think of... This seems like it should work

Does anyone have any ideas why this is timing out?

Thomas Walpole
  • 48,548
  • 5
  • 64
  • 78
Niles M
  • 1
  • 1
  • I also tried a version of the select above w an event listener: ``` return new Cypress.Promise(resolve => { // Once we receive the event, const onTurboLoad = () => { // clean up $document.removeEventListener('turbo:frame-load', onTurboLoad); // signal to Cypress that we're done resolve(); } // Add our logic as event listener $document.addEventListener('turbo:frame-load', onTurboLoad); // Finally, we are ready to perform the actual select operation originalFn(subject, ...args); }) ``` – Niles M Jul 19 '22 at 01:45
  • Ive also tried writing the test in RSPEC using capybara, and I get the same issue there as well. – Niles M Jul 19 '22 at 01:49
  • Potentially your mutation observer is firing too early or too late (so `resolve()` never called). If the purpose of Turbo code is to dynamically add options (years), I would add `.find("option").should('have.length', n)` to ensure you are selecting after last year is added. That effectively does the same as the mutation observer but with less moving parts. – Fody Jul 19 '22 at 02:30
  • Thanks for responding! Sadly, that does not work either, I got to the event listener and mutation observer after trying that approach. The expectations (that a new years worth of results appear on the page) after that don't have the updated information. The expectations in that case fire before the page has been re-rendered. In line timeouts and waits don't seem to work either. – Niles M Jul 19 '22 at 18:26
  • Hard to say what's going on, but mutation observer should be removed and Cypress assertions with timeout used instead. Maybe the timeout just needs to be increased? If you have a public-facing web page I can check further. – Fody Jul 19 '22 at 21:42
  • @Fody- I have done that, we get failing expectations about the content of that page locally and remotely. The page updates too quickly or too slowly. Hence the event listener or mutation observer, which help the test to work 100% of the locally. – Niles M Jul 20 '22 at 18:44

0 Answers0