feat: add Cypress.Commands.overwriteQuery (#25674)

* feat: add Cypress.Commands.overwriteQuery

Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com>
Co-authored-by: Zach Bloomquist <git@chary.us>
This commit is contained in:
Blue F
2023-02-13 11:24:56 -08:00
committed by GitHub
parent 0acdd2c790
commit a11e266b8b
5 changed files with 97 additions and 16 deletions
+2 -1
View File
@@ -1,10 +1,11 @@
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
## 12.6.0
_Released 02/14/2023 (PENDING)_
**Features:**
- It is now possible to overwrite query commands using [`Cypress.Commands.overwriteQuery`](https://on.cypress.io/api/custom-queries). Addressed in [#25674](https://github.com/cypress-io/cypress/pull/25674).
- Added the "Open in IDE" feature for failed tests reported from the Debug page. Addressed in [#25691](https://github.com/cypress-io/cypress/pull/25691).
- Added a new CLI flag, called [`--auto-cancel-after-failures`](https://docs.cypress.io/guides/guides/command-line#Options), that overrides the project-level CI ["Auto Cancellation"](https://docs.cypress.io/guides/cloud/smart-orchestration#Auto-Cancellation) value when recording to the Cloud. This gives Cloud users on Business and Enterprise plans the flexibility to alter the auto-cancellation value per run. Addressed in [#25237](https://github.com/cypress-io/cypress/pull/25237).
- Added `Cypress.require()` for including dependencies within the `cy.origin()` callback. Removed support for `require()` and `import()` within the callback. Addresses [#24976](https://github.com/cypress-io/cypress/issues/24976).
+9
View File
@@ -53,6 +53,9 @@ declare namespace Cypress {
interface QueryFn<T extends keyof ChainableMethods> {
(this: Command, ...args: Parameters<ChainableMethods[T]>): (subject: any) => any
}
interface QueryFnWithOriginalFn<T extends keyof Chainable> {
(this: Command, originalFn: QueryFn<T>, ...args: Parameters<ChainableMethods[T]>): (subject: any) => any
}
interface ObjectLike {
[key: string]: any
}
@@ -648,6 +651,12 @@ declare namespace Cypress {
* @see https://on.cypress.io/api/custom-queries
*/
addQuery<T extends keyof Chainable>(name: T, fn: QueryFn<T>): void
/**
* Overwrite an existing Cypress query with a new implementation
* @see https://on.cypress.io/api/custom-queries
*/
overwriteQuery<T extends keyof Chainable>(name: T, fn: QueryFnWithOriginalFn<T>): void
}
/**
@@ -565,5 +565,54 @@ describe('driver/src/cypress/cy', () => {
cy.get('body').find('#specific-contains').children().should('have.class', 'active')
})
context('overwriting queries', () => {
it('does not allow commands to overwrite queries', () => {
const fn = () => Cypress.Commands.overwrite('get', () => {})
expect(fn).to.throw().with.property('message')
.and.include('Cannot overwite the `get` query. Queries can only be overwritten with `Cypress.Commands.overwriteQuery()`.')
expect(fn).to.throw().with.property('docsUrl')
.and.include('https://on.cypress.io/api')
})
it('does not allow queries to overwrite commands', () => {
const fn = () => Cypress.Commands.overwriteQuery('click', () => {})
expect(fn).to.throw().with.property('message')
.and.include('Cannot overwite the `click` command. Commands can only be overwritten with `Cypress.Commands.overwrite()`.')
expect(fn).to.throw().with.property('docsUrl')
.and.include('https://on.cypress.io/api')
})
it('can call the originalFn', () => {
// Ensure nothing gets confused when we overwrite the same query multiple times.
// Both overwrites should succeed, layered on top of each other.
let overwriteCalled = 0
Cypress.Commands.overwriteQuery('get', function (originalFn, ...args) {
overwriteCalled++
return originalFn.call(this, ...args)
})
let secondOverwriteCalled = 0
Cypress.Commands.overwriteQuery('get', function (originalFn, ...args) {
secondOverwriteCalled++
return originalFn.call(this, ...args)
})
cy.get('button').should('have.length', 24)
cy.then(() => {
expect(overwriteCalled).to.eq(1)
expect(secondOverwriteCalled).to.eq(1)
})
})
})
})
})
+29 -11
View File
@@ -27,11 +27,9 @@ const getTypeByPrevSubject = (prevSubject) => {
return 'parent'
}
const internalError = (path, name) => {
const internalError = (path, args) => {
$errUtils.throwErrByPath(path, {
args: {
name,
},
args,
stack: (new cy.state('specWindow').Error('add command stack')).stack,
errProps: {
appendToStack: {
@@ -88,11 +86,11 @@ export default {
add (name, options, fn) {
if (builtInCommandNames[name]) {
internalError('miscellaneous.invalid_new_command', name)
internalError('miscellaneous.invalid_new_command', { name })
}
if (reservedCommandNames.has(name)) {
internalError('miscellaneous.reserved_command', name)
internalError('miscellaneous.reserved_command', { name })
}
// .hover & .mount are special case commands. allow as builtins so users
@@ -126,11 +124,11 @@ export default {
const original = commands[name]
if (queries[name]) {
internalError('miscellaneous.invalid_overwrite_query_with_command', name)
internalError('miscellaneous.invalid_overwrite_query_with_command', { name })
}
if (!original) {
internalError('miscellaneous.invalid_overwrite', name)
internalError('miscellaneous.invalid_overwrite', { name, type: 'command' })
}
function originalFn (...args) {
@@ -157,13 +155,13 @@ export default {
return cy.addCommand(overridden)
},
addQuery (name: string, fn: () => QueryFunction) {
addQuery (name: string, fn: (...args: any[]) => QueryFunction) {
if (reservedCommandNames.has(name)) {
internalError('miscellaneous.reserved_command_query', name)
internalError('miscellaneous.reserved_command_query', { name })
}
if (cy[name]) {
internalError('miscellaneous.invalid_new_query', name)
internalError('miscellaneous.invalid_new_query', { name })
}
if (addingBuiltIns) {
@@ -173,6 +171,26 @@ export default {
queries[name] = fn
cy.addQuery({ name, fn })
},
overwriteQuery (name: string, fn: (...args: any[]) => QueryFunction) {
if (commands[name]) {
internalError('miscellaneous.invalid_overwrite_command_with_query', { name })
}
const original = queries[name]
if (!original) {
internalError('miscellaneous.invalid_overwrite', { name, type: 'command' })
}
queries[name] = function overridden (...args) {
args.unshift(original)
return fn.apply(this, args)
}
cy.addQuery({ name, fn: queries[name] })
},
}
addingBuiltIns = true
@@ -862,12 +862,16 @@ export default {
docsUrl: 'https://on.cypress.io/api/custom-queries',
},
invalid_overwrite: {
message: 'Cannot overwite command for: `{{name}}`. An existing command does not exist by that name.',
docsUrl: 'https://on.cypress.io/api',
message: 'Cannot overwite command for: `{{name}}`. An existing {{type}} does not exist by that name.',
docsUrl: 'https://on.cypress.io/api/custom-commands',
},
invalid_overwrite_command_with_query: {
message: 'Cannot overwite the `{{name}}` command. Commands can only be overwritten with `Cypress.Commands.overwrite()`.',
docsUrl: 'https://on.cypress.io/api/custom-commands',
},
invalid_overwrite_query_with_command: {
message: 'Cannot overwite the `{{name}}` query. Queries cannot be overwritten.',
docsUrl: 'https://on.cypress.io/api',
message: 'Cannot overwite the `{{name}}` query. Queries can only be overwritten with `Cypress.Commands.overwriteQuery()`.',
docsUrl: 'https://on.cypress.io/api/custom-queries',
},
invoking_child_without_parent (obj) {
return stripIndent`\