Skip to content

Commit 0f96a3d

Browse files
authored
feat: support assertions with two values (#112)
* feat: support assertions with two values * bump upload artifact action * update v11 code * store screenshots and videos on failure * updated the tests * debugging * fix the last else branch
1 parent 40e0e48 commit 0f96a3d

File tree

9 files changed

+143
-19
lines changed

9 files changed

+143
-19
lines changed

.github/workflows/ci.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ jobs:
1414
# check if the types agree
1515
build: npm run types
1616

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+
1732
# there was a breaking change under the hood in Cypress v11.1.0
1833
# so make sure this plugin still works for older versions
1934
test-cypress-v11-0:
@@ -45,7 +60,8 @@ jobs:
4560
with:
4661
working-directory: cypress-v9
4762

48-
- uses: actions/upload-artifact@v2
63+
# https://github.com/actions/upload-artifact
64+
- uses: actions/upload-artifact@v4
4965
name: Store any v9 screenshots 🖼
5066
if: failure()
5167
with:

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,17 @@ cy.wrap(42).if('not.null') // takes IF path
268268

269269
See spec [null.cy.js](./cypress/e2e/null.cy.js)
270270

271+
## Multiple values
272+
273+
Some assertions need two values, for example:
274+
275+
```js
276+
// only checks the presence of the "data-x" HTML attribute
277+
.if('have.attr', 'data-x')
278+
// checks if the "data-x" attribute present AND has value "123"
279+
.if('have.attr', 'data-x', '123')
280+
```
281+
271282
## raise
272283

273284
This plugin includes a utility custom command `cy.raise` that lets you conveniently throw an error.

cypress.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module.exports = defineConfig({
88
viewportWidth: 200,
99
viewportHeight: 200,
1010
defaultCommandTimeout: 1000,
11+
video: true,
1112
setupNodeEvents(on, config) {
1213
// implement node event listeners here
1314
on('task', {

cypress/e2e/has-attribute.cy.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/// <reference types="cypress" />
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+
})

cypress/e2e/terms-and-conditions.cy.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import '../../src'
55
it('submits the terms forms', () => {
66
cy.visit('cypress/terms.html')
77
cy.get('#agreed')
8+
cy.get('#agreed')
9+
.should('be.visible')
810
.if('not.checked')
911
.click()
12+
.log('clicked the checkbox')
1013
.else()
1114
.log('The user already agreed')
1215
cy.get('button#submit').click()

cypress/terms.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
} else {
2727
console.log('checked = false')
2828
}
29+
30+
// set an attribute after a delay
31+
setTimeout(() => {
32+
document.getElementById('submit').setAttribute('data-X', '123')
33+
}, 1000)
2934
</script>
3035
</body>
3136
</html>

src/index-v11.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function getCypressCurrentSubject() {
5050
Cypress.Commands.add(
5151
'if',
5252
{ prevSubject: true },
53-
function (subject, assertion, assertionValue) {
53+
function (subject, assertion, assertionValue1, assertionValue2) {
5454
const cmd = cy.state('current')
5555
debug('if', cmd.attributes, 'subject', subject, 'assertion?', assertion)
5656
debug('next command', cmd.next)
@@ -86,18 +86,33 @@ Cypress.Commands.add(
8686
const parts = assertion.split('.')
8787
let assertionReduced = expect(subject).to
8888
parts.forEach((assertionPart, k) => {
89-
if (
90-
k === parts.length - 1 &&
91-
typeof assertionValue !== 'undefined'
92-
) {
93-
assertionReduced = assertionReduced[assertionPart](assertionValue)
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+
}
94104
} else {
95105
assertionReduced = assertionReduced[assertionPart]
96106
}
97107
})
98108
} else {
99-
if (typeof assertionValue !== 'undefined') {
100-
expect(subject).to.be[assertion](assertionValue)
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)
101116
} else {
102117
expect(subject).to.be[assertion]
103118
}

src/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ declare namespace Cypress {
2424
if(
2525
this: Chainable<Subject>,
2626
assertion?: string,
27-
value?: any,
27+
value1?: any,
28+
value2?: any,
2829
): Chainable<Subject>
2930

3031
/**

src/index.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ if (major < 12) {
5454
Cypress.Commands.add(
5555
'if',
5656
{ prevSubject: true },
57-
function (subject, assertion, assertionValue) {
57+
function (subject, assertion, assertionValue1, assertionValue2) {
5858
const cmd = cy.state('current')
5959
debug('if', cmd.attributes, 'subject', subject, 'assertion?', assertion)
6060
debug('next command', cmd.next)
@@ -90,19 +90,33 @@ if (major < 12) {
9090
const parts = assertion.split('.')
9191
let assertionReduced = expect(subject).to
9292
parts.forEach((assertionPart, k) => {
93-
if (
94-
k === parts.length - 1 &&
95-
typeof assertionValue !== 'undefined'
96-
) {
97-
assertionReduced =
98-
assertionReduced[assertionPart](assertionValue)
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+
}
99108
} else {
100109
assertionReduced = assertionReduced[assertionPart]
101110
}
102111
})
103112
} else {
104-
if (typeof assertionValue !== 'undefined') {
105-
expect(subject).to.be[assertion](assertionValue)
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)
106120
} else {
107121
expect(subject).to.be[assertion]
108122
}

0 commit comments

Comments
 (0)