Skip to content

Commit 30a9a27

Browse files
author
Blue F
authored
Merge pull request #20704 from sainthkh/issue-19116
fix: `cy.contains` and `should('contain')` handle backslash correctly.
2 parents 4972872 + 81f8a85 commit 30a9a27

File tree

7 files changed

+74
-17
lines changed

7 files changed

+74
-17
lines changed

packages/driver/cypress/integration/commands/assertions_spec.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,36 @@ describe('src/cy/commands/assertions', () => {
12691269
})
12701270
})
12711271

1272+
// TODO: this suite should be merged with the suite above
1273+
describe('message formatting', () => {
1274+
const expectMarkdown = (test, message, done) => {
1275+
cy.then(() => {
1276+
test()
1277+
})
1278+
1279+
cy.on('log:added', (attrs, log) => {
1280+
if (attrs.name === 'assert') {
1281+
cy.removeAllListeners('log:added')
1282+
1283+
expect(log.get('message')).to.eq(message)
1284+
1285+
done()
1286+
}
1287+
})
1288+
}
1289+
1290+
// https://github.com/cypress-io/cypress/issues/19116
1291+
it('text with backslashes', (done) => {
1292+
const text = '"<OE_D]dQ\\'
1293+
1294+
expectMarkdown(
1295+
() => expect(text).to.equal(text),
1296+
`expected **"<OE_D]dQ\\\\** to equal **"<OE_D]dQ\\\\**`,
1297+
done,
1298+
)
1299+
})
1300+
})
1301+
12721302
context('chai overrides', () => {
12731303
beforeEach(function () {
12741304
this.$body = cy.$$('body')
@@ -1319,6 +1349,15 @@ describe('src/cy/commands/assertions', () => {
13191349

13201350
cy.get('#escape-quotes').should('contain', 'shouldn\'t')
13211351
})
1352+
1353+
// https://github.com/cypress-io/cypress/issues/19116
1354+
it('escapes backslashes', () => {
1355+
const $span = '<span id="escape-backslashes">"&lt;OE_D]dQ\\</span>'
1356+
1357+
cy.$$($span).appendTo(cy.$$('body'))
1358+
1359+
cy.get('#escape-backslashes').should('contain', '"<OE_D]dQ\\')
1360+
})
13221361
})
13231362

13241363
describe('#match', () => {

packages/driver/cypress/integration/commands/querying/querying_spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,12 @@ describe('src/cy/commands/querying', () => {
12241224
cy.contains(/\'/)
12251225
})
12261226

1227+
// https://github.com/cypress-io/cypress/issues/19116
1228+
it('handles backslashes', () => {
1229+
$('<div id="backslashes">"&lt;OE_D]dQ\\</div>').appendTo(cy.$$('body'))
1230+
cy.get('#backslashes').contains('"<OE_D]dQ\\')
1231+
})
1232+
12271233
describe('should(\'not.exist\')', () => {
12281234
it('returns null when no content exists', () => {
12291235
cy.contains('alksjdflkasjdflkajsdf').should('not.exist').then(($el) => {

packages/driver/src/cy/chai.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import sinonChai from '@cypress/sinon-chai'
88

99
import $dom from '../dom'
1010
import $utils from '../cypress/utils'
11+
import { escapeBackslashes, escapeQuotes } from '../util/escape'
1112
import $errUtils from '../cypress/error_utils'
1213
import $stackUtils from '../cypress/stack_utils'
1314
import $chaiJquery from '../cypress/chai_jquery'
@@ -29,6 +30,8 @@ const trailingWhitespaces = /\s*'\*\*/g
2930
const whitespace = /\s/g
3031
const valueHasLeadingOrTrailingWhitespaces = /\*\*'\s+|\s+'\*\*/g
3132
const imageMarkdown = /!\[.*?\]\(.*?\)/g
33+
const doubleslashRe = /\\\\/g
34+
const escapedDoubleslashRe = /__double_slash__/g
3235

3336
type CreateFunc = ((specWindow, state, assertFn) => ({
3437
chai: Chai.ChaiStatic
@@ -103,6 +106,9 @@ chai.use((chai, u) => {
103106
return
104107
})
105108

109+
const escapeDoubleSlash = (str: string) => str.replace(doubleslashRe, '__double_slash__')
110+
const restoreDoubleSlash = (str: string) => str.replace(escapedDoubleslashRe, '\\\\')
111+
106112
// remove any single quotes between our **,
107113
// except escaped quotes, empty strings and number strings.
108114
const removeOrKeepSingleQuotesBetweenStars = (message) => {
@@ -277,7 +283,9 @@ chai.use((chai, u) => {
277283
return _super.apply(this, arguments)
278284
}
279285

280-
const escText = $utils.escapeQuotes(text)
286+
const escText = escapeQuotes(
287+
escapeBackslashes(text),
288+
)
281289

282290
const selector = `:contains('${escText}'), [type='submit'][value~='${escText}']`
283291

@@ -457,7 +465,9 @@ chai.use((chai, u) => {
457465
let message = chaiUtils.getMessage(this, customArgs as Chai.AssertionArgs)
458466
const actual = chaiUtils.getActual(this, customArgs as Chai.AssertionArgs)
459467

468+
message = escapeDoubleSlash(message)
460469
message = removeOrKeepSingleQuotesBetweenStars(message)
470+
message = restoreDoubleSlash(message)
461471
message = escapeMarkdown(message)
462472

463473
try {

packages/driver/src/cypress/utils.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { $Location } from './location'
1010

1111
const tagOpen = /\[([a-z\s='"-]+)\]/g
1212
const tagClosed = /\[\/([a-z]+)\]/g
13-
const quotesRe = /('|")/g
1413

1514
const defaultOptions = {
1615
delay: 10,
@@ -267,12 +266,6 @@ export default {
267266
}
268267
},
269268

270-
escapeQuotes (text) {
271-
// convert to str and escape any single
272-
// or double quotes
273-
return (`${text}`).replace(quotesRe, '\\$1')
274-
},
275-
276269
normalizeNumber (num) {
277270
const parsed = Number(num)
278271

packages/driver/src/dom/elements/find.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import $document from '../document'
44
import $jquery from '../jquery'
55
import { getTagName } from './elementHelpers'
66
import { isWithinShadowRoot, getShadowElementFromPoint } from './shadow'
7-
import { normalizeWhitespaces, escapeQuotes } from './utils'
7+
import { normalizeWhitespaces } from './utils'
8+
import { escapeQuotes, escapeBackslashes } from '../../util/escape'
89

910
/**
1011
* Find Parents relative to an initial element
@@ -228,7 +229,9 @@ export const getContainsSelector = (text, filter = '', options: {
228229
} = {}) => {
229230
const $expr = $.expr[':']
230231

231-
const escapedText = escapeQuotes(text)
232+
const escapedText = escapeQuotes(
233+
escapeBackslashes(text),
234+
)
232235

233236
// they may have written the filter as
234237
// comma separated dom els, so we want to search all

packages/driver/src/dom/elements/utils.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import $window from '../window'
55
import $document from '../document'
66

77
const whitespaces = /\s+/g
8-
const quotesRe = /('|")/g
98

109
// When multiple space characters are considered as a single whitespace in all tags except <pre>.
1110
export const normalizeWhitespaces = (elem) => {
@@ -29,12 +28,6 @@ export const isSelector = ($el: JQuery<HTMLElement>, selector) => {
2928
return $el.is(selector)
3029
}
3130

32-
export function escapeQuotes (text) {
33-
// convert to str and escape any single
34-
// or double quotes
35-
return (`${text}`).replace(quotesRe, '\\$1')
36-
}
37-
3831
export function switchCase (value, casesObj, defaultKey = 'default') {
3932
if (_.has(casesObj, value)) {
4033
return _.result(casesObj, value)

packages/driver/src/util/escape.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const quotesRe = /('|")/g
2+
const backslashRe = /\\/g
3+
4+
export function escapeQuotes (text) {
5+
// convert to str and escape any single
6+
// or double quotes
7+
return (`${text}`).replace(quotesRe, '\\$1')
8+
}
9+
10+
export function escapeBackslashes (text) {
11+
// convert to str and escape any backslashes
12+
return (`${text}`).replace(backslashRe, '\\\\')
13+
}

0 commit comments

Comments
 (0)