├── .github
└── workflows
│ ├── badges.yml
│ └── ci.yml
├── .gitignore
├── .prettierrc.json
├── README.md
├── cypress-v9
├── cypress.json
├── cypress
│ └── integration
│ │ └── spec.cy.js
├── package-lock.json
└── package.json
├── cypress.config.js
├── cypress
├── checkbox.html
├── close-dialog.html
├── colors.html
├── e2e
│ ├── alias.cy.js
│ ├── and-or.cy.js
│ ├── callback.cy.js
│ ├── checked.cy.js
│ ├── click-enabled.cy.js
│ ├── close-dialog.cy.js
│ ├── colors.cy.js
│ ├── contains.cy.js
│ ├── else.cy.js
│ ├── exists.cy.js
│ ├── finally.cy.js
│ ├── find.cy.js
│ ├── has-attribute.cy.js
│ ├── has-class.cy.js
│ ├── if-should.cy.js
│ ├── input-value.cy.js
│ ├── issue-59.cy.js
│ ├── more-than-n.cy.js
│ ├── not.cy.js
│ ├── null.cy.js
│ ├── raise-error.cy.js
│ ├── spec.cy.js
│ ├── task.cy.js
│ ├── terms-and-conditions.cy.js
│ ├── traversal.cy.js
│ ├── url.cy.js
│ └── wrap.cy.js
├── enabled-button.html
├── funky-input.html
├── index.html
├── list.html
└── terms.html
├── img
├── debug.png
├── dialog-closed.png
└── dialog-open.gif
├── package-lock.json
├── package.json
├── renovate.json
├── src
├── index-v11.js
├── index.d.ts
└── index.js
└── tsconfig.json
/.github/workflows/badges.yml:
--------------------------------------------------------------------------------
1 | name: badges
2 | on:
3 | schedule:
4 | # update badges every night
5 | # because we have a few badges that are linked
6 | # to the external repositories
7 | - cron: '0 3 * * *'
8 |
9 | jobs:
10 | badges:
11 | name: Badges
12 | runs-on: ubuntu-20.04
13 | steps:
14 | - name: Checkout 🛎
15 | uses: actions/checkout@v4
16 |
17 | - name: Update version badges 🏷
18 | run: npm run badges
19 |
20 | - name: Commit any changed files 💾
21 | uses: stefanzweifel/git-auto-commit-action@v4
22 | with:
23 | commit_message: Updated badges
24 | branch: main
25 | file_pattern: README.md
26 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on: [push, pull_request]
3 | jobs:
4 | test:
5 | runs-on: ubuntu-20.04
6 | steps:
7 | - name: Checkout 🛎
8 | uses: actions/checkout@v4
9 |
10 | - name: Run Cypress tests 🧪
11 | # https://github.com/cypress-io/github-action
12 | uses: cypress-io/github-action@v6
13 | with:
14 | # check if the types agree
15 | build: npm run types
16 |
17 | # https://github.com/actions/upload-artifact
18 | - uses: actions/upload-artifact@v4
19 | name: Store any error screenshots 🖼
20 | if: failure()
21 | with:
22 | name: cypress-screenshots
23 | path: cypress/screenshots
24 |
25 | - uses: actions/upload-artifact@v4
26 | name: Store any videos 🖼
27 | if: failure()
28 | with:
29 | name: cypress-videos
30 | path: cypress/videos
31 |
32 | # there was a breaking change under the hood in Cypress v11.1.0
33 | # so make sure this plugin still works for older versions
34 | test-cypress-v11-0:
35 | runs-on: ubuntu-20.04
36 | steps:
37 | - name: Checkout 🛎
38 | uses: actions/checkout@v4
39 |
40 | - name: Run Cypress tests 🧪
41 | # https://github.com/cypress-io/github-action
42 | uses: cypress-io/github-action@v6
43 | with:
44 | build: npm install -D cypress@11.0.1
45 |
46 | test-cypress-v9:
47 | runs-on: ubuntu-20.04
48 | steps:
49 | - name: Checkout 🛎
50 | uses: actions/checkout@v4
51 |
52 | - name: Install top dependencies 📦
53 | # https://github.com/cypress-io/github-action
54 | uses: cypress-io/github-action@v6
55 | with:
56 | runTests: false
57 |
58 | - name: Run Cypress v9 tests 🧪
59 | uses: cypress-io/github-action@v6
60 | with:
61 | working-directory: cypress-v9
62 |
63 | # https://github.com/actions/upload-artifact
64 | - uses: actions/upload-artifact@v4
65 | name: Store any v9 screenshots 🖼
66 | if: failure()
67 | with:
68 | name: cypress-screenshots-v9
69 | path: cypress-v9/cypress/screenshots
70 |
71 | release:
72 | needs: [test, test-cypress-v9, test-cypress-v11-0]
73 | runs-on: ubuntu-20.04
74 | if: github.ref == 'refs/heads/main'
75 | steps:
76 | - name: Checkout 🛎
77 | uses: actions/checkout@v4
78 |
79 | - name: Install only the semantic release 📦
80 | run: npm install semantic-release
81 |
82 | - name: Semantic Release 🚀
83 | uses: cycjimmy/semantic-release-action@v4
84 | with:
85 | branch: main
86 | env:
87 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
88 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
89 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | cypress/videos/
3 | cypress/screenshots/
4 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cypress-if  [](https://github.com/bahmutov/cypress-if/actions/workflows/ci.yml)
2 |
3 | > Easy conditional if-else logic for your Cypress tests
4 |
5 | Tested with `cy.get`, `cy.contains`, `cy.find`, `.then`, `.within` commands in Cypress v9 and v10+.
6 |
7 | - 📝 [Conditional Commands For Cypress](https://glebbahmutov.com/blog/cypress-if/)
8 | - 📝 [Cypress Flakiness Examples](https://glebbahmutov.com/blog/flakiness-example/)
9 | - 📝 [Click Button If Enabled](https://glebbahmutov.com/blog/click-button-if-enabled/)
10 | - 📺 [Introduction To Using cypress-if Plugin to Write Conditional Cypress Commands](https://youtu.be/TVwU0OvrVUA)
11 | - 📺 [Confirm Cypress Command Execution Order Using Sinon.js Spies](https://youtu.be/RTzJu44yAc8)
12 | - 📺 [cypress-if Plugin Supports Cypress v12+: Close The Popup Dialog If It Is Visible](https://youtu.be/PLP5Bq7KHTk)
13 | - 📺 [Click Button If Enabled Using cypress-if And cypress-await Plugins](https://youtu.be/H04FSnH-6U0)
14 | - 🎓 Covered in my [Cypress Plugins course](https://cypress.tips/courses/cypress-plugins)
15 | - [Lesson d1: Write conditional commands using cypress-if](https://cypress.tips/courses/cypress-plugins/lessons/d1)
16 | - [Lesson d2: Conditionally clear the items in a TodoMVC application](https://cypress.tips/courses/cypress-plugins/lessons/d2)
17 | - [Lesson d3: Ensure the settings dialog is open](https://cypress.tips/courses/cypress-plugins/lessons/d3)
18 | - [Lesson d4: How to avoid conditional test logic](https://cypress.tips/courses/cypress-plugins/lessons/d4)
19 | - [Lesson d5: Create a new user if the test cannot log in](https://cypress.tips/courses/cypress-plugins/lessons/d5)
20 | - [Lesson n5: Pagination using cypress-if](https://cypress.tips/courses/cypress-plugins/lessons/n5)
21 | - [Lesson d6: Expand a section based on its attribute value](https://cypress.tips/courses/cypress-plugins/lessons/n6)
22 | - 🎓 Used in my course [Visual Testing With Cypress](https://cypress.tips/courses/visual-testing)
23 | - [Bonus 04: Hide an element if it is visible](https://cypress.tips/courses/visual-testing/lessons/bonus04)
24 |
25 | ## ⚠️ Warning
26 |
27 | In general, Cypress team considers [conditional testing an anti-pattern](https://on.cypress.io/conditional-testing). Thus `cypress-if` should be used only if the test really cannot deterministically execute its steps. You can also read my [conditional testing](https://glebbahmutov.com/cypress-examples/recipes/conditional-testing.html) examples.
28 |
29 | ## No xpath support
30 |
31 | This plugin works by overriding `cy.get`, `cy.find`, and some other Cypress commands. It does NOT override the [cy.xpath](https://www.npmjs.com/package/@cypress/xpath) commands that comes from another plugin. I personally suggest never using `xpath` selectors (and I wrote `cy.xpath`), the jQuery selectors included with Cypress are much more powerful and less prone to breaking. Learn them using [cypress-examples](https://glebbahmutov.com/cypress-examples).
32 |
33 | ## Install
34 |
35 | Add this package as a dev dependency
36 |
37 | ```
38 | $ npm i -D cypress-if
39 | # or using Yarn
40 | $ yarn add -D cypress-if
41 | ```
42 |
43 | Include this package in your spec or support file
44 |
45 | ```js
46 | import 'cypress-if'
47 | ```
48 |
49 | ### Types
50 |
51 | Types for the `.if()` and `.else()` commands are described in the include typescript file [src/index.d.ts](./src/index.d.ts) file. If you need intellisense, include the type for this package in your `tscofig.json`
52 |
53 | ```jsonc
54 | "compilerOptions": {
55 | "types": [
56 | "cypress",
57 | "cypress-if" // add this line
58 | ]
59 | }
60 | ```
61 |
62 | For JavaScript projects that cannot use `tsconfig.json` or `jscofig.json`, the special comment might do the trick:
63 |
64 | ```js
65 | // your spec file "cypress/e2e/spec.cy.js" add this comment
66 | ///
67 | ```
68 |
69 | If it does not work, and TS still complains about unknown command `.if`, then do the following trick and move on:
70 |
71 | ```js
72 | cy.get(...)
73 | // @ts-ignore
74 | .if()
75 | ```
76 |
77 | ## Use
78 |
79 | Let's say, there is a dialog that might sometimes be visible when you visit the page. You can close it by finding it using the [cy.get](https://on.cypress.io/get) command follows by the `.if()` command. If the dialog really exists, then all commands chained after `.if()` run. If the dialog is not found, then the rest of the chain is skipped.
80 |
81 | ```js
82 | cy.get('dialog#survey').if().contains('button', 'Close').click()
83 | ```
84 |
85 | 
86 |
87 | ## Assertions
88 |
89 | By default, the `.if()` command just checks the existence of the element returned by the `cy.get` command. You might use instead a different assertion, like close a dialog if it is visible:
90 |
91 | ```js
92 | cy.get('dialog#survey').if('visible').contains('button', 'Close').click()
93 | ```
94 |
95 | If the dialog was invisible, the visibility assertion fails, and the rest of the commands was skipped
96 |
97 | 
98 |
99 | You can use assertions with arguments
100 |
101 | ```js
102 | cy.wrap(42).if('equal', 42)...
103 | ```
104 |
105 | You can use assertions with `not`
106 |
107 | ```js
108 | cy.get('#enrolled').if('not.checked').check()
109 | ```
110 |
111 | ### Callback function
112 |
113 | You can check the value yourself by writing a callback function, similar to the [should(callback)](http://on.cypress.io/should#Function) and its [many examples](https://glebbahmutov.com/cypress-examples/commands/assertions.html). You can use predicate and Chai assertions, but you **cannot use any Cypress commands inside the callback**, since it only synchronously checks the given value.
114 |
115 | ```js
116 | // predicate function returning a boolean
117 | const isEven = (n) => n % 2 === 0
118 | cy.wrap(42).if(isEven).log('even').else().log('odd')
119 | // a function using Chai assertions
120 | const is42 = (n) => expect(n).to.equal(42)
121 | cy.wrap(42).if(is42).log('42!').else().log('some other number')
122 | ```
123 |
124 | For more examples, see the [cypress/e2e/callback.cy.js](./cypress/e2e/callback.cy.js) spec
125 |
126 | ### Combining assertions
127 |
128 | If you want to right complex assertions that combine other checks using AND, OR connectors, please use a callback function.
129 |
130 | ```js
131 | // AND predicate using &&
132 | cy.wrap(42).if((n) => n > 20 && n < 50)
133 | // AND connector using Chai "and" connector
134 | cy.wrap(42).if((n) => expect(n).to.be.greaterThan(20).and.to.be.lessThan(50))
135 | // OR predicate using ||
136 | cy.wrap(42).if((n) => n > 20 || n < 10)
137 | ```
138 |
139 | Unfortunately, there is no Chai OR connector.
140 |
141 | For more examples, see the [cypress/e2e/and-or.cy.js](./cypress/e2e/and-or.cy.js) spec file
142 |
143 | ## else command
144 |
145 | You can chain `.else()` command that is only executed if the `.if()` is skipped.
146 |
147 | ```js
148 | cy.contains('Accept cookies')
149 | .if('visible')
150 | .click()
151 | .else()
152 | .log('no cookie banner')
153 | ```
154 |
155 | The subject from the `.if()` command will be passed to the `.else()` chain, this allows you to work with the original element:
156 |
157 | ```js
158 | cy.get('#enrolled')
159 | .if('checked')
160 | .log('**already enrolled**')
161 | // the checkbox should be passed into .else()
162 | .else()
163 | .check()
164 | ```
165 |
166 | You can print a message if the `ELSE` branch is taken
167 |
168 | ```js
169 | cy.get('...').if('...').else().log('a message')
170 | // same as
171 | cy.get('...').if('...').else('a message')
172 | ```
173 |
174 | ## Multiple commands
175 |
176 | Sometimes it makes sense to place the "if" or "else" commands into `.then()` block
177 |
178 | ```js
179 | cy.get('#survey')
180 | .if('visible')
181 | .then(() => {
182 | cy.log('closing the survey')
183 | cy.contains('button', 'Close').click()
184 | })
185 | .else()
186 | .then(() => {
187 | cy.log('Already closed')
188 | })
189 | ```
190 |
191 | ## Within
192 |
193 | You can attach `.within()` command to the `.if()`
194 |
195 | ```js
196 | cy.get('#survey')
197 | .if('visible')
198 | .within(() => {
199 | // fill the survey
200 | // click the submit button
201 | })
202 | ```
203 |
204 | ## finally
205 |
206 | You might want to finish if/else command chains and continue afterwards. This is the purpose for the `.finally()` child command:
207 |
208 | ```js
209 | cy.get('#agreed')
210 | .if('not.checked')
211 | .check()
212 | .else()
213 | .log('already checked')
214 | .finally()
215 | .should('be.checked')
216 | ```
217 |
218 | `.finally` comes in useful when you are chaining something and don't want the "if/else" to "leak" to the next series of commands. From [#59](https://github.com/bahmutov/cypress-if/issues/59) comes the [issue-59.cy.js](./github/../cypress/e2e/issue-59.cy.js)
219 |
220 | ```js
221 | function bar() {
222 | return (
223 | cy
224 | .wrap('testing')
225 | .if()
226 | .then(() => cy.wrap('got it'))
227 | .else()
228 | .then(() => cy.wrap('else do'))
229 | // to correctly STOP the chaining if/else
230 | // from putting anything chained of bar()
231 | // need to add .finally() command
232 | .finally()
233 | )
234 | }
235 | bar().then((it) => {
236 | cy.log(`result: ${it}`)
237 | })
238 | // logs:
239 | // "testing"
240 | // "got it"
241 | // result: got it"
242 | ```
243 |
244 | ## cy.task
245 |
246 | You can perform commands if the `cy.task` failed
247 |
248 | ```js
249 | cy.task('throws').if('failed')
250 | // handle the failure
251 | ```
252 |
253 | ## Aliases
254 |
255 | You can have conditional commands depending on an alias that might exist.
256 |
257 | ```js
258 | cy.get('@maybe')
259 | .if()
260 | // commands to execute if the alias "maybe" exists
261 | .else()
262 | // commands to execute if the alias "maybe" does not exist
263 | .finally()
264 | // commands to execute after
265 | .log(...)
266 | ```
267 |
268 | See spec [alias.cy.js](./cypress/e2e/alias.cy.js)
269 |
270 | ## Null values
271 |
272 | Typically `null` values are treated same as `undefined` and follow the "else" path. You can specifically check for `null` and `not.null` using these assertions:
273 |
274 | ```js
275 | cy.wrap(null).if('null') // takes IF path
276 | cy.wrap(null).if('not.null') // takes ELSE path
277 | cy.wrap(42).if('not.null') // takes IF path
278 | ```
279 |
280 | See spec [null.cy.js](./cypress/e2e/null.cy.js)
281 |
282 | ## Multiple values
283 |
284 | Some assertions need two values, for example:
285 |
286 | ```js
287 | // only checks the presence of the "data-x" HTML attribute
288 | .if('have.attr', 'data-x')
289 | // checks if the "data-x" attribute present AND has value "123"
290 | .if('have.attr', 'data-x', '123')
291 | ```
292 |
293 | ## raise
294 |
295 | This plugin includes a utility custom command `cy.raise` that lets you conveniently throw an error.
296 |
297 | ```js
298 | cy.get('li').if('not.have.length', 3).raise('Wrong number of todos')
299 | ```
300 |
301 | **Tip:** the above syntax works, but you better pass an Error instance rather than a string to get the exact stack trace location
302 |
303 | ```js
304 | cy.get('li').if('not.have.length', 3).raise(new Error('Wrong number of todos'))
305 | ```
306 |
307 | ## More examples
308 |
309 | Check out the spec files in [cypress/e2e](./cypress/e2e/) folder. If you still have a question, [open a GitHub issue](https://github.com/bahmutov/cypress-if/issues).
310 |
311 | ## Debugging
312 |
313 | This module uses [debug](https://github.com/debug-js/debug#readme) module to output verbose browser console messages when needed. To turn the logging on, open the browser's DevTools console and set the local storage entry:
314 |
315 | ```js
316 | localStorage.debug = 'cypress-if'
317 | ```
318 |
319 | If you re-run the tests, you should see the messages appear in the console
320 |
321 | 
322 |
323 | ## See also
324 |
325 | - [cypress-wait-if-happens](https://github.com/bahmutov/cypress-wait-if-happens)
326 | - [cypress-ngx-ui-testing](https://github.com/swimlane/ngx-ui/tree/master/projects/swimlane/ngx-ui-testing)
327 |
328 | ## Small print
329 |
330 | Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2022
331 |
332 | - [@bahmutov](https://twitter.com/bahmutov)
333 | - [glebbahmutov.com](https://glebbahmutov.com)
334 | - [blog](https://glebbahmutov.com/blog)
335 | - [videos](https://www.youtube.com/glebbahmutov)
336 | - [presentations](https://slides.com/bahmutov)
337 | - [cypress.tips](https://cypress.tips)
338 | - [Cypress Tips & Tricks Newsletter](https://cypresstips.substack.com/)
339 | - [my Cypress courses](https://cypress.tips/courses)
340 |
341 | License: MIT - do anything with the code, but don't blame me if it does not work.
342 |
343 | Support: if you find any problems with this module, email / tweet /
344 | [open issue](https://github.com/bahmutov/cypress-if/issues) on Github
345 |
346 | ## MIT License
347 |
348 | Copyright (c) 2022 Gleb Bahmutov <gleb.bahmutov@gmail.com>
349 |
350 | Permission is hereby granted, free of charge, to any person
351 | obtaining a copy of this software and associated documentation
352 | files (the "Software"), to deal in the Software without
353 | restriction, including without limitation the rights to use,
354 | copy, modify, merge, publish, distribute, sublicense, and/or sell
355 | copies of the Software, and to permit persons to whom the
356 | Software is furnished to do so, subject to the following
357 | conditions:
358 |
359 | The above copyright notice and this permission notice shall be
360 | included in all copies or substantial portions of the Software.
361 |
362 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
363 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
364 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
365 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
366 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
367 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
368 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
369 | OTHER DEALINGS IN THE SOFTWARE.
370 |
--------------------------------------------------------------------------------
/cypress-v9/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "pluginsFile": false,
3 | "supportFile": false,
4 | "fixturesFolder": false
5 | }
6 |
--------------------------------------------------------------------------------
/cypress-v9/cypress/integration/spec.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import '../../../src'
3 |
4 | it('executes the IF branch', () => {
5 | cy.wrap(1)
6 | .if('equal', 1)
7 | .then(cy.spy().as('if'))
8 | .else()
9 | .then(cy.spy().as('else'))
10 | .finally()
11 | .should('equal', 1)
12 | cy.get('@if').should('have.been.calledOnce')
13 | cy.get('@else').should('not.be.called')
14 | })
15 |
16 | describe('wrapped value', () => {
17 | it('performs an action if the wrapped value is equal to 42', () => {
18 | cy.wrap(42).if('equal', 42).then(cy.spy().as('action')).then(cy.log)
19 | cy.get('@action').should('have.been.calledOnce')
20 | })
21 |
22 | it('does nothing if it is not 42', () => {
23 | cy.wrap(1).if('equal', 42).then(cy.spy().as('action')).then(cy.log)
24 | cy.get('@action').should('not.have.been.called')
25 | })
26 |
27 | context('.else', () => {
28 | it('passes the subject to the else branch', () => {
29 | cy.wrap(1).if('equal', 42).log('if branch').else().should('equal', 1)
30 | })
31 |
32 | it('passes the subject if().else()', () => {
33 | cy.wrap(1).if('equal', 42).else().should('equal', 1)
34 | })
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/cypress-v9/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cypress-v9",
3 | "version": "1.0.0",
4 | "description": "Testing on Cypress v9",
5 | "private": true,
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "cypress run"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "cypress": "9.7.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/cypress.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('cypress')
2 |
3 | module.exports = defineConfig({
4 | e2e: {
5 | experimentalRunAllSpecs: true,
6 | fixturesFolder: false,
7 | supportFile: false,
8 | viewportWidth: 200,
9 | viewportHeight: 200,
10 | defaultCommandTimeout: 1000,
11 | video: true,
12 | setupNodeEvents(on, config) {
13 | // implement node event listeners here
14 | on('task', {
15 | get42() {
16 | console.log('returning 42')
17 | return 42
18 | },
19 |
20 | throws() {
21 | console.log('throwing an error from cy.task')
22 | throw new Error('Nope')
23 | },
24 | })
25 | },
26 | },
27 | })
28 |
--------------------------------------------------------------------------------
/cypress/checkbox.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Enrolled already
7 |
8 |
9 |
10 |
11 | Agree
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/cypress/close-dialog.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 | My page
15 |
16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
17 | tempor incididunt ut labore et dolore magna aliqua. Hac habitasse platea
18 | dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Scelerisque
19 | fermentum dui faucibus in ornare. Suspendisse potenti nullam ac tortor
20 | vitae purus. Porta lorem mollis aliquam ut. Nunc sed augue lacus viverra
21 | vitae congue eu consequat. Consequat id porta nibh venenatis cras sed.
22 | Gravida quis blandit turpis cursus in hac habitasse. Felis eget nunc
23 | lobortis mattis. Ac odio tempor orci dapibus. Est pellentesque elit
24 | ullamcorper dignissim cras. Mollis nunc sed id semper risus in hendrerit.
25 | Vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Et netus et
26 | malesuada fames ac turpis egestas.
27 |
28 |
29 | Take a survey!
30 |
31 |
32 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/cypress/colors.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/cypress/e2e/alias.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('aliases', () => {
7 | it('has an existing alias', () => {
8 | cy.wrap(42).as('answer')
9 | cy.get('@answer')
10 | .if('exist')
11 | .log('alias exists')
12 | .then(cy.spy().as('if'))
13 | .else()
14 | .then(cy.spy().as('else'))
15 | cy.get('@if').should('have.been.called')
16 | cy.get('@else').should('not.have.been.called')
17 | })
18 |
19 | it('has an existing alias no arguments', () => {
20 | cy.wrap(42).as('answer')
21 | cy.get('@answer')
22 | .if()
23 | .log('alias exists')
24 | .then(cy.spy().as('if'))
25 | .else()
26 | .then(cy.spy().as('else'))
27 | cy.get('@if').should('have.been.called')
28 | cy.get('@else').should('not.have.been.called')
29 | })
30 |
31 | it('has no alias', () => {
32 | // notice there is no alias with the name "answer"
33 | cy.get('@answer')
34 | .if()
35 | .log('alias exists')
36 | .then(cy.spy().as('if'))
37 | .else()
38 | .then(cy.spy().as('else'))
39 | cy.get('@else').should('have.been.called')
40 | cy.get('@if').should('not.have.been.called')
41 | })
42 |
43 | it('alias has null value is treated as non-existent', () => {
44 | cy.wrap(null).as('nullAlias')
45 | cy.get('@nullAlias')
46 | .if()
47 | .log('alias exists')
48 | .then(cy.spy().as('if'))
49 | .else()
50 | .then(cy.spy().as('else'))
51 | cy.get('@else').should('have.been.called')
52 | cy.get('@if').should('not.have.been.called')
53 | })
54 | })
55 |
--------------------------------------------------------------------------------
/cypress/e2e/and-or.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('AND assertions', () => {
7 | context('uses && inside the predicate callback', () => {
8 | it('T && T', () => {
9 | cy.wrap(42)
10 | .if((n) => n > 20 && n < 50)
11 | .then(cy.spy().as('if'))
12 | .else()
13 | .then(cy.spy().as('else'))
14 | cy.get('@if').should('have.been.called')
15 | cy.get('@else').should('not.have.been.called')
16 | })
17 |
18 | it('T && F', () => {
19 | cy.wrap(42)
20 | .if((n) => n > 20 && n < 40)
21 | .then(cy.spy().as('if'))
22 | .else()
23 | .then(cy.spy().as('else'))
24 | cy.get('@else').should('have.been.called')
25 | cy.get('@if').should('not.have.been.called')
26 | })
27 | })
28 |
29 | context('uses Chai assertions', () => {
30 | it('ok and ok', () => {
31 | cy.wrap(42)
32 | .if((n) => expect(n).to.be.greaterThan(20).and.to.be.lessThan(50))
33 | .then(cy.spy().as('if'))
34 | .else()
35 | .then(cy.spy().as('else'))
36 | cy.get('@if').should('have.been.called')
37 | cy.get('@else').should('not.have.been.called')
38 | })
39 |
40 | it('ok and not ok', () => {
41 | cy.wrap(42)
42 | .if((n) => expect(n).to.be.greaterThan(20).and.to.be.lessThan(40))
43 | .then(cy.spy().as('if'))
44 | .else()
45 | .then(cy.spy().as('else'))
46 | cy.get('@else').should('have.been.called')
47 | cy.get('@if').should('not.have.been.called')
48 | })
49 | })
50 | })
51 |
52 | describe('OR assertions', () => {
53 | context('uses || inside the predicate callback', () => {
54 | it('T || F', () => {
55 | cy.wrap(42)
56 | .if((n) => n > 20 || n < 10)
57 | .then(cy.spy().as('if'))
58 | .else()
59 | .then(cy.spy().as('else'))
60 | cy.get('@if').should('have.been.called')
61 | cy.get('@else').should('not.have.been.called')
62 | })
63 |
64 | it('F || T', () => {
65 | cy.wrap(42)
66 | .if((n) => n > 200 || n < 50)
67 | .then(cy.spy().as('if'))
68 | .else()
69 | .then(cy.spy().as('else'))
70 | cy.get('@if').should('have.been.called')
71 | cy.get('@else').should('not.have.been.called')
72 | })
73 |
74 | it('F || F', () => {
75 | cy.wrap(42)
76 | .if((n) => n > 200 || n < -40)
77 | .then(cy.spy().as('if'))
78 | .else()
79 | .then(cy.spy().as('else'))
80 | cy.get('@else').should('have.been.called')
81 | cy.get('@if').should('not.have.been.called')
82 | })
83 | })
84 |
85 | // note that we cannot easily do OR using Chai assertions
86 | })
87 |
--------------------------------------------------------------------------------
/cypress/e2e/callback.cy.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import '../../src'
4 |
5 | describe('assertion callback function', () => {
6 | context('predicate', () => {
7 | const isEven = (n) => n % 2 === 0
8 |
9 | it('if branch', () => {
10 | cy.wrap(42)
11 | .if(isEven)
12 | .log('even')
13 | .then(cy.spy().as('if'))
14 | .else()
15 | .log('odd')
16 | .then(cy.spy().as('else'))
17 | cy.get('@if').should('have.been.called')
18 | cy.get('@else').should('not.be.called')
19 | })
20 |
21 | it('else branch', () => {
22 | cy.wrap(1)
23 | .if(isEven)
24 | .log('even')
25 | .then(cy.spy().as('if'))
26 | .else()
27 | .log('odd')
28 | .then(cy.spy().as('else'))
29 | cy.get('@else').should('have.been.called')
30 | cy.get('@if').should('not.be.called')
31 | })
32 | })
33 |
34 | context('Chai assertion', () => {
35 | const is42 = (n) => expect(n).to.equal(42)
36 |
37 | it('if branch', () => {
38 | cy.wrap(42)
39 | .if(is42)
40 | .log('even')
41 | .then(cy.spy().as('if'))
42 | .else()
43 | .log('odd')
44 | .then(cy.spy().as('else'))
45 | cy.get('@if').should('have.been.called')
46 | cy.get('@else').should('not.be.called')
47 | })
48 |
49 | it('else branch', () => {
50 | cy.wrap(1)
51 | .if(is42)
52 | .log('even')
53 | .then(cy.spy().as('if'))
54 | .else()
55 | .log('odd')
56 | .then(cy.spy().as('else'))
57 | cy.get('@else').should('have.been.called')
58 | cy.get('@if').should('not.be.called')
59 | })
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/cypress/e2e/checked.cy.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import '../../src'
4 |
5 | describe('checkbox', () => {
6 | it('checks the box when it is not checked already', () => {
7 | cy.visit('cypress/checkbox.html')
8 | cy.get('#enrolled').if('not.checked').check()
9 | })
10 |
11 | it('does nothing if the box is already checked', () => {
12 | cy.visit('cypress/checkbox.html')
13 | cy.get('#agreed').if('not.checked').check()
14 | })
15 |
16 | context('with else() branches', () => {
17 | it('logs a message when nothing to check', () => {
18 | cy.visit('cypress/checkbox.html')
19 | cy.get('#agreed')
20 | .if('not.checked')
21 | .check()
22 | .else()
23 | .log('**already agreed**')
24 | })
25 |
26 | it('handles if().else() short chain', () => {
27 | cy.visit('cypress/checkbox.html')
28 | cy.get('#enrolled').if('checked').else().check()
29 | cy.get('#enrolled').should('be.checked')
30 | })
31 |
32 | it('checks the button if not checked', () => {
33 | cy.visit('cypress/checkbox.html')
34 | cy.get('#enrolled')
35 | .if('checked')
36 | // .log('**already enrolled**')
37 | .else()
38 | .check()
39 | cy.get('#enrolled').should('be.checked')
40 | })
41 |
42 | it('passes the subject to the else() branch', () => {
43 | cy.visit('cypress/checkbox.html')
44 | cy.get('#enrolled')
45 | .if('checked')
46 | .log('**already enrolled**')
47 | // the checkbox should be passed into .else()
48 | .else()
49 | .check()
50 | cy.get('#enrolled').should('be.checked')
51 | })
52 | })
53 | })
54 |
--------------------------------------------------------------------------------
/cypress/e2e/click-enabled.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | // https://github.com/bahmutov/cypress-if
5 | import '../../src'
6 |
7 | it('clicks the button if enabled', () => {
8 | cy.visit('cypress/enabled-button.html')
9 | cy.contains('button', 'Click Me')
10 | // tests using Chai assertion "be.enabled"
11 | .if('enabled')
12 | .click()
13 | .should('have.text', 'Clicked')
14 | .else('Button is disabled')
15 | })
16 |
17 | it('clicks the button if enabled, checks using jQuery is :enabled', () => {
18 | cy.visit('cypress/enabled-button.html')
19 | cy.contains('button', 'Click Me')
20 | .invoke('is', ':enabled')
21 | .if('equals', true)
22 | // grab the button again using "cy.document + cy.contains"
23 | // combination to avoid using the Boolean subject
24 | // from the previous command
25 | .document()
26 | .contains('button', 'Click Me')
27 | .click()
28 | .should('have.text', 'Clicked')
29 | .else('Button is disabled')
30 | })
31 |
--------------------------------------------------------------------------------
/cypress/e2e/close-dialog.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | function visit(showDialog = true) {
7 | cy.visit('cypress/close-dialog.html', {
8 | onBeforeLoad(win) {
9 | // trick the app to open / hide the dialog
10 | cy.stub(win.Math, 'random').returns(showDialog ? 0 : 1)
11 | },
12 | })
13 | const submitForm = cy.stub().as('submitForm')
14 | if (showDialog) {
15 | cy.get('dialog#survey').invoke('on', 'submit', submitForm)
16 | }
17 | }
18 |
19 | it(
20 | 'closes the survey dialog',
21 | { viewportWidth: 500, viewportHeight: 500 },
22 | () => {
23 | visit(true)
24 | cy.get('dialog#survey')
25 | .if('visible')
26 | .wait(1000)
27 | .contains('button', 'Close')
28 | .click()
29 | // if there is a dialog on top,
30 | // then the main text is not visible
31 | cy.get('#main').should('be.visible')
32 | // in this test the dialog should have been submitted
33 | cy.get('@submitForm').should('have.been.calledOnce')
34 | },
35 | )
36 |
37 | it(
38 | 'skips the commands since the dialog is closed',
39 | { viewportWidth: 500, viewportHeight: 500 },
40 | () => {
41 | visit(false)
42 | cy.get('dialog#survey')
43 | .if('visible')
44 | .wait(1000)
45 | .contains('button', 'Close')
46 | .click()
47 | // if there is a dialog on top,
48 | // then the main text is not visible
49 | cy.get('#main').should('be.visible')
50 | // in this test the dialog was never submitted
51 | cy.get('@submitForm').should('not.have.been.called')
52 | },
53 | )
54 |
55 | it(
56 | 'controls the cy.get timeout',
57 | { viewportWidth: 500, viewportHeight: 500 },
58 | () => {
59 | visit(false)
60 | cy.get('does-not-exist', { timeout: 0 })
61 | .if()
62 | .log('found it')
63 | .else()
64 | .log('does not exist')
65 | },
66 | )
67 |
68 | describe('cy.then support', () => {
69 | it(
70 | 'executes the .then callback if the dialog is visible',
71 | { viewportWidth: 500, viewportHeight: 500 },
72 | () => {
73 | visit(true)
74 | cy.get('dialog#survey')
75 | .if('visible')
76 | .then(() => {
77 | cy.log('**closing the dialog**')
78 | cy.contains('dialog#survey button', 'Close').wait(1000).click()
79 | cy.get('dialog').should('not.be.visible')
80 | })
81 |
82 | // if there is a dialog on top,
83 | // then the main text is not visible
84 | cy.get('#main').should('be.visible')
85 | // in this test the dialog should have been submitted
86 | cy.get('@submitForm').should('have.been.calledOnce')
87 | },
88 | )
89 |
90 | it(
91 | 'skips the .then callback if the dialog is hidden',
92 | { viewportWidth: 500, viewportHeight: 500 },
93 | () => {
94 | visit(false)
95 | cy.get('dialog#survey')
96 | .if('visible')
97 | .then(() => {
98 | cy.log('**closing the dialog**')
99 | cy.contains('dialog#survey button', 'Close').wait(1000).click()
100 | cy.get('dialog').should('not.be.visible')
101 | })
102 | // if there is a dialog on top,
103 | // then the main text is not visible
104 | cy.get('#main').should('be.visible')
105 | // in this test the dialog was never submitted
106 | cy.get('@submitForm').should('not.have.been.called')
107 | },
108 | )
109 | })
110 |
111 | describe('cy.contains support', () => {
112 | it(
113 | 'clicks the close survey button',
114 | { viewportWidth: 500, viewportHeight: 500 },
115 | () => {
116 | visit(true)
117 | cy.contains('dialog#survey button', 'Close')
118 | .if('visible')
119 | .wait(1000)
120 | .click()
121 | // if there is a dialog on top,
122 | // then the main text is not visible
123 | cy.get('#main').should('be.visible')
124 | // in this test the dialog should have been submitted
125 | cy.get('@submitForm').should('have.been.calledOnce')
126 | },
127 | )
128 |
129 | it(
130 | 'skips click when the button is hidden',
131 | { viewportWidth: 500, viewportHeight: 500 },
132 | () => {
133 | visit(false)
134 | cy.contains('dialog#survey button', 'Close')
135 | .if('visible')
136 | .wait(1000)
137 | .click()
138 | // if there is a dialog on top,
139 | // then the main text is not visible
140 | cy.get('#main').should('be.visible')
141 | // in this test the dialog was never submitted
142 | cy.get('@submitForm').should('not.have.been.called')
143 | },
144 | )
145 | })
146 |
147 | describe('cy.find support', () => {
148 | it(
149 | 'finds the close button and closes the dialog',
150 | { viewportWidth: 500, viewportHeight: 500 },
151 | () => {
152 | visit(true)
153 | // make sure the page has finished loading
154 | cy.get('#main')
155 | cy.get('body').find('#close').if('visible').wait(1000).click()
156 | // if there is a dialog on top,
157 | // then the main text is not visible
158 | cy.get('#main').should('be.visible')
159 | // in this test the dialog should have been submitted
160 | cy.get('@submitForm').should('have.been.calledOnce')
161 | },
162 | )
163 |
164 | it(
165 | 'skips click when it cannot find the button',
166 | { viewportWidth: 500, viewportHeight: 500 },
167 | () => {
168 | visit(false)
169 | // make sure the page has finished loading
170 | cy.get('#main')
171 | // then check if the close button is present
172 | cy.get('body').find('#close').if('visible').wait(1000).click()
173 | // if there is a dialog on top,
174 | // then the main text is not visible
175 | cy.get('#main').should('be.visible')
176 | // in this test the dialog was never submitted
177 | cy.get('@submitForm').should('not.have.been.called')
178 | },
179 | )
180 | })
181 |
--------------------------------------------------------------------------------
/cypress/e2e/colors.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | it('combines all colors on the page', () => {
7 | cy.visit('cypress/colors.html')
8 | for (let k = 1; k < 10; k += 1) {
9 | cy.get('#colors #color' + k, { timeout: 100 })
10 | .if('exist')
11 | .invoke('text')
12 | .invoke('trim')
13 | .as('color' + k)
14 | }
15 | cy.then(function () {
16 | const colorNames = [
17 | this.color1,
18 | this.color2,
19 | this.color3,
20 | this.color4,
21 | this.color5,
22 | this.color6,
23 | this.color7,
24 | this.color8,
25 | this.color9,
26 | ]
27 | .filter(Boolean)
28 | .join(', ')
29 | expect(colorNames, 'color names').to.equal('red, green')
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/cypress/e2e/contains.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('cy.contains support', () => {
7 | beforeEach(() => {
8 | cy.visit('cypress/index.html')
9 | })
10 |
11 | it('clicks on the existing button', () => {
12 | cy.get('#load').invoke('on', 'click', cy.spy().as('clicked'))
13 | cy.log('**button exists**')
14 | cy.contains('button', 'Load').if().click()
15 | cy.get('@clicked').should('have.been.calledOnce')
16 | })
17 |
18 | it('works with the text only', () => {
19 | cy.get('#load').invoke('on', 'click', cy.spy().as('clicked'))
20 | cy.contains('Load').if().click()
21 | cy.get('@clicked').should('have.been.calledOnce')
22 | })
23 |
24 | it('passes the attached assertions', () => {
25 | cy.log('**attached assertions are passing**')
26 | cy.contains('button', 'Load').if().should('be.visible')
27 | })
28 |
29 | it('clicks on the button by text if exists', () => {
30 | cy.log('**button does not exist**')
31 | cy.contains('button', 'does-not-exist').if().click()
32 | cy.log('**attached assertions are skipped**')
33 | cy.contains('button', 'does-not-exist').if().should('not.exist')
34 | })
35 |
36 | it('passes the timeout', () => {
37 | cy.contains('button', 'does-not-exist', { timeout: 500 }).if().click()
38 | })
39 |
40 | it('does not click invisible button', () => {
41 | cy.log('**button exist but is hidden**')
42 | cy.contains('button#hidden', 'Cannot see me').if('visible').click()
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/cypress/e2e/else.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('else branch', () => {
7 | it('takes the if branch', () => {
8 | cy.wrap(42).if('equal', 42).log('if branch').else().log('else branch')
9 | cy.log('**built-in log**')
10 | cy.wrap(42).if('equal', 42).log('if branch').else('else branch')
11 | })
12 |
13 | it('takes the else branch', () => {
14 | cy.wrap(42).if('equal', 1).log('if branch').else().log('else branch')
15 | cy.log('**built-in log**')
16 | cy.wrap(42).if('equal', 1).log('if branch').else('else branch')
17 | cy.log('**prints numbers**')
18 | cy.wrap(42).if('equal', 1).log('if branch').else(42)
19 | })
20 |
21 | it('logs the else message', () => {
22 | cy.spy(cy, 'log').as('log')
23 | cy.wrap(true).if('false').else('else branch')
24 | cy.get('@log').should('have.been.calledWith', 'else branch')
25 | })
26 |
27 | it('logs the default else message', () => {
28 | cy.spy(cy, 'log').as('log')
29 | cy.wrap(true).if('false').else()
30 | cy.get('@log').should('not.have.been.called')
31 | })
32 |
33 | it('can have multiple if-else', () => {
34 | cy.wrap(1)
35 | .if('equal', 2)
36 | .log('is 2')
37 | .else()
38 | .if('equal', 3)
39 | .log('is 3')
40 | .if('equal', 1)
41 | .log('is 1')
42 | })
43 |
44 | it('attaches should', () => {
45 | cy.wrap(1).if('equal', 1).should('equal', 1).else().should('equal', 2)
46 | })
47 |
48 | context('with checks', () => {
49 | it('calls actions in the if branch', () => {
50 | cy.wrap(42)
51 | .if('equal', 42)
52 | .then(cy.spy().as('if'))
53 | .else()
54 | .then(cy.spy().as('else'))
55 | cy.get('@if').should('have.been.calledOnce')
56 | cy.get('@else').should('not.be.called')
57 | })
58 |
59 | it('skips the entire ELSE chain', () => {
60 | cy.visit('cypress/index.html')
61 | cy.get('#load')
62 | .if()
63 | .log('found it')
64 | .get('#load')
65 | .click()
66 | .else()
67 | .log('ughh, why execute the else branch')
68 | .then(() => {
69 | throw new Error('no!!!')
70 | })
71 | })
72 |
73 | it('skips the entire ELSE chain even if it has parent commands', () => {
74 | cy.visit('cypress/index.html')
75 | cy.get('#load')
76 | .if()
77 | .log('found it')
78 | .get('#load')
79 | .click()
80 | .else()
81 | .get('#load')
82 | .log('ughh, why execute the else branch after a parent command')
83 | .then(() => {
84 | throw new Error('no!!!')
85 | })
86 | })
87 |
88 | it('calls actions in the else branch', () => {
89 | cy.wrap(42)
90 | .if('equal', 1)
91 | .then(cy.spy().as('if'))
92 | .else()
93 | .then(cy.spy().as('else'))
94 | cy.get('@else').should('have.been.calledOnce')
95 | cy.get('@if').should('not.be.called')
96 | })
97 |
98 | it('attaches the .then block correctly', () => {
99 | cy.wrap(42)
100 | .if('equal', 1)
101 | .then(cy.spy().as('if'))
102 | .else()
103 | .then(() => {
104 | cy.spy().as('else1')()
105 | cy.spy().as('else2')()
106 | })
107 | cy.get('@else1').should('have.been.calledOnce')
108 | cy.get('@else2').should('have.been.calledOnce')
109 | cy.get('@if').should('not.be.called')
110 | })
111 | })
112 | })
113 |
--------------------------------------------------------------------------------
/cypress/e2e/exists.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | beforeEach(() => {
7 | cy.visit('cypress/index.html')
8 | })
9 |
10 | it('checks an element that exists', () => {
11 | cy.get('#fruits')
12 | .if('exist')
13 | .then(cy.spy().as('if'))
14 | .else()
15 | .then(cy.spy().as('else'))
16 | cy.get('@if').should('have.been.calledOnce')
17 | cy.get('@else').should('not.have.been.called')
18 | })
19 |
20 | it('checks an element that does not exists', () => {
21 | cy.get('#not-found')
22 | .if('exist')
23 | .then(cy.spy().as('if'))
24 | .else()
25 | .then(cy.spy().as('else'))
26 | cy.get('@else').should('have.been.calledOnce')
27 | cy.get('@if').should('not.have.been.called')
28 | })
29 |
30 | // https://github.com/bahmutov/cypress-if/issues/45
31 | it('checks an element that does not exists using not.exist', () => {
32 | cy.get('#not-found').should('not.exist')
33 | cy.get('#not-found')
34 | .if('not.exist')
35 | .then(cy.spy().as('if'))
36 | .else()
37 | .then(cy.spy().as('else'))
38 | cy.get('@if').should('have.been.calledOnce')
39 | cy.get('@else').should('not.have.been.called')
40 | })
41 |
42 | it('clicks on the element with force: true', () => {
43 | cy.window()
44 | .its('console')
45 | .then((console) => {
46 | cy.spy(console, 'log').withArgs('clicked').as('logClicked')
47 | })
48 | cy.get('#load').if('exist').click({ force: true })
49 | cy.get('@logClicked').should('have.been.calledOnce')
50 | })
51 |
52 | it('uses exists as an alias to exist', () => {
53 | cy.get('#fruits')
54 | .if('exists')
55 | .then(cy.spy().as('if'))
56 | .else()
57 | .then(cy.spy().as('else'))
58 | cy.get('@if').should('have.been.calledOnce')
59 | cy.get('@else').should('not.have.been.called')
60 | })
61 |
62 | it('supports cy.not', () => {
63 | cy.log('**IF path**')
64 | cy.get('#fruits li')
65 | .not(':odd')
66 | .if('exists')
67 | .log('found even')
68 | .else()
69 | .raise('it is odd')
70 |
71 | cy.log('**ELSE path**')
72 | cy.get('#fruits li')
73 | .not('li')
74 | .if('exists')
75 | .raise('cy.not is not supported')
76 | .else()
77 | .log('cy.not is supported')
78 | })
79 |
--------------------------------------------------------------------------------
/cypress/e2e/finally.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('finally', () => {
7 | it('executes after the IF path', () => {
8 | cy.visit('cypress/checkbox.html')
9 | cy.get('#enrolled').if('not.checked').check().finally().should('be.checked')
10 | })
11 |
12 | it('executes after the ELSE path', () => {
13 | cy.visit('cypress/checkbox.html')
14 | cy.get('#agreed')
15 | .if('not.checked')
16 | .check()
17 | .else()
18 | .log('already checked')
19 | .finally()
20 | .should('be.checked')
21 | })
22 |
23 | it('executes the IF branch', () => {
24 | cy.wrap(1)
25 | .if('equal', 1)
26 | .then(cy.spy().as('if'))
27 | .else()
28 | .then(cy.spy().as('else'))
29 | .finally()
30 | .then((/** @type number */ subject) => {
31 | expect(subject, 'subject').to.equal(1)
32 | })
33 | .should('equal', 1)
34 | cy.get('@if').should('have.been.calledOnce')
35 | cy.get('@else').should('not.be.called')
36 | })
37 |
38 | it('executes the ELSE branch', () => {
39 | cy.wrap(1)
40 | .if('equal', 42)
41 | .then(cy.spy().as('if'))
42 | .else()
43 | .then(cy.spy().as('else'))
44 | .finally()
45 | .should('equal', 1)
46 | cy.get('@else').should('have.been.calledOnce')
47 | cy.get('@if').should('not.be.called')
48 | })
49 |
50 | it('executes the FINALLY command', () => {
51 | cy.wrap(1)
52 | .if('equal', 42)
53 | .then(cy.spy().as('if'))
54 | .else()
55 | .then(cy.spy().as('else'))
56 | .finally()
57 | .then(cy.spy().as('finally'))
58 | cy.get('@else').should('have.been.calledOnce')
59 | cy.get('@if').should('not.be.called')
60 | cy.get('@finally').should('have.been.calledOnce')
61 | })
62 |
63 | it('yields the IF subject without ELSE branch', () => {
64 | cy.wrap(1)
65 | .if('equal', 1)
66 | .then((n) => {
67 | expect(n, 'if n').to.equal(1)
68 | console.log('if path, n = %d', n)
69 | return 101
70 | })
71 | .finally()
72 | .should('equal', 101)
73 | })
74 |
75 | it('yields the IF subject', () => {
76 | cy.wrap(1)
77 | .if('equal', 1)
78 | .then((n) => {
79 | expect(n, 'if n').to.equal(1)
80 | console.log('if path, n = %d', n)
81 | return 101
82 | })
83 | .else()
84 | .then(() => -1)
85 | .finally()
86 | .should('equal', 101)
87 | })
88 |
89 | it('yields the ELSE subject', () => {
90 | cy.wrap(1)
91 | .if('equal', 42)
92 | .then((n) => {
93 | expect(n, 'if n').to.equal(1)
94 | console.log('if path, n = %d', n)
95 | return 101
96 | })
97 | .else()
98 | .then(() => -1)
99 | .finally()
100 | .should('equal', -1)
101 | })
102 |
103 | it('calls else command before finally', () => {
104 | cy.wrap(1)
105 | // .if() comes from the cypress-if plugin
106 | // https://github.com/bahmutov/cypress-if
107 | .if('equals', 2)
108 | .log('if branch')
109 | .then(cy.spy().as('if'))
110 | .else()
111 | .log('else branch')
112 | .then(cy.spy().as('else'))
113 | .finally()
114 | .log('finally')
115 | .then(cy.spy().as('finally'))
116 | cy.get('@else').should('have.been.calledOnce')
117 | cy.get('@finally').should('have.been.calledOnce')
118 | cy.get('@if').should('not.be.called')
119 | cy.log('**else was called before finally**')
120 | cy.get('@finally').then((fin) => {
121 | cy.get('@else').should('have.been.calledBefore', fin)
122 | })
123 | })
124 | })
125 |
--------------------------------------------------------------------------------
/cypress/e2e/find.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('cy.find', () => {
7 | it('takes the if branch', () => {
8 | cy.visit('cypress/checkbox.html')
9 | cy.get('#app1')
10 | .find('#enrolled')
11 | .if('exist')
12 | .then(cy.spy().as('if'))
13 | .log('checkbox found')
14 | .else()
15 | .log('checkbox not found')
16 | .then(cy.spy().as('else'))
17 | cy.get('@if').should('have.been.called')
18 | cy.get('@else').should('not.have.been.called')
19 | })
20 |
21 | it('takes the else branch', () => {
22 | cy.visit('cypress/checkbox.html')
23 | // the checkbox should not be checked
24 | cy.get('#enrolled').should('not.be.checked')
25 |
26 | cy.get('#app1')
27 | .find('#enrolled')
28 | .if('checked')
29 | .then(cy.spy().as('if'))
30 | .log('checkbox found')
31 | .else()
32 | .log('checkbox not found')
33 | .then(cy.spy().as('else'))
34 | cy.get('@else').should('have.been.called')
35 | cy.get('@if').should('not.have.been.called')
36 | })
37 |
38 | // https://github.com/bahmutov/cypress-if/issues/32
39 | it('checks if the element exists (it does not)', () => {
40 | cy.visit('cypress/checkbox.html')
41 | cy.get('#app1')
42 | .find('#doest-not-exist')
43 | .if('exist')
44 | .then(cy.spy().as('if'))
45 | .log('checkbox found')
46 | .else()
47 | .log('checkbox not found')
48 | .then(cy.spy().as('else'))
49 | cy.get('@else').should('have.been.called')
50 | cy.get('@if').should('not.have.been.called')
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/cypress/e2e/has-attribute.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('has attribute assertion', () => {
7 | beforeEach(() => {
8 | cy.visit('cypress/terms.html')
9 | })
10 |
11 | it('has attribute present', () => {
12 | cy.get('#submit')
13 | .if('have.attr', 'id')
14 | .log('button has an id')
15 | .else()
16 | .raise(new Error('button should have an id'))
17 | })
18 |
19 | it(
20 | 'has attribute present after delay',
21 | { defaultCommandTimeout: 2000 },
22 | () => {
23 | cy.get('#submit').should('have.attr', 'data-x')
24 | cy.get('#submit')
25 | .if('have.attr', 'data-x')
26 | .invoke('attr', 'data-x')
27 | .should('equal', '123')
28 | .else()
29 | .raise(new Error('data-x not found'))
30 | },
31 | )
32 |
33 | it(
34 | 'has attribute with matching value present after delay',
35 | { defaultCommandTimeout: 2000 },
36 | () => {
37 | cy.get('#submit').should('have.attr', 'data-x')
38 | cy.get('#submit')
39 | .if('have.attr', 'data-x', '123')
40 | .log('data-X found')
41 | .else()
42 | .raise(new Error('data-x not found'))
43 | },
44 | )
45 |
46 | it(
47 | 'has attribute with a different value',
48 | { defaultCommandTimeout: 2000 },
49 | () => {
50 | cy.get('#submit').should('have.attr', 'data-x')
51 | cy.get('#submit')
52 | // the attribute is present, but has a different value
53 | .if('have.attr', 'data-x', '99')
54 | .raise(new Error('data-x has wrong value'))
55 | .else('data-x value is correct')
56 | },
57 | )
58 | })
59 |
--------------------------------------------------------------------------------
/cypress/e2e/has-class.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('has class assertion', () => {
7 | beforeEach(() => {
8 | cy.visit('cypress/index.html')
9 | })
10 |
11 | it('has active class', () => {
12 | cy.get('#fruits').invoke('addClass', 'active')
13 | // check if the fruits element has class "active"
14 | cy.get('#fruits')
15 | .if('have.class', 'active')
16 | .log('has class active')
17 | .then(cy.spy().as('if'))
18 | .else()
19 | .then(cy.spy().as('else'))
20 | cy.get('@if').should('have.been.called')
21 | cy.get('@else').should('not.have.been.called')
22 | })
23 |
24 | it('has no active class', () => {
25 | cy.get('#fruits')
26 | .if('have.class', 'active')
27 | .log('has class active')
28 | .then(cy.spy().as('if'))
29 | .else()
30 | .then(cy.spy().as('else'))
31 | cy.get('@else').should('have.been.called')
32 | cy.get('@if').should('not.have.been.called')
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/cypress/e2e/if-should.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | it('checks that there are 3 items if they exist', () => {
7 | cy.visit('cypress/index.html')
8 | cy.get('#fruits li').if('exist').should('have.length', 3)
9 | })
10 |
11 | it('skips assertion if there are no items', () => {
12 | cy.visit('cypress/index.html')
13 | cy.get('#does-not-exist li', { timeout: 1000 })
14 | .if('exist')
15 | .should('have.length', 3)
16 | cy.log('all good')
17 | })
18 |
--------------------------------------------------------------------------------
/cypress/e2e/input-value.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('Input element', () => {
7 | it('types again if the value was corrupted', () => {
8 | cy.visit('cypress/funky-input.html')
9 | cy.get('#name')
10 | .type('Cypress', { delay: 20 })
11 | .if('not.have.value', 'Cypress')
12 | .clear()
13 | .type('Cypress')
14 | .else()
15 | .log('Input has expected value')
16 | .finally()
17 | .should('have.value', 'Cypress')
18 | })
19 |
20 | it('have.value positive', () => {
21 | cy.wrap(Cypress.$(' '))
22 | .if('have.value', 'foo')
23 | .then(cy.spy().as('if'))
24 | .else()
25 | .then(cy.spy().as('else'))
26 | cy.get('@if').should('be.calledOnce')
27 | cy.get('@else').should('not.be.called')
28 | })
29 |
30 | it('have.value negative', () => {
31 | cy.wrap(Cypress.$(' '))
32 | .if('have.value', 'bar')
33 | .then(cy.spy().as('if'))
34 | .else()
35 | .then(cy.spy().as('else'))
36 | cy.get('@else').should('be.calledOnce')
37 | cy.get('@if').should('not.be.called')
38 | })
39 |
40 | it('have.value positive', () => {
41 | cy.wrap(Cypress.$(' '))
42 | .if('have.value', 'foo')
43 | .then(cy.spy().as('if'))
44 | .else()
45 | .then(cy.spy().as('else'))
46 | cy.get('@if').should('be.calledOnce')
47 | cy.get('@else').should('not.be.called')
48 | })
49 |
50 | context('not.have.value', () => {
51 | it('positive', () => {
52 | cy.wrap(Cypress.$(' '))
53 | .if('not.have.value', 'foo')
54 | .then(cy.spy().as('if'))
55 | .else()
56 | .then(cy.spy().as('else'))
57 | cy.get('@else').should('be.calledOnce')
58 | cy.get('@if').should('not.be.called')
59 | })
60 |
61 | it('negative', () => {
62 | cy.wrap(Cypress.$(' '))
63 | .if('not.have.value', 'bar')
64 | .then(cy.spy().as('if'))
65 | .else()
66 | .then(cy.spy().as('else'))
67 | cy.get('@if').should('be.calledOnce')
68 | cy.get('@else').should('not.be.called')
69 | })
70 | })
71 | })
72 |
--------------------------------------------------------------------------------
/cypress/e2e/issue-59.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | // https://github.com/bahmutov/cypress-if/issues/59
7 |
8 | function bar() {
9 | return (
10 | cy
11 | .wrap('testing')
12 | .if()
13 | .then(() => cy.wrap('got it'))
14 | .else()
15 | .then(() => cy.wrap('else do'))
16 | // to correctly STOP the chaining if/else
17 | // from putting anything chained of bar()
18 | // need to add .finally() command
19 | .finally()
20 | )
21 | }
22 |
23 | it('stops the chaining', () => {
24 | bar().then((it) => {
25 | cy.log(`result: ${it}`)
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/cypress/e2e/more-than-n.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | it('clicks on the button that appears if there are more than 6 items', () => {
7 | cy.visit('cypress/list.html')
8 | // in 50% of the tests the page will show > 5 items
9 | // and will show the button "Load more"
10 | cy.get('#fruits li')
11 | .if('have.length.above', 5)
12 | .root()
13 | .contains('button', 'Load more')
14 | .click()
15 | .else()
16 | .log('Few items, no button')
17 | })
18 |
--------------------------------------------------------------------------------
/cypress/e2e/not.cy.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import '../../src'
4 |
5 | describe('Not', () => {
6 | it('not.equal', () => {
7 | cy.wrap(42).if('not.equal', 42).raise(new Error('should not be here'))
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/cypress/e2e/null.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | // https://github.com/bahmutov/cypress-if/issues/38
7 | describe('null value', () => {
8 | it('handles null assertion', () => {
9 | cy.wrap(null)
10 | .if('null')
11 | .log('null value')
12 | .then(cy.spy().as('if'))
13 | .else()
14 | .then(cy.spy().as('else'))
15 | cy.get('@if').should('have.been.called')
16 | cy.get('@else').should('not.have.been.called')
17 | })
18 |
19 | it('handles not.null assertion', () => {
20 | cy.wrap(null)
21 | .if('not.null')
22 | .log('null value')
23 | .then(cy.spy().as('if'))
24 | .else()
25 | .then(cy.spy().as('else'))
26 | cy.get('@else').should('have.been.called')
27 | cy.get('@if').should('not.have.been.called')
28 | })
29 |
30 | it('handles 42 with not.null assertion', () => {
31 | cy.wrap(42)
32 | .if('not.null')
33 | .log('null value')
34 | .then(cy.spy().as('if'))
35 | .else()
36 | .then(cy.spy().as('else'))
37 | cy.get('@if').should('have.been.called')
38 | cy.get('@else').should('not.have.been.called')
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/cypress/e2e/raise-error.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | it('raises an error if wrong number of elements', () => {
7 | // prevent ".raise" from failing the test
8 | Cypress.Commands.overwrite('raise', cy.stub().as('raise'))
9 |
10 | cy.visit('cypress/index.html')
11 | // we have 3 items
12 | cy.get('#fruits li').should('have.length', 3)
13 | // force an error
14 | cy.get('#fruits li')
15 | .if('have.length', 1)
16 | .then(cy.spy().as('if'))
17 | .log('right number of elements')
18 | .else()
19 | .log('too many elements')
20 | .then(cy.spy().as('else'))
21 | .raise('Too many elements')
22 | cy.get('@else').should('have.been.calledOnce')
23 | cy.get('@if').should('not.have.been.called')
24 | })
25 |
26 | it('raises an error if not the right number of elements', () => {
27 | // prevent ".raise" from failing the test
28 | Cypress.Commands.overwrite('raise', cy.stub().as('raise'))
29 |
30 | cy.visit('cypress/index.html')
31 | // we have 3 items
32 | cy.get('#fruits li').should('have.length', 3)
33 | // force an error
34 | cy.get('#fruits li')
35 | .if('not.have.length', 1)
36 | .log('wrong number of items')
37 | .then(cy.spy().as('else'))
38 | .raise('Too many elements')
39 | cy.get('@else').should('have.been.calledOnce')
40 | })
41 |
42 | it('raises an error instance', () => {
43 | // prevent ".raise" from failing the test
44 | Cypress.Commands.overwrite('raise', cy.stub().as('raise'))
45 |
46 | cy.visit('cypress/index.html')
47 | // we have 3 items
48 | cy.get('#fruits li').should('have.length', 3)
49 | // force an error
50 | cy.get('#fruits li')
51 | .if('not.have.length', 1)
52 | .log('wrong number of items')
53 | .then(cy.spy().as('else'))
54 | // when using an Error instance (and not a string)
55 | // the error stack will point at this spec location
56 | .raise(new Error('Too many elements'))
57 | cy.get('@else').should('have.been.calledOnce')
58 | })
59 |
60 | it.skip('raises an error if element does not exist', () => {
61 | // prevent ".raise" from failing the test
62 | Cypress.Commands.overwrite('raise', cy.stub().as('raise'))
63 |
64 | cy.visit('cypress/index.html')
65 | // force an error
66 | cy.get('#does-not-exist')
67 | .if('not.exist')
68 | .log('no such element')
69 | .then(cy.spy().as('else'))
70 | .raise('Cannot find it')
71 | cy.get('@else').should('have.been.calledOnce')
72 | })
73 |
--------------------------------------------------------------------------------
/cypress/e2e/spec.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | it('finds the li elements', () => {
7 | cy.visit('cypress/index.html')
8 | // if the list exists, it should have three items
9 | cy.get('#fruits li').if().should('have.length', 3)
10 | // if the list exists, it should have three items
11 | cy.get('#veggies li').if().should('have.length', 3)
12 | // if the button exists, it should have certain text
13 | // and then we click on it
14 | cy.get('button#load').if().should('have.text', 'Load').click()
15 | // if the button exists, click on it
16 | cy.get('button#does-not-exist').if().click()
17 | })
18 |
19 | it('clicks on the button if it is visible', () => {
20 | cy.visit('cypress/index.html')
21 | cy.get('button#hidden').if('visible').click()
22 | // but we can click on the visible button
23 | cy.get('button#load').if('visible').click()
24 | })
25 |
26 | it('works if nothing is attached', () => {
27 | cy.wrap(1).if('equal', 1)
28 | cy.wrap(1)
29 | .if('not.equal', 1)
30 | .then((/** @type number */ subject) => {
31 | expect(subject).to.not.equal(1)
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/cypress/e2e/task.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('cy.task support', () => {
7 | before(() => {
8 | // confirm the task yields 42
9 | cy.task('get42').should('equal', 42)
10 | })
11 |
12 | it('runs if branch after cy.task', () => {
13 | cy.task('get42')
14 | .if('equals', 42)
15 | .then(cy.spy().as('if'))
16 | .else()
17 | .then(cy.spy().as('else'))
18 | cy.get('@if').should('have.been.called')
19 | cy.get('@else').should('not.have.been.called')
20 | })
21 |
22 | it('runs else branch after cy.task', () => {
23 | cy.task('get42')
24 | .if('equals', 99)
25 | .then(cy.spy().as('if'))
26 | .else()
27 | .then(cy.spy().as('else'))
28 | cy.get('@else').should('have.been.called')
29 | cy.get('@if').should('not.have.been.called')
30 | })
31 |
32 | it('throws an error', () => {
33 | cy.task('throws')
34 | .if('failed')
35 | .then(cy.spy().as('if'))
36 | .log('cy.task has failed')
37 | .else()
38 | .then(cy.spy().as('else'))
39 | cy.get('@if').should('have.been.called')
40 | cy.get('@else').should('not.have.been.called')
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/cypress/e2e/terms-and-conditions.cy.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import '../../src'
4 |
5 | it('submits the terms forms', () => {
6 | cy.visit('cypress/terms.html')
7 | cy.get('#agreed')
8 | cy.get('#agreed')
9 | .should('be.visible')
10 | .if('not.checked')
11 | .click()
12 | .log('clicked the checkbox')
13 | .else()
14 | .log('The user already agreed')
15 | cy.get('button#submit').click()
16 | })
17 |
18 | it('submits the terms forms using cy.then', () => {
19 | cy.visit('cypress/terms.html')
20 | cy.get('#agreed').then(($input) => {
21 | if ($input.is(':checked')) {
22 | cy.log('The user already agreed')
23 | } else {
24 | cy.wrap($input).click()
25 | }
26 | })
27 | cy.get('button#submit').click()
28 | })
29 |
--------------------------------------------------------------------------------
/cypress/e2e/traversal.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | it('traversals by chain id', () => {
7 | cy.visit('cypress/index.html')
8 | cy.get('#does-not-exist', { timeout: 2000 })
9 | .if()
10 | .log('hmm') // should be skipped
11 | .get('#does-not-exist') // should be skipped
12 | .should('exist') // should be skipped
13 | .then(() => {
14 | throw new Error('Hmm, did not skip me')
15 | })
16 | .else()
17 | .log('does not exist')
18 | })
19 |
--------------------------------------------------------------------------------
/cypress/e2e/url.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('cy.url support', () => {
7 | it('checks if url contains a give string (it does)', () => {
8 | cy.visit('cypress/index.html')
9 | cy.url().should('include', 'index.html').and('include', 'localhost')
10 | cy.url()
11 | .if('includes', 'index.html')
12 | .log('includes index.html')
13 | .then(cy.spy().as('if'))
14 | .else()
15 | .then(cy.spy().as('else'))
16 | cy.get('@if').should('have.been.called')
17 | cy.get('@else').should('not.have.been.called')
18 | })
19 |
20 | it('url does not contain a string', () => {
21 | cy.visit('cypress/index.html')
22 | cy.url()
23 | .if('includes', 'acme.co')
24 | .log('running on production')
25 | .then(cy.spy().as('if'))
26 | .else()
27 | .log('running NOT on production')
28 | .then(cy.spy().as('else'))
29 | cy.get('@else').should('have.been.called')
30 | cy.get('@if').should('not.have.been.called')
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/cypress/e2e/wrap.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | // @ts-check
3 |
4 | import '../../src'
5 |
6 | describe('wrapped value', () => {
7 | it('performs an action if the wrapped value is equal to 42', () => {
8 | cy.wrap(42).if('equal', 42).then(cy.spy().as('action')).then(cy.log)
9 | cy.get('@action').should('have.been.calledOnce')
10 | })
11 |
12 | it('does nothing if it is not 42', () => {
13 | cy.wrap(1).if('equal', 42).then(cy.spy().as('action')).then(cy.log)
14 | cy.get('@action').should('not.have.been.called')
15 | })
16 |
17 | context('.else', () => {
18 | it('passes the subject to the else branch', () => {
19 | cy.wrap(1).if('equal', 42).log('if branch').else().should('equal', 1)
20 | })
21 |
22 | it('passes the subject if().else()', () => {
23 | cy.wrap(1).if('equal', 42).else().should('equal', 1)
24 | })
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/cypress/enabled-button.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | The button might be disabled
6 | Click Me
7 |
8 |
9 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/cypress/funky-input.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Your name?
4 |
10 |
11 |
--------------------------------------------------------------------------------
/cypress/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 | Apples
15 | Grapes
16 | Kiwi
17 |
18 | Load
19 | Cannot see me
20 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/cypress/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Apples
5 | Grapes
6 | Kiwi
7 |
8 | Load more
9 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/cypress/terms.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 | I agree to the terms & conditions.
13 |
14 | Submit
15 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/img/debug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bahmutov/cypress-if/26ac2680e308d2c577b2d0373d21dcbb7560f7cb/img/debug.png
--------------------------------------------------------------------------------
/img/dialog-closed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bahmutov/cypress-if/26ac2680e308d2c577b2d0373d21dcbb7560f7cb/img/dialog-closed.png
--------------------------------------------------------------------------------
/img/dialog-open.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bahmutov/cypress-if/26ac2680e308d2c577b2d0373d21dcbb7560f7cb/img/dialog-open.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cypress-if",
3 | "version": "0.0.0-development",
4 | "description": "Easy conditional if-else logic for your Cypress tests",
5 | "main": "src/index.js",
6 | "types": "src/index.d.ts",
7 | "scripts": {
8 | "test": "cypress run",
9 | "badges": "npx -p dependency-version-badge update-badge cypress",
10 | "semantic-release": "semantic-release",
11 | "types": "tsc"
12 | },
13 | "files": [
14 | "src"
15 | ],
16 | "keywords": [
17 | "cypress-plugin"
18 | ],
19 | "author": "Gleb Bahmutov ",
20 | "license": "MIT",
21 | "devDependencies": {
22 | "cypress": "14.3.0",
23 | "prettier": "3.2.5",
24 | "semantic-release": "^19.0.5",
25 | "typescript": "^5.5.2"
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/bahmutov/cypress-if.git"
30 | },
31 | "dependencies": {
32 | "debug": "^4.3.4"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | "automerge": true,
4 | "major": {
5 | "automerge": false
6 | },
7 | "minor": {
8 | "automerge": true
9 | },
10 | "prConcurrentLimit": 3,
11 | "prHourlyLimit": 2,
12 | "schedule": ["after 10pm and before 5am on every weekday", "every weekend"],
13 | "masterIssue": true,
14 | "labels": ["type: dependencies", "renovate"],
15 | "packageRules": [
16 | {
17 | "packagePatterns": ["*"],
18 | "excludePackagePatterns": ["cypress", "debug"],
19 | "enabled": false
20 | }
21 | ],
22 | "ignorePaths": ["**/node_modules/**", "cypress-v9/**"]
23 | }
24 |
--------------------------------------------------------------------------------
/src/index-v11.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('cypress-if')
2 |
3 | const isIfCommand = (cmd) =>
4 | cmd && cmd.attributes && cmd.attributes.name === 'if'
5 |
6 | const skipCommand = (cmd) => {
7 | cmd.attributes.skip = true
8 | cmd.state = 'skipped'
9 | }
10 |
11 | function skipRestOfTheChain(cmd, chainerId) {
12 | while (
13 | cmd &&
14 | cmd.attributes.chainerId === chainerId &&
15 | cmd.attributes.name !== 'finally'
16 | ) {
17 | debug('skipping "%s"', cmd.attributes.name)
18 | skipCommand(cmd)
19 | cmd = cmd.attributes.next
20 | }
21 | }
22 |
23 | function findMyIfSubject(elseCommandAttributes) {
24 | if (!elseCommandAttributes) {
25 | return
26 | }
27 | if (elseCommandAttributes.name === 'if') {
28 | return elseCommandAttributes.ifSubject
29 | }
30 | if (
31 | !elseCommandAttributes.skip &&
32 | !Cypress._.isNil(elseCommandAttributes.subject)
33 | ) {
34 | return elseCommandAttributes.subject
35 | }
36 | if (elseCommandAttributes.prev) {
37 | return findMyIfSubject(elseCommandAttributes.prev.attributes)
38 | }
39 | }
40 |
41 | function getCypressCurrentSubject() {
42 | if (typeof cy.currentSubject === 'function') {
43 | return cy.currentSubject()
44 | }
45 | // fallback for Cypress v9 and some early v10 versions
46 | return cy.state('subject')
47 | }
48 |
49 | // cy.if command
50 | Cypress.Commands.add(
51 | 'if',
52 | { prevSubject: true },
53 | function (subject, assertion, assertionValue1, assertionValue2) {
54 | const cmd = cy.state('current')
55 | debug('if', cmd.attributes, 'subject', subject, 'assertion?', assertion)
56 | debug('next command', cmd.next)
57 | debug('if() current subject', getCypressCurrentSubject())
58 | // console.log('subjects', cy.state('subjects'))
59 | // keep the subject, if there is an "else" branch
60 | // it can look it up to use
61 | cmd.attributes.ifSubject = subject
62 |
63 | // let's be friendly and if the user
64 | // wrote "if exists" just go with it
65 | if (assertion === 'exists') {
66 | assertion = 'exist'
67 | }
68 |
69 | let hasSubject = Boolean(subject)
70 | let assertionsPassed = true
71 |
72 | const evaluateAssertion = () => {
73 | try {
74 | if (Cypress._.isFunction(assertion)) {
75 | const result = assertion(subject)
76 | if (Cypress._.isBoolean(result)) {
77 | // function was a predicate
78 | if (!result) {
79 | throw new Error('Predicate function failed')
80 | }
81 | }
82 | } else if (
83 | assertion.startsWith('not') ||
84 | assertion.startsWith('have')
85 | ) {
86 | const parts = assertion.split('.')
87 | let assertionReduced = expect(subject).to
88 | parts.forEach((assertionPart, k) => {
89 | if (k === parts.length - 1) {
90 | if (
91 | typeof assertionValue1 !== 'undefined' &&
92 | typeof assertionValue2 !== 'undefined'
93 | ) {
94 | assertionReduced = assertionReduced[assertionPart](
95 | assertionValue1,
96 | assertionValue2,
97 | )
98 | } else if (typeof assertionValue1 !== 'undefined') {
99 | assertionReduced =
100 | assertionReduced[assertionPart](assertionValue1)
101 | } else {
102 | assertionReduced = assertionReduced[assertionPart]
103 | }
104 | } else {
105 | assertionReduced = assertionReduced[assertionPart]
106 | }
107 | })
108 | } else {
109 | if (
110 | typeof assertionValue1 !== 'undefined' &&
111 | typeof assertionValue2 !== 'undefined'
112 | ) {
113 | expect(subject).to.be[assertion](assertionValue1, assertionValue2)
114 | } else if (typeof assertionValue1 !== 'undefined') {
115 | expect(subject).to.be[assertion](assertionValue1)
116 | } else {
117 | expect(subject).to.be[assertion]
118 | }
119 | }
120 | } catch (e) {
121 | console.error(e)
122 | assertionsPassed = false
123 | if (e.message.includes('Invalid Chai property')) {
124 | throw e
125 | }
126 | }
127 | }
128 |
129 | // check if the previous command was cy.task
130 | // and it has failed and it was expected
131 | if (
132 | assertion === 'failed' &&
133 | Cypress._.get(cmd, 'attributes.prev.attributes.name') === 'task' &&
134 | Cypress._.isError(Cypress._.get(cmd, 'attributes.prev.attributes.error'))
135 | ) {
136 | debug('cy.task has failed and it was expected')
137 | // set the subject and the assertions to take the IF branch
138 | hasSubject = Cypress._.get(cmd, 'attributes.prev.attributes.error')
139 | } else {
140 | if (subject === null) {
141 | if (assertion === 'null') {
142 | hasSubject = true
143 | assertionsPassed = true
144 | } else if (assertion === 'not.null') {
145 | hasSubject = true
146 | assertionsPassed = false
147 | }
148 | } else if (hasSubject && assertion) {
149 | evaluateAssertion()
150 | } else if (subject === undefined && assertion) {
151 | evaluateAssertion()
152 | hasSubject = true
153 | }
154 | }
155 |
156 | const chainerId = cmd.attributes.chainerId
157 | if (!chainerId) {
158 | throw new Error('Command is missing chainer id')
159 | }
160 |
161 | if (!hasSubject || !assertionsPassed) {
162 | let nextCommand = cmd.attributes.next
163 | while (nextCommand && nextCommand.attributes.chainerId === chainerId) {
164 | debug(
165 | 'skipping the next "%s" command "%s"',
166 | nextCommand.attributes.type,
167 | nextCommand.attributes.name,
168 | )
169 | // cy.log(`**skipping ${cmd.attributes.next.attributes.name}**`)
170 | if (nextCommand.attributes.name === 'else') {
171 | debug('else branch starts right away')
172 | nextCommand = null
173 | } else {
174 | debug('am skipping "%s"', nextCommand.attributes.name)
175 | debug(nextCommand.attributes)
176 | skipCommand(nextCommand)
177 |
178 | nextCommand = nextCommand.attributes.next
179 | if (nextCommand && nextCommand.attributes.name === 'else') {
180 | debug('stop skipping command on "else" command')
181 | nextCommand = null
182 | }
183 | }
184 | }
185 |
186 | if (subject) {
187 | debug('wrapping subject', subject)
188 | cy.wrap(subject, { log: false })
189 | }
190 | return
191 | } else {
192 | // skip possible "else" branch
193 | debug('skipping a possible "else" branch')
194 | let nextCommand = cmd.attributes.next
195 | while (nextCommand && nextCommand.attributes.chainerId === chainerId) {
196 | debug(
197 | 'next command "%s" type "%s"',
198 | nextCommand.attributes.name,
199 | nextCommand.attributes.type,
200 | nextCommand.attributes,
201 | )
202 |
203 | if (nextCommand.attributes.name === 'else') {
204 | // found the "else" command, start skipping
205 | debug('found the "else" branch command start')
206 | skipRestOfTheChain(nextCommand, chainerId)
207 | nextCommand = null
208 | } else {
209 | nextCommand = nextCommand.attributes.next
210 | }
211 | }
212 | }
213 | return subject
214 | },
215 | )
216 |
217 | Cypress.Commands.add('else', { prevSubject: true }, (subject, text) => {
218 | debug('else command, subject', subject)
219 | if (typeof subject === 'undefined') {
220 | // find the subject from the "if()" before
221 | subject = findMyIfSubject(cy.state('current').attributes)
222 | }
223 | if (typeof text !== 'undefined') {
224 | cy.log(text)
225 | }
226 | if (subject) {
227 | cy.wrap(subject, { log: false })
228 | }
229 | })
230 |
231 | Cypress.Commands.add('finally', { prevSubject: true }, (subject) => {
232 | debug('finally with the subject', subject)
233 |
234 | // notice: cy.log yields "null" 🤯
235 | // https://github.com/cypress-io/cypress/issues/23400
236 | if (typeof subject === 'undefined' || subject === null) {
237 | // find the subject from the "if()" before
238 | const currentCommand = cy.state('current').attributes
239 | debug('current command is finally', currentCommand)
240 | subject = findMyIfSubject(currentCommand)
241 | debug('found subject', subject)
242 | }
243 | if (subject) {
244 | cy.wrap(subject, { log: false })
245 | }
246 | })
247 |
248 | Cypress.Commands.overwrite('get', function (get, selector, options) {
249 | // can we see the next command already?
250 | const cmd = cy.state('current')
251 | debug(cmd)
252 | const next = cmd.attributes.next
253 |
254 | if (isIfCommand(next)) {
255 | if (selector.startsWith('@')) {
256 | try {
257 | return get(selector, options)
258 | } catch (e) {
259 | if (e.message.includes('could not find a registered alias for')) {
260 | return undefined
261 | }
262 | }
263 | }
264 | // disable the built-in assertion
265 | return get(selector, options).then(
266 | (getResult) => {
267 | debug('internal get result', getResult)
268 | return getResult
269 | },
270 | (noResult) => {
271 | debug('no get result', noResult)
272 | },
273 | )
274 | }
275 |
276 | return get(selector, options)
277 | })
278 |
279 | Cypress.Commands.overwrite(
280 | 'contains',
281 | function (contains, prevSubject, selector, text, options) {
282 | debug('cy.contains arguments number', arguments.length)
283 | if (arguments.length === 3) {
284 | text = selector
285 | selector = undefined
286 | }
287 | debug('cy.contains args', { prevSubject, selector, text, options })
288 |
289 | const cmd = cy.state('current')
290 | debug(cmd)
291 | const next = cmd.attributes.next
292 |
293 | if (next && next.attributes.name === 'if') {
294 | // disable the built-in assertion
295 | return contains(prevSubject, selector, text, options).then(
296 | (getResult) => {
297 | debug('internal contains result', getResult)
298 | return getResult
299 | },
300 | (noResult) => {
301 | debug('no contains result', noResult)
302 | },
303 | )
304 | }
305 |
306 | return contains(prevSubject, selector, text, options)
307 | },
308 | )
309 |
310 | Cypress.Commands.overwrite('not', function (notCommand, prevSubject, selector) {
311 | debug('cy.not args', { prevSubject, selector })
312 |
313 | const cmd = cy.state('current')
314 | debug(cmd)
315 | const next = cmd.attributes.next
316 |
317 | if (next && next.attributes.name === 'if') {
318 | // disable the built-in assertion
319 | return notCommand(prevSubject, selector).then(
320 | (getResult) => {
321 | debug('internal cy.not result', getResult)
322 | return getResult
323 | },
324 | (noResult) => {
325 | debug('no cy.not result', noResult)
326 | },
327 | )
328 | }
329 |
330 | return notCommand(prevSubject, selector, text, options)
331 | })
332 |
333 | Cypress.Commands.overwrite(
334 | 'find',
335 | function (find, prevSubject, selector, options) {
336 | debug('cy.find args', { prevSubject, selector, options })
337 |
338 | const cmd = cy.state('current')
339 | debug(cmd)
340 | const next = cmd.attributes.next
341 |
342 | if (next && next.attributes.name === 'if') {
343 | // disable the built-in assertion
344 | return find(prevSubject, selector, options).then(
345 | (getResult) => {
346 | debug('internal cy.find result', getResult)
347 | return getResult
348 | },
349 | (noResult) => {
350 | debug('no cy.find result', noResult)
351 | },
352 | )
353 | }
354 |
355 | return find(prevSubject, selector, options)
356 | },
357 | )
358 |
359 | Cypress.Commands.overwrite('task', function (task, args, options) {
360 | debug('cy.task %o', { args, options })
361 |
362 | const cmd = cy.state('current')
363 | if (cmd) {
364 | debug(cmd)
365 | const next = cmd.attributes.next
366 |
367 | if (next && next.attributes.name === 'if') {
368 | // disable the built-in assertion
369 | return task(args, options).then(
370 | (taskResult) => {
371 | debug('internal task result', taskResult)
372 | return taskResult
373 | },
374 | (error) => {
375 | debug('task error', error)
376 | cmd.attributes.error = error
377 | },
378 | )
379 | }
380 | }
381 |
382 | return task(args, options)
383 | })
384 |
385 | Cypress.Commands.add('raise', (x) => {
386 | if (Cypress._.isError(x)) {
387 | throw x
388 | }
389 | const e = new Error(
390 | String(x) +
391 | '\n' +
392 | 'cypress-if tip: pass an error instance to have correct stack',
393 | )
394 | throw e
395 | })
396 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A function that returns true if the value is good for "if" branch
3 | * No Cypress commands allowed.
4 | */
5 | type PredicateFn = (x: any) => boolean
6 |
7 | /**
8 | * A function that uses Chai assertions inside.
9 | * No Cypress commands allowed.
10 | */
11 | type AssertionFn = (x: any) => void
12 |
13 | declare namespace Cypress {
14 | interface Chainable {
15 | /**
16 | * Child `.if()` command to start an optional chain
17 | * depending on the subject
18 | * @param assertion Chai assertion (optional, existence by default)
19 | * @param value Assertion value
20 | * @example
21 | * cy.get('#close').if('visible').click()
22 | * cy.wrap(1).if('equal', 1).should('equal', 1)
23 | */
24 | if(
25 | this: Chainable,
26 | assertion?: string,
27 | value1?: any,
28 | value2?: any,
29 | ): Chainable
30 |
31 | /**
32 | * Child `.if()` command to start an optional chain
33 | * depending on the subject
34 | * @param callback Predicate function (returning a Boolean value)
35 | * @example
36 | * cy.wrap(1).if(n => n % 2 === 0)...
37 | */
38 | if(this: Chainable, callback: PredicateFn): Chainable
39 |
40 | /**
41 | * Child `.if()` command to start an optional chain
42 | * depending on the subject
43 | * @param callback Function with Chai assertions
44 | * @example
45 | * cy.wrap(1).if(n => expect(n).to.equal(1))...
46 | */
47 | if(this: Chainable, callback: AssertionFn): Chainable
48 |
49 | /**
50 | * Creates new chain of commands that only
51 | * execute if the previous `.if()` command skipped
52 | * the "IF" branch. Note: `.if()` passes its subject
53 | * to the `.else()`
54 | * You can also print a message if the ELSE branch
55 | * is taken
56 | * @param message Message to print to the console. Optional.
57 | * @example
58 | * cy.get('checkox#agree')
59 | * .if('checked').log('Already agreed')
60 | * .else().check()
61 | * @example
62 | * cy.get('...')
63 | * .if('not.visible').log('Not visible')
64 | * .else('visible')
65 | */
66 | else(this: Chainable, message?: any): Chainable
67 |
68 | /**
69 | * Finishes if/else commands and continues
70 | * with the subject yielded by the original command
71 | * or if/else path taken
72 | * @example
73 | * cy.get('checkbox')
74 | * .if('not.checked').check()
75 | * .else().log('already checked')
76 | * .finally().should('be.checked')
77 | */
78 | finally(this: Chainable): Chainable
79 |
80 | /**
81 | * A simple way to throw an error
82 | * @example
83 | * cy.get('li')
84 | * .if('not.have.length', 3)
85 | * .raise('wrong number of todo items')
86 | */
87 | raise(x: string | Error): Chainable
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('cypress-if')
2 |
3 | const [major] = Cypress.version.split('.')
4 | if (major < 12) {
5 | require('./index-v11')
6 | } else {
7 | const isIfCommand = (cmd) =>
8 | cmd && cmd.attributes && cmd.attributes.name === 'if'
9 |
10 | const skipCommand = (cmd) => {
11 | cmd.attributes.skip = true
12 | cmd.state = 'skipped'
13 | }
14 |
15 | function skipRestOfTheChain(cmd, chainerId) {
16 | while (
17 | cmd &&
18 | cmd.attributes.chainerId === chainerId &&
19 | cmd.attributes.name !== 'finally'
20 | ) {
21 | debug('skipping "%s"', cmd.attributes.name)
22 | skipCommand(cmd)
23 | cmd = cmd.attributes.next
24 | }
25 | }
26 |
27 | function findMyIfSubject(elseCommandAttributes) {
28 | if (!elseCommandAttributes) {
29 | return
30 | }
31 | if (elseCommandAttributes.name === 'if') {
32 | return elseCommandAttributes.ifSubject
33 | }
34 | if (
35 | !elseCommandAttributes.skip &&
36 | !Cypress._.isNil(elseCommandAttributes.subject)
37 | ) {
38 | return elseCommandAttributes.subject
39 | }
40 | if (elseCommandAttributes.prev) {
41 | return findMyIfSubject(elseCommandAttributes.prev.attributes)
42 | }
43 | }
44 |
45 | function getCypressCurrentSubject() {
46 | if (typeof cy.currentSubject === 'function') {
47 | return cy.currentSubject()
48 | }
49 | // fallback for Cypress v9 and some early v10 versions
50 | return cy.state('subject')
51 | }
52 |
53 | // cy.if command
54 | Cypress.Commands.add(
55 | 'if',
56 | { prevSubject: true },
57 | function (subject, assertion, assertionValue1, assertionValue2) {
58 | const cmd = cy.state('current')
59 | debug('if', cmd.attributes, 'subject', subject, 'assertion?', assertion)
60 | debug('next command', cmd.next)
61 | debug('if() current subject', getCypressCurrentSubject())
62 | // console.log('subjects', cy.state('subjects'))
63 | // keep the subject, if there is an "else" branch
64 | // it can look it up to use
65 | cmd.attributes.ifSubject = subject
66 |
67 | // let's be friendly and if the user
68 | // wrote "if exists" just go with it
69 | if (assertion === 'exists') {
70 | assertion = 'exist'
71 | }
72 |
73 | let hasSubject = Boolean(subject)
74 | let assertionsPassed = true
75 |
76 | const evaluateAssertion = () => {
77 | try {
78 | if (Cypress._.isFunction(assertion)) {
79 | const result = assertion(subject)
80 | if (Cypress._.isBoolean(result)) {
81 | // function was a predicate
82 | if (!result) {
83 | throw new Error('Predicate function failed')
84 | }
85 | }
86 | } else if (
87 | assertion.startsWith('not') ||
88 | assertion.startsWith('have')
89 | ) {
90 | const parts = assertion.split('.')
91 | let assertionReduced = expect(subject).to
92 | parts.forEach((assertionPart, k) => {
93 | if (k === parts.length - 1) {
94 | if (
95 | typeof assertionValue1 !== 'undefined' &&
96 | typeof assertionValue2 !== 'undefined'
97 | ) {
98 | assertionReduced = assertionReduced[assertionPart](
99 | assertionValue1,
100 | assertionValue2,
101 | )
102 | } else if (typeof assertionValue1 !== 'undefined') {
103 | assertionReduced =
104 | assertionReduced[assertionPart](assertionValue1)
105 | } else {
106 | assertionReduced = assertionReduced[assertionPart]
107 | }
108 | } else {
109 | assertionReduced = assertionReduced[assertionPart]
110 | }
111 | })
112 | } else {
113 | if (
114 | typeof assertionValue1 !== 'undefined' &&
115 | typeof assertionValue2 !== 'undefined'
116 | ) {
117 | expect(subject).to.be[assertion](assertionValue1, assertionValue2)
118 | } else if (typeof assertionValue1 !== 'undefined') {
119 | expect(subject).to.be[assertion](assertionValue1)
120 | } else {
121 | expect(subject).to.be[assertion]
122 | }
123 | }
124 | } catch (e) {
125 | console.error(e)
126 | assertionsPassed = false
127 | if (e.message.includes('Invalid Chai property')) {
128 | throw e
129 | }
130 | }
131 | }
132 |
133 | // check if the previous command was cy.task
134 | // and it has failed and it was expected
135 | if (
136 | assertion === 'failed' &&
137 | Cypress._.get(cmd, 'attributes.prev.attributes.name') === 'task' &&
138 | Cypress._.isError(
139 | Cypress._.get(cmd, 'attributes.prev.attributes.error'),
140 | )
141 | ) {
142 | debug('cy.task has failed and it was expected')
143 | // set the subject and the assertions to take the IF branch
144 | hasSubject = Cypress._.get(cmd, 'attributes.prev.attributes.error')
145 | } else {
146 | if (subject === null) {
147 | if (assertion === 'null') {
148 | hasSubject = true
149 | assertionsPassed = true
150 | } else if (assertion === 'not.null') {
151 | hasSubject = true
152 | assertionsPassed = false
153 | }
154 | } else if (hasSubject && assertion) {
155 | evaluateAssertion()
156 | } else if (subject === undefined && assertion) {
157 | evaluateAssertion()
158 | hasSubject = true
159 | }
160 | }
161 |
162 | const chainerId = cmd.attributes.chainerId
163 | if (!chainerId) {
164 | throw new Error('Command is missing chainer id')
165 | }
166 |
167 | if (!hasSubject || !assertionsPassed) {
168 | let nextCommand = cmd.attributes.next
169 | while (nextCommand && nextCommand.attributes.chainerId === chainerId) {
170 | debug(
171 | 'skipping the next "%s" command "%s"',
172 | nextCommand.attributes.type,
173 | nextCommand.attributes.name,
174 | )
175 | // cy.log(`**skipping ${cmd.attributes.next.attributes.name}**`)
176 | if (nextCommand.attributes.name === 'else') {
177 | debug('else branch starts right away')
178 | nextCommand = null
179 | } else {
180 | debug('am skipping "%s"', nextCommand.attributes.name)
181 | debug(nextCommand.attributes)
182 | skipCommand(nextCommand)
183 |
184 | nextCommand = nextCommand.attributes.next
185 | if (nextCommand && nextCommand.attributes.name === 'else') {
186 | debug('stop skipping command on "else" command')
187 | nextCommand = null
188 | }
189 | }
190 | }
191 |
192 | if (subject) {
193 | debug('wrapping subject', subject)
194 | cy.wrap(subject, { log: false })
195 | }
196 | return
197 | } else {
198 | // skip possible "else" branch
199 | debug('skipping a possible "else" branch')
200 | let nextCommand = cmd.attributes.next
201 | while (nextCommand && nextCommand.attributes.chainerId === chainerId) {
202 | debug(
203 | 'next command "%s" type "%s"',
204 | nextCommand.attributes.name,
205 | nextCommand.attributes.type,
206 | nextCommand.attributes,
207 | )
208 |
209 | if (nextCommand.attributes.name === 'else') {
210 | // found the "else" command, start skipping
211 | debug('found the "else" branch command start')
212 | skipRestOfTheChain(nextCommand, chainerId)
213 | nextCommand = null
214 | } else {
215 | nextCommand = nextCommand.attributes.next
216 | }
217 | }
218 | }
219 | return subject
220 | },
221 | )
222 |
223 | Cypress.Commands.add('else', { prevSubject: true }, (subject, text) => {
224 | debug('else command, subject', subject)
225 | if (typeof subject === 'undefined') {
226 | // find the subject from the "if()" before
227 | subject = findMyIfSubject(cy.state('current').attributes)
228 | }
229 | if (typeof text !== 'undefined') {
230 | cy.log(text)
231 | } else {
232 | debug('nothing to log for else branch')
233 | }
234 | if (subject) {
235 | cy.wrap(subject, { log: false })
236 | }
237 | })
238 |
239 | Cypress.Commands.add('finally', { prevSubject: true }, (subject) => {
240 | debug('finally with the subject', subject)
241 |
242 | // notice: cy.log yields "null" 🤯
243 | // https://github.com/cypress-io/cypress/issues/23400
244 | if (typeof subject === 'undefined' || subject === null) {
245 | // find the subject from the "if()" before
246 | const currentCommand = cy.state('current').attributes
247 | debug('current command is finally', currentCommand)
248 | subject = findMyIfSubject(currentCommand)
249 | debug('found subject', subject)
250 | }
251 | if (subject) {
252 | cy.wrap(subject, { log: false })
253 | }
254 | })
255 |
256 | Cypress.Commands.overwriteQuery('get', function (get, selector, options) {
257 | // can we see the next command already?
258 | const cmd = cy.state('current')
259 | debug(cmd)
260 | const next = cmd.attributes.next
261 |
262 | const innerFn = get.call(this, selector, options)
263 |
264 | if (isIfCommand(next)) {
265 | if (selector.startsWith('@')) {
266 | return (subject) => {
267 | try {
268 | return innerFn(subject)
269 | } catch (e) {
270 | if (e.message.includes('could not find a registered alias for')) {
271 | return undefined
272 | }
273 | }
274 | }
275 | }
276 | // disable the built-in assertion
277 | return (subject) => {
278 | const res = innerFn(subject)
279 | if (res && res.length) {
280 | debug('internal get result', res)
281 | return res
282 | }
283 | debug('no get result')
284 | }
285 | }
286 |
287 | return (subject) => innerFn(subject)
288 | })
289 |
290 | Cypress.Commands.overwriteQuery(
291 | 'contains',
292 | function (contains, selector, text, options) {
293 | debug('cy.contains arguments number', arguments.length)
294 | if (arguments.length === 2) {
295 | text = selector
296 | selector = undefined
297 | }
298 | debug('cy.contains args', { selector, text, options })
299 |
300 | const cmd = cy.state('current')
301 | debug(cmd)
302 | const next = cmd.attributes.next
303 | const innerFn = contains.call(this, selector, text, options)
304 |
305 | if (isIfCommand(next)) {
306 | // disable the built-in assertion
307 | return (subject) => {
308 | const res = innerFn(subject)
309 | if (res && res.length) {
310 | debug('internal contains result', res)
311 | return res
312 | }
313 | debug('no contains result')
314 | }
315 | }
316 |
317 | return (subject) => innerFn(subject)
318 | },
319 | )
320 |
321 | Cypress.Commands.overwriteQuery('find', function (find, selector, options) {
322 | debug('cy.find args', { selector, options })
323 |
324 | const cmd = cy.state('current')
325 | debug(cmd)
326 | const next = cmd.attributes.next
327 | const innerFn = find.call(this, selector, options)
328 |
329 | if (isIfCommand(next)) {
330 | // disable the built-in assertion
331 | return (subject) => {
332 | const res = innerFn(subject)
333 | if (res && res.length) {
334 | debug('internal cy.find result', res)
335 | return res
336 | }
337 | debug('no cy.find result')
338 | }
339 | }
340 |
341 | return (subject) => innerFn(subject)
342 | })
343 |
344 | Cypress.Commands.overwrite('task', function (task, args, options) {
345 | debug('cy.task %o', { args, options })
346 |
347 | const cmd = cy.state('current')
348 | if (cmd) {
349 | debug(cmd)
350 | const next = cmd.attributes.next
351 |
352 | if (isIfCommand(next)) {
353 | // disable the built-in assertion
354 | return task(args, options).then(
355 | (taskResult) => {
356 | debug('internal task result', taskResult)
357 | return taskResult
358 | },
359 | (error) => {
360 | debug('task error', error)
361 | cmd.attributes.error = error
362 | },
363 | )
364 | }
365 | }
366 |
367 | return task(args, options)
368 | })
369 |
370 | Cypress.Commands.add('raise', (x) => {
371 | if (Cypress._.isError(x)) {
372 | throw x
373 | }
374 | const e = new Error(
375 | String(x) +
376 | '\n' +
377 | 'cypress-if tip: pass an error instance to have correct stack',
378 | )
379 | throw e
380 | })
381 |
382 | Cypress.Commands.overwriteQuery('not', function (notCommand, selector) {
383 | debug('cy.not args', { selector })
384 |
385 | const cmd = cy.state('current')
386 | debug(cmd)
387 | const next = cmd.attributes.next
388 | const innerFn = notCommand.call(this, selector)
389 |
390 | if (isIfCommand(next)) {
391 | // disable the built-in assertion
392 | return (subject) => {
393 | const res = innerFn(subject)
394 | if (res && res.length) {
395 | debug('internal not result', res)
396 | return res
397 | }
398 | debug('no not result')
399 | }
400 | }
401 |
402 | return (subject) => innerFn(subject)
403 | })
404 | }
405 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["es6", "DOM"],
4 | "types": ["cypress"],
5 | "noEmit": true,
6 | "allowJs": true
7 | },
8 | "include": ["src/index-v11.js", "src/index.d.ts", "cypress/**/*.js"]
9 | }
10 |
--------------------------------------------------------------------------------