-1

I'm testing an ASP.NET webapp (to the code of which I do not have access) with multiple sequential pages with the following relevant parts in a Cypress script:

describe("Calculation", function() {

  // ...

  context("Pages", function() {

    // ... 

    // Page 1
    it("With all inputs", function() {
      cy.visit(url)
      // ... setting input fields ...
      cy.get(next).click()
      cy.get('.stg-error-mark').should('have.length', 0)
    })
  
    // Page 2
    it("No selection", function() {
      cy.get(next).click()
      cy.get('.stg-error-mark').should('have.length', 2)
    })
  })
})

Page 2 it(...)'s Command Log contains:

...
- CLICK
(FORM SUB)   --submitting form--   ← Page 2 looks fine in Cypress' App Preview.
(PAGE LOAD)   --page loaded--   ← The webapp shows the error below in the App Preview.
...

 

Error

The input transaction was automatically cancelled because it was not continued for too long. Please start again.

It seems that the webapp lacks some internal state if Page 1 hasn't been visit()ed in the it(...) of Page 2.

I know that putting all relevant page code parts into (describe-global) functions and calling them increasingly and such always starting at Page 1 in each test works. But, since this app has 33 different functional paths with 5 to 6 sequential pages (depending on user input on a previous page) I'd like to avoid this code-bloating, confusing, error-prone and time-consuming function chaining–and globals are of the devil.

UPDATE

From theory to practice, this is the functional paths chart:

  +------ Seite 1 -------+----- Seite 2 -----+------ Seite 3 ------+------ Seite 4 ------+- Seite 5.1 -+- Seite 5.2 -+
  |    Wohneinheiten     |   Fördermodell    |   1. Wohneinheit    |   2. Wohneinheit    | Berechnung  | Berechnung  |
  |         2/4          |        8/2        |        12/10        |         6/5         |   Input     |   Output    |
  |                      |                   |                     |                     |             |             |
  |    Wohneinheiten:    |   ▼ Modell        |  Heizwärmebedarf:   |                     |             |             |
  |  +- <no> --->(X)     |  +-▼ Passiv ------>-- HWB: ----------------------------------->------------->             |
  |  |                   |  |                |   +- !]0..10]       |                     |             |             |
  |  +- WE1: --->(X)     |  |                |      +--->(X)       |                     |             |             |
  |  |                   |  |                |                     |                     |             |             |
  |  +- WE2: --->(X)     |  +-▼ Punkte ------>-- HWB: ----------------------------------->------------->             |
  |  |                   |  |                |   +- !]10..49]      |                     |             |             |
  |  +- WE1:             |  |                |      +--->(X)       |                     |             |             |
  |  |  +- AV-V: -------->--+                |                     |                     |             |             |
  |  |                   |  |                |  +-▼ PV ---------------------------------->------------->             |
  >--+                   |  |                |  +-▼ Solar ------------------------------->------------->             |
  |  |                   |  +-▼ Niedrig ----->--+-x WRL --------------------------------->------------->             |
  |  |                   |  |                |  +- <no> --->(X)    |                     |             |             |
  |  +- WE2:             |  |                |                     |                     |             |             |
  |  |  +- AV-V: --->(X) |  +-▼ Niedrigst --->--▼ Heizsystem ---------------------------->------------->             |
  |  |                   |  |                |    +-▼ Elektrisch   |                     |             |             |
  |  |                   |  +-▼ <no> --->(X) |        +-▼ PV2kWp   |                     |             |             |
  |  |                   |                   |        |   +--->(X) |                     |             |             |
  |  |                   |                   |        +-o WRL      |                     |             |             |
  |  |                   |                   |            +--->(X) |                     |             |             |
  |  +- WE1:             |                   |                     |                     |             |             |
  |     +- WE2:          |   ▼ Modell        |  Heizwärmebedarf:   |  Heizwärmebedarf:   |             |             |
  |        +- AV-V: ----->--+-▼ Passiv ------>-- HWB: ------------->-- HWB: ------------->------------->             |
  |                      |  |                |   +- !]0..10]       |   +- !]0..10]       |             |             |
  +----------------------+  |                |      +--->(X)       |      +--->(X)       |             |             |
                         |  |                |                     |                     |             |             |
                         |  +-▼ Punkte ------>-- HWB: ------------->-- HWB: ------------->------------->             |
                         |  |                |   +- !]10..49]      |   +- !]10..49]      |             |             |
                         |  |                |      +--->(X)       |      +--->(X)       |             |             |
                         |  |                |                     |                     |             |             |
                         |  +-▼ Niedrig ----->--+-▼ PV  ----------->--+-▼ PV  ----------->------------->             |
                         |  |                |  +-▼ Solar --------->  +-▼ Solar --------->------------->             |
                         |  |                |  +-x WRL ----------->  +-x WRL ----------->------------->             |
                         |  |                |  +- <no> --->(X)    |  +- <no> --->(X)    |             |             |
                         |  |                |                     |                     |             |             |
                         |  +-▼ Niedrigst --->--▼ Heizsystem ------>--▼ Heizsystem ------>------------->             |
                         |  |                |    +-▼ Elektrisch   |    +-▼ Elektrisch   |             |             |
                         |  +-▼ <no> --->(X) |        +-▼ PV2kWp   |        +-▼ PV2kWp   |             |             |
                         |                   |        |   +--->(X) |        |   +--->(X) |             |             |
                         |                   |        +-x WRL      |        +-x WRL      |             |             |
                         |                   |            +--->(X) |            +--->(X) |             |             |
                         +-------------------+---------------------+---------------------+-------------+-------------+
