diff --git a/packages/driver/src/cy/commands/querying.js b/packages/driver/src/cy/commands/querying.js index af78bc946847..18124f7013c7 100644 --- a/packages/driver/src/cy/commands/querying.js +++ b/packages/driver/src/cy/commands/querying.js @@ -1,28 +1,10 @@ const _ = require('lodash') -const $ = require('jquery') const Promise = require('bluebird') const $dom = require('../../dom') const $errUtils = require('../../cypress/error_utils') -const $expr = $.expr[':'] - -const $contains = $expr.contains - -const restoreContains = () => { - return $expr.contains = $contains -} - -const whitespaces = /\s+/g - module.exports = (Commands, Cypress, cy) => { - // restore initially when a run starts - restoreContains() - - // restore before each test and whenever we stop - Cypress.on('test:before:run', restoreContains) - Cypress.on('stop', restoreContains) - Commands.addAll({ focused (options = {}) { _.defaults(options, { @@ -483,46 +465,9 @@ module.exports = (Commands, Cypress, cy) => { options._log.set({ $el }) } - // When multiple space characters are considered as a single whitespace in all tags except
. - const normalizeWhitespaces = (elem) => { - let testText = elem.textContent || elem.innerText || $.text(elem) - - if (elem.tagName === 'PRE') { - return testText - } - - return testText.replace(whitespaces, ' ') - } - - if (_.isRegExp(text)) { - if (options.matchCase === false && !text.flags.includes('i')) { - text = new RegExp(text.source, text.flags + 'i') // eslint-disable-line prefer-template - } - - // taken from jquery's normal contains method - $expr.contains = (elem) => { - let testText = normalizeWhitespaces(elem) - - return text.test(testText) - } - } - - if (_.isString(text)) { - $expr.contains = (elem) => { - let testText = normalizeWhitespaces(elem) - - if (!options.matchCase) { - testText = testText.toLowerCase() - text = text.toLowerCase() - } - - return testText.includes(text) - } - } - - // find elements by the :contains psuedo selector + // find elements by the :cy-contains psuedo selector // and any submit inputs with the attributeContainsWord selector - const selector = $dom.getContainsSelector(text, filter) + const selector = $dom.getContainsSelector(text, filter, options) const resolveElements = () => { const getOpts = _.extend(_.clone(options), { @@ -563,11 +508,6 @@ module.exports = (Commands, Cypress, cy) => { return Promise .try(resolveElements) - // always restore contains in case - // we used a regexp! - .finally(() => { - restoreContains() - }) }, }) diff --git a/packages/driver/src/dom/elements.ts b/packages/driver/src/dom/elements.ts index fc1641b6a4d1..904c1936159d 100644 --- a/packages/driver/src/dom/elements.ts +++ b/packages/driver/src/dom/elements.ts @@ -945,7 +945,23 @@ const getElements = ($el) => { return els } -const getContainsSelector = (text, filter = '') => { +const whitespaces = /\s+/g + +// When multiple space characters are considered as a single whitespace in all tags except. +const normalizeWhitespaces = (elem) => { + let testText = elem.textContent || elem.innerText || $(elem).text() + + if (elem.tagName === 'PRE') { + return testText + } + + return testText.replace(whitespaces, ' ') +} +const getContainsSelector = (text, filter = '', options: { + matchCase?: boolean +} = {}) => { + const $expr = $.expr[':'] + const escapedText = $utils.escapeQuotes(text) // they may have written the filter as @@ -953,8 +969,41 @@ const getContainsSelector = (text, filter = '') => { // https://github.com/cypress-io/cypress/issues/2407 const filters = filter.trim().split(',') + let cyContainsSelector + + if (_.isRegExp(text)) { + if (options.matchCase === false && !text.flags.includes('i')) { + text = new RegExp(text.source, text.flags + 'i') // eslint-disable-line prefer-template + } + + // taken from jquery's normal contains method + cyContainsSelector = function (elem) { + let testText = normalizeWhitespaces(elem) + + return text.test(testText) + } + } else if (_.isString(text)) { + cyContainsSelector = function (elem) { + let testText = normalizeWhitespaces(elem) + + if (!options.matchCase) { + testText = testText.toLowerCase() + text = text.toLowerCase() + } + + return testText.includes(text) + } + } else { + cyContainsSelector = $expr.contains + } + + // we set the `cy-contains` jquery selector which will only be used + // in the context of cy.contains(...) command and selector playground. + $expr['cy-contains'] = cyContainsSelector + const selectors = _.map(filters, (filter) => { - return `${filter}:not(script,style):contains('${escapedText}'), ${filter}[type='submit'][value~='${escapedText}']` + // use custom cy-contains selector that is registered above + return `${filter}:not(script,style):cy-contains('${escapedText}'), ${filter}[type='submit'][value~='${escapedText}']` }) return selectors.join() diff --git a/packages/driver/test/cypress/integration/commands/assertions_spec.js b/packages/driver/test/cypress/integration/commands/assertions_spec.js index 481e4bcc404a..05b8859f3404 100644 --- a/packages/driver/test/cypress/integration/commands/assertions_spec.js +++ b/packages/driver/test/cypress/integration/commands/assertions_spec.js @@ -355,6 +355,26 @@ describe('src/cy/commands/assertions', () => { cy.contains('Nested Find').should('have.length', 2) }) + + // https://github.com/cypress-io/cypress/issues/6384 + it('can chain contains assertions off of cy.contains', () => { + cy.timeout(100) + cy.contains('foo') + .should('not.contain', 'asdfasdf') + + cy.contains('foo') + .should('contain', 'foo') + + cy.contains(/foo/) + .should('not.contain', 'asdfsadf') + + cy.contains(/foo/) + .should('contain', 'foo') + + // this isn't valid: .should('contain') does not support regex + // cy.contains(/foo/) + // .should('contain', /foo/) + }) }) describe('have.class', () => { diff --git a/packages/driver/test/cypress/plugins/index.js b/packages/driver/test/cypress/plugins/index.js index ee321ee0af4a..b0a17637cffb 100644 --- a/packages/driver/test/cypress/plugins/index.js +++ b/packages/driver/test/cypress/plugins/index.js @@ -7,6 +7,7 @@ const fs = require('fs-extra') const Promise = require('bluebird') const webpack = require('@cypress/webpack-preprocessor') +process.env.NO_LIVERELOAD = '1' const webpackOptions = require('@packages/runner/webpack.config.ts').default /** diff --git a/packages/web-config/webpack.config.base.ts b/packages/web-config/webpack.config.base.ts index be234e278fa0..bc7f7a0e1866 100644 --- a/packages/web-config/webpack.config.base.ts +++ b/packages/web-config/webpack.config.base.ts @@ -15,7 +15,7 @@ execa.sync('rebuild-node-sass', { cwd: path.join(__dirname, './node_modules/.bin const env = process.env.NODE_ENV === 'production' ? 'production' : 'development' const args = process.argv.slice(2) -const liveReloadEnabled = !args.includes('--no-livereload') +const liveReloadEnabled = !(args.includes('--no-livereload') || process.env.NO_LIVERELOAD) const watchModeEnabled = args.includes('--watch') || args.includes('-w') // opt out of livereload with arg --no-livereload