Skip to content

Commit

Permalink
feat: Add 'type' option to .as to store aliases by value (#25251)
Browse files Browse the repository at this point in the history
* feat: Add 'type' option to `.as` to store aliases by value

Co-authored-by: Chris Breiding <chrisbreiding@users.noreply.github.com>
Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com>
  • Loading branch information
3 people committed Jan 24, 2023
1 parent 5d5d075 commit 094e3d0
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 40 deletions.
69 changes: 42 additions & 27 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,29 +827,13 @@ declare namespace Cypress {
* @see https://on.cypress.io/variables-and-aliases
* @see https://on.cypress.io/get
* @example
```
// Get the aliased 'todos' elements
cy.get('ul#todos').as('todos')
//...hack hack hack...
// later retrieve the todos
cy.get('@todos')
```
*/
as(alias: string): Chainable<Subject>

/**
* Select a file with the given <input> element, or drag and drop a file over any DOM subject.
* // Get the aliased 'todos' elements
* cy.get('ul#todos').as('todos')
*
* @param {FileReference} files - The file(s) to select or drag onto this element.
* @see https://on.cypress.io/selectfile
* @example
* cy.get('input[type=file]').selectFile(Cypress.Buffer.from('text'))
* cy.get('input[type=file]').selectFile({
* fileName: 'users.json',
* contents: [{name: 'John Doe'}]
* })
* // later retrieve the todos
* cy.get('@todos')
*/
selectFile(files: FileReference | FileReference[], options?: Partial<SelectFileOptions>): Chainable<Subject>
as(alias: string, options?: Partial<AsOptions>): Chainable<Subject>

/**
* Blur a focused element. This element must currently be in focus.
Expand Down Expand Up @@ -1915,6 +1899,20 @@ declare namespace Cypress {
*/
select(valueOrTextOrIndex: string | number | Array<string | number>, options?: Partial<SelectOptions>): Chainable<Subject>

/**
* Select a file with the given <input> element, or drag and drop a file over any DOM subject.
*
* @param {FileReference} files - The file(s) to select or drag onto this element.
* @see https://on.cypress.io/selectfile
* @example
* cy.get('input[type=file]').selectFile(Cypress.Buffer.from('text'))
* cy.get('input[type=file]').selectFile({
* fileName: 'users.json',
* contents: [{name: 'John Doe'}]
* })
*/
selectFile(files: FileReference | FileReference[], options?: Partial<SelectFileOptions>): Chainable<Subject>

/**
* Set a browser cookie.
*
Expand Down Expand Up @@ -2650,6 +2648,7 @@ declare namespace Cypress {
waitForAnimations: boolean
/**
* The distance in pixels an element must exceed over time to be considered animating
*
* @default 5
*/
animationDistanceThreshold: number
Expand All @@ -2661,15 +2660,20 @@ declare namespace Cypress {
scrollBehavior: scrollBehaviorOptions
}

interface SelectFileOptions extends Loggable, Timeoutable, ActionableOptions {
/**
* Options to affect how an alias is stored
*
* @see https://on.cypress.io/as
*/
interface AsOptions {
/**
* Which user action to perform. `select` matches selecting a file while
* `drag-drop` matches dragging files from the operating system into the
* document.
* The type of alias to store, which impacts how the value is retrieved later in the test.
* If an alias should be a 'query' (re-runs all queries leading up to the resulting value so it's alway up-to-date) or a
* 'static' (read once when the alias is saved and is never updated). `type` has no effect when aliasing intercepts, spies, and stubs.
*
* @default 'select'
* @default 'query'
*/
action: 'select' | 'drag-drop'
type: 'query' | 'static'
}

interface BlurOptions extends Loggable, Timeoutable, Forceable { }
Expand Down Expand Up @@ -3515,6 +3519,17 @@ declare namespace Cypress {

type SameSiteStatus = 'no_restriction' | 'strict' | 'lax'

interface SelectFileOptions extends Loggable, Timeoutable, ActionableOptions {
/**
* Which user action to perform. `select` matches selecting a file while
* `drag-drop` matches dragging files from the operating system into the
* document.
*
* @default 'select'
*/
action: 'select' | 'drag-drop'
}

interface SetCookieOptions extends Loggable, Timeoutable {
path: string
domain: string
Expand Down
2 changes: 2 additions & 0 deletions cli/types/tests/cypress-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,8 @@ cy.stub().withArgs('').log(false).as('foo')

cy.spy().withArgs('').log(false).as('foo')

cy.get('something').as('foo', {type: 'static'})

cy.wrap('foo').then(subject => {
subject // $ExpectType string
return cy.wrap(subject)
Expand Down
51 changes: 47 additions & 4 deletions packages/driver/cypress/e2e/commands/aliasing.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ describe('src/cy/commands/aliasing', () => {
cy.get('@obj').should('deep.eq', { foo: 'bar' })
})

it('allows users to store a static value', () => {
const obj = { foo: 'bar' }

cy.wrap(obj).its('foo').as('alias1', { type: 'static' })
cy.wrap(obj).its('foo').as('alias2', { type: 'query' })

cy.then(() => {
obj.foo = 'baz'
})

cy.get('@alias1').should('eq', 'bar')
cy.get('@alias2').should('eq', 'baz')
})

it('allows dot in alias names', () => {
cy.get('body').as('body.foo').then(() => {
expect(cy.state('aliases')['body.foo']).to.exist
Expand Down Expand Up @@ -236,6 +250,28 @@ describe('src/cy/commands/aliasing', () => {
cy.get('div:first').as(reserved)
})
})

it('throws when given non-object options', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq(`\`cy.as()\` only accepts an options object for its second argument. You passed: \`wut?\``)
expect(err.docsUrl).to.eq('https://on.cypress.io/as')

done()
})

cy.wrap({}).as('value', 'wut?')
})

it('throws when given invalid `type`', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq(`\`cy.as()\` only accepts a \`type\` of \`'query'\` or \`'static'\`. You passed: \`wut?\``)
expect(err.docsUrl).to.eq('https://on.cypress.io/as')

done()
})

cy.wrap({}).as('value', { type: 'wut?' })
})
})

describe('log', () => {
Expand Down Expand Up @@ -284,11 +320,19 @@ describe('src/cy/commands/aliasing', () => {
const { lastLog } = this

assertLogLength(this.logs, 1)
expect(lastLog.get('alias')).to.eq('foo')
expect(lastLog.get('alias')).to.eq('@foo')
expect(lastLog.get('aliasType')).to.eq('dom')
})
})

it('includes the alias `type` when set to `static`', () => {
cy.wrap({}).as('foo', { type: 'static' }).then(function () {
const { lastLog } = this

expect(lastLog.get('alias')).to.eq('@foo (static)')
})
})

it('does not match alias when the alias has already been applied', () => {
cy
.visit('/fixtures/commands.html')
Expand Down Expand Up @@ -495,9 +539,8 @@ describe('src/cy/commands/aliasing', () => {
})
})

// TODO: Re-enable as part of https://github.com/cypress-io/cypress/issues/23902
it.skip('maintains .within() context while reading aliases', () => {
cy.get('#specific-contains').within(() => {
it('maintains .within() context while reading aliases', () => {
cy.get('#nested-div').within(() => {
cy.get('span').as('spanWithin').should('have.length', 1)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ describe('src/cy/commands/querying', () => {
state: 'passed',
name: 'get',
message: 'body',
alias: 'b',
alias: '@b',
aliasType: 'dom',
referencesAlias: undefined,
}
Expand Down
4 changes: 2 additions & 2 deletions packages/driver/cypress/e2e/commands/window.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe('src/cy/commands/window', () => {

expect(win).to.eq(this.win)

expect(this.logs[0].get('alias')).to.eq('win')
expect(this.logs[0].get('alias')).to.eq('@win')
expect(this.logs[0].get('aliasType')).to.eq('primitive')

expect(this.logs[2].get('aliasType')).to.eq('primitive')
Expand Down Expand Up @@ -329,7 +329,7 @@ describe('src/cy/commands/window', () => {

expect(doc).to.eq(this.doc)

expect(logs[0].get('alias')).to.eq('doc')
expect(logs[0].get('alias')).to.eq('@doc')
expect(logs[0].get('aliasType')).to.eq('primitive')

expect(logs[2].get('aliasType')).to.eq('primitive')
Expand Down
4 changes: 2 additions & 2 deletions packages/driver/cypress/e2e/e2e/origin/commands/actions.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ context('cy.origin actions', { browser: '!webkit' }, () => {
})
})

it('.alias()', () => {
it('.as()', () => {
cy.get('a[data-cy="dom-link"]').click()
cy.origin('http://www.foobar.com:3500', () => {
cy.get('#button').as('buttonAlias')
Expand All @@ -255,7 +255,7 @@ context('cy.origin actions', { browser: '!webkit' }, () => {
// make sure $el is in fact a jquery instance to keep the logs happy
expect($el.jquery).to.be.ok

expect(alias).to.equal('buttonAlias')
expect(alias).to.equal('@buttonAlias')
expect(aliasType).to.equal('dom')
expect(consoleProps.Command).to.equal('get')
expect(consoleProps.Elements).to.equal(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ context('cy.origin aliasing', { browser: '!webkit' }, () => {
// make sure $el is in fact a jquery instance to keep the logs happy
expect($el.jquery).to.be.ok

expect(alias).to.equal('buttonAlias')
expect(alias).to.equal('@buttonAlias')
expect(aliasType).to.equal('dom')
expect(consoleProps.Command).to.equal('get')
expect(consoleProps.Elements).to.equal(1)
Expand Down
22 changes: 19 additions & 3 deletions packages/driver/src/cy/commands/aliasing.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import _ from 'lodash'
import $dom from '../../dom'
import $errUtils from '../../cypress/error_utils'

export default function (Commands, Cypress, cy) {
Commands.addQuery('as', function asFn (alias) {
Commands.addQuery('as', function asFn (alias, options = {} as Partial<Cypress.AsOptions>) {
Cypress.ensure.isChildCommand(this, [alias], cy)
cy.validateAlias(alias)

if (!_.isPlainObject(options)) {
$errUtils.throwErrByPath('as.invalid_options', { args: { arg: options } })
}

if (options.type && !['query', 'static'].includes(options.type)) {
$errUtils.throwErrByPath('as.invalid_options_type', { args: { type: options.type } })
}

const prevCommand = cy.state('current').get('prev')

prevCommand.set('alias', alias)

// Shallow clone of the existing subject chain, so that future commands running on the same chainer
// don't apply here as well.
const subjectChain = [...cy.subjectChain()]
let subjectChain = [...cy.subjectChain()]

// If the user wants us to store a specific static value, rather than
// requery it live, we replace the subject chain with a resolved value.
// https://github.com/cypress-io/cypress/issues/25173
if (options.type === 'static') {
subjectChain = [cy.getSubjectFromChain(subjectChain)]
}

const fileName = prevCommand.get('fileName')

Expand Down Expand Up @@ -42,7 +58,7 @@ export default function (Commands, Cypress, cy) {

if (!alreadyAliasedLog && log) {
log.set({
alias,
alias: `@${alias}${options.type === 'static' ? ` (${ options.type })` : ''}`,
aliasType: $dom.isElement(subject) ? 'dom' : 'primitive',
})
}
Expand Down
2 changes: 2 additions & 0 deletions packages/driver/src/cypress/error_messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ export default {
reserved_word: {
message: `${cmd('as')} cannot be aliased as: \`{{alias}}\`. This word is reserved.`,
},
invalid_options: `${cmd('as')} only accepts an options object for its second argument. You passed: \`{{arg}}\``,
invalid_options_type: `${cmd('as')} only accepts a \`type\` of \`'query'\` or \`'static'\`. You passed: \`{{type}}\``,
},

blur: {
Expand Down

5 comments on commit 094e3d0

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 094e3d0 Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.4.0/linux-arm64/develop-094e3d03cc9f379965a7f0fb09a9e1cf44992014/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 094e3d0 Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.4.0/linux-x64/develop-094e3d03cc9f379965a7f0fb09a9e1cf44992014/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 094e3d0 Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.4.0/darwin-arm64/develop-094e3d03cc9f379965a7f0fb09a9e1cf44992014/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 094e3d0 Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.4.0/darwin-x64/develop-094e3d03cc9f379965a7f0fb09a9e1cf44992014/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 094e3d0 Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.4.0/win32-x64/develop-094e3d03cc9f379965a7f0fb09a9e1cf44992014/cypress.tgz

Please sign in to comment.