Legend:
  : ...... Textbox
  .. ..... Interval
  ▼ ...... Dropdown-Listbox or selection
  x,o .... Checkbox (checked, unchecked)
  <no> ... No input or selection
  ! ...... Not
  (X) .... Error
  > ...... Proceed to page or error

Everything's clear now, isn't it? (: OK, it looks a bit less overwhelming in green and a higher and wider view in an editor.)

It's Seite 3 and Seite 4 in the lower part that are identical and duplicated in case of WE2 input on Seite 1.

Community
  • 1
  • 1
Gerold Broser
  • 14,080
  • 5
  • 48
  • 107

2 Answers2

1

(I'm not the downvoter)

I'm not sure what your issue is really about -- you'd need to supply more information:

  • where is the error coming from and what triggers it?
  • are you sure you don't tear down state/cookies/etc. between test cases?

Either way, in general you shouldn't share state between test cases where one test case depends on the previous, and the test cases must run in given order. Test cases (it) should be independent of each other and be self-sufficient.

Not adhering to this best practice results in complex test cases that are hard to maintain. You also wouldn't be able to toggle it.only to test a single case, and so on.

This is especially important when you don't own the whole codebase (in team projects), because someone might change how tests are being run (e.g. clear state between tests; randomize test order...).

dwelle
  • 6,894
  • 2
  • 46
  • 70
  • Thanks for your answer. I know that unit tests, for instance, should be self-contained/self-sufficient. Does this apply to functional tests in general, too? However, starting each test from `Page 1` seems the only way to go anyway here and `it.only` is a good point. – Gerold Broser Aug 03 '19 at 12:21
  • 1
    Yes, I'd say it applies generally - there's even a mention about it in [cypress docs](https://docs.cypress.io/guides/references/best-practices.html#Having-tests-rely-on-the-state-of-previous-tests) – dwelle Aug 03 '19 at 12:26
  • Thanks for the link. I visited a lot of Cypress' doc pages but not _that_ one. As I feared: I have to externalize all relevant code to `describe`-global functions and variables since it seems not to be possible to chain `beforeEach`, for instance, like `let beforeEachP2 = function() { beforeEach( function() { beforeEachP1(); ... }; beforeEachP2();`, is it? It seems that only one `beforeEach` runs in one `context`: the first that's invoked. – Gerold Broser Aug 03 '19 at 12:56
  • Yea, you can't declare `beforeEach` in a function that you invoke manually. You should let mocha (used by Cypress) do its thing on its own. But each nested `describe`/`context` can have its own `beforeEach`. Also note that `describe` & `context` is the same thing -- I wouldn't use both because it may suggest they both serve different purpose. – dwelle Aug 03 '19 at 14:48
  • Yes, but `beforeEach`, or `it` for that matter, in each nested `desribed`/`context` doesn't support me enough in this case. Having to start from the very beginning (`Page 1`) for each test, the same code is executed over and over again. Multiplying it in each `beforeEach`, or `it`, isn't really DRY. – Gerold Broser Aug 03 '19 at 15:04
0

I solved this by what I mentioned trying to avoid in my question: Externalizing relevant code to describe-global functions. That's all what I mentioned above but it's DRY, at least.

describe("Calculation", function() {

  let P1withAllInputs = function() {
    cy.visit(url)
    // ... setting input fields ...
    cy.get(next).click()
    cy.get('.stg-error-mark').should('have.length', 0)
  }

  context("Page 1", function() {

    // ... 

    it("With all inputs", function() {
      P1withAllInputs()
    })
  })

  context("Page 2", function() {

    P1withAllInputs()

    // ... 

    it("No selection", function() {
      cy.get(next).click()
      cy.get('.stg-error-mark').should('have.length', 2)
    })
  })
})
Gerold Broser
  • 14,080
  • 5
  • 48
  • 107