Skip to content

Commit 094e3d0

Browse files
Blue Fchrisbreidingemilyrohrbough
authored
feat: Add 'type' option to .as to store aliases by value (#25251)
* 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>
1 parent 5d5d075 commit 094e3d0

File tree

9 files changed

+118
-40
lines changed

9 files changed

+118
-40
lines changed

cli/types/cypress.d.ts

+42-27
Original file line numberDiff line numberDiff line change
@@ -827,29 +827,13 @@ declare namespace Cypress {
827827
* @see https://on.cypress.io/variables-and-aliases
828828
* @see https://on.cypress.io/get
829829
* @example
830-
```
831-
// Get the aliased 'todos' elements
832-
cy.get('ul#todos').as('todos')
833-
//...hack hack hack...
834-
// later retrieve the todos
835-
cy.get('@todos')
836-
```
837-
*/
838-
as(alias: string): Chainable<Subject>
839-
840-
/**
841-
* Select a file with the given <input> element, or drag and drop a file over any DOM subject.
830+
* // Get the aliased 'todos' elements
831+
* cy.get('ul#todos').as('todos')
842832
*
843-
* @param {FileReference} files - The file(s) to select or drag onto this element.
844-
* @see https://on.cypress.io/selectfile
845-
* @example
846-
* cy.get('input[type=file]').selectFile(Cypress.Buffer.from('text'))
847-
* cy.get('input[type=file]').selectFile({
848-
* fileName: 'users.json',
849-
* contents: [{name: 'John Doe'}]
850-
* })
833+
* // later retrieve the todos
834+
* cy.get('@todos')
851835
*/
852-
selectFile(files: FileReference | FileReference[], options?: Partial<SelectFileOptions>): Chainable<Subject>
836+
as(alias: string, options?: Partial<AsOptions>): Chainable<Subject>
853837

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

1902+
/**
1903+
* Select a file with the given <input> element, or drag and drop a file over any DOM subject.
1904+
*
1905+
* @param {FileReference} files - The file(s) to select or drag onto this element.
1906+
* @see https://on.cypress.io/selectfile
1907+
* @example
1908+
* cy.get('input[type=file]').selectFile(Cypress.Buffer.from('text'))
1909+
* cy.get('input[type=file]').selectFile({
1910+
* fileName: 'users.json',
1911+
* contents: [{name: 'John Doe'}]
1912+
* })
1913+
*/
1914+
selectFile(files: FileReference | FileReference[], options?: Partial<SelectFileOptions>): Chainable<Subject>
1915+
19181916
/**
19191917
* Set a browser cookie.
19201918
*
@@ -2650,6 +2648,7 @@ declare namespace Cypress {
26502648
waitForAnimations: boolean
26512649
/**
26522650
* The distance in pixels an element must exceed over time to be considered animating
2651+
*
26532652
* @default 5
26542653
*/
26552654
animationDistanceThreshold: number
@@ -2661,15 +2660,20 @@ declare namespace Cypress {
26612660
scrollBehavior: scrollBehaviorOptions
26622661
}
26632662

2664-
interface SelectFileOptions extends Loggable, Timeoutable, ActionableOptions {
2663+
/**
2664+
* Options to affect how an alias is stored
2665+
*
2666+
* @see https://on.cypress.io/as
2667+
*/
2668+
interface AsOptions {
26652669
/**
2666-
* Which user action to perform. `select` matches selecting a file while
2667-
* `drag-drop` matches dragging files from the operating system into the
2668-
* document.
2670+
* The type of alias to store, which impacts how the value is retrieved later in the test.
2671+
* 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
2672+
* 'static' (read once when the alias is saved and is never updated). `type` has no effect when aliasing intercepts, spies, and stubs.
26692673
*
2670-
* @default 'select'
2674+
* @default 'query'
26712675
*/
2672-
action: 'select' | 'drag-drop'
2676+
type: 'query' | 'static'
26732677
}
26742678

26752679
interface BlurOptions extends Loggable, Timeoutable, Forceable { }
@@ -3515,6 +3519,17 @@ declare namespace Cypress {
35153519

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

3522+
interface SelectFileOptions extends Loggable, Timeoutable, ActionableOptions {
3523+
/**
3524+
* Which user action to perform. `select` matches selecting a file while
3525+
* `drag-drop` matches dragging files from the operating system into the
3526+
* document.
3527+
*
3528+
* @default 'select'
3529+
*/
3530+
action: 'select' | 'drag-drop'
3531+
}
3532+
35183533
interface SetCookieOptions extends Loggable, Timeoutable {
35193534
path: string
35203535
domain: string

cli/types/tests/cypress-tests.ts

+2
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@ cy.stub().withArgs('').log(false).as('foo')
546546

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

549+
cy.get('something').as('foo', {type: 'static'})
550+
549551
cy.wrap('foo').then(subject => {
550552
subject // $ExpectType string
551553
return cy.wrap(subject)

packages/driver/cypress/e2e/commands/aliasing.cy.js

+47-4
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ describe('src/cy/commands/aliasing', () => {
6565
cy.get('@obj').should('deep.eq', { foo: 'bar' })
6666
})
6767

68+
it('allows users to store a static value', () => {
69+
const obj = { foo: 'bar' }
70+
71+
cy.wrap(obj).its('foo').as('alias1', { type: 'static' })
72+
cy.wrap(obj).its('foo').as('alias2', { type: 'query' })
73+
74+
cy.then(() => {
75+
obj.foo = 'baz'
76+
})
77+
78+
cy.get('@alias1').should('eq', 'bar')
79+
cy.get('@alias2').should('eq', 'baz')
80+
})
81+
6882
it('allows dot in alias names', () => {
6983
cy.get('body').as('body.foo').then(() => {
7084
expect(cy.state('aliases')['body.foo']).to.exist
@@ -236,6 +250,28 @@ describe('src/cy/commands/aliasing', () => {
236250
cy.get('div:first').as(reserved)
237251
})
238252
})
253+
254+
it('throws when given non-object options', (done) => {
255+
cy.on('fail', (err) => {
256+
expect(err.message).to.eq(`\`cy.as()\` only accepts an options object for its second argument. You passed: \`wut?\``)
257+
expect(err.docsUrl).to.eq('https://on.cypress.io/as')
258+
259+
done()
260+
})
261+
262+
cy.wrap({}).as('value', 'wut?')
263+
})
264+
265+
it('throws when given invalid `type`', (done) => {
266+
cy.on('fail', (err) => {
267+
expect(err.message).to.eq(`\`cy.as()\` only accepts a \`type\` of \`'query'\` or \`'static'\`. You passed: \`wut?\``)
268+
expect(err.docsUrl).to.eq('https://on.cypress.io/as')
269+
270+
done()
271+
})
272+
273+
cy.wrap({}).as('value', { type: 'wut?' })
274+
})
239275
})
240276

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

286322
assertLogLength(this.logs, 1)
287-
expect(lastLog.get('alias')).to.eq('foo')
323+
expect(lastLog.get('alias')).to.eq('@foo')
288324
expect(lastLog.get('aliasType')).to.eq('dom')
289325
})
290326
})
291327

328+
it('includes the alias `type` when set to `static`', () => {
329+
cy.wrap({}).as('foo', { type: 'static' }).then(function () {
330+
const { lastLog } = this
331+
332+
expect(lastLog.get('alias')).to.eq('@foo (static)')
333+
})
334+
})
335+
292336
it('does not match alias when the alias has already been applied', () => {
293337
cy
294338
.visit('/fixtures/commands.html')
@@ -495,9 +539,8 @@ describe('src/cy/commands/aliasing', () => {
495539
})
496540
})
497541

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

packages/driver/cypress/e2e/commands/querying/querying.cy.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ describe('src/cy/commands/querying', () => {
419419
state: 'passed',
420420
name: 'get',
421421
message: 'body',
422-
alias: 'b',
422+
alias: '@b',
423423
aliasType: 'dom',
424424
referencesAlias: undefined,
425425
}

packages/driver/cypress/e2e/commands/window.cy.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ describe('src/cy/commands/window', () => {
145145

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

148-
expect(this.logs[0].get('alias')).to.eq('win')
148+
expect(this.logs[0].get('alias')).to.eq('@win')
149149
expect(this.logs[0].get('aliasType')).to.eq('primitive')
150150

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

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

332-
expect(logs[0].get('alias')).to.eq('doc')
332+
expect(logs[0].get('alias')).to.eq('@doc')
333333
expect(logs[0].get('aliasType')).to.eq('primitive')
334334

335335
expect(logs[2].get('aliasType')).to.eq('primitive')

packages/driver/cypress/e2e/e2e/origin/commands/actions.cy.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ context('cy.origin actions', { browser: '!webkit' }, () => {
243243
})
244244
})
245245

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

258-
expect(alias).to.equal('buttonAlias')
258+
expect(alias).to.equal('@buttonAlias')
259259
expect(aliasType).to.equal('dom')
260260
expect(consoleProps.Command).to.equal('get')
261261
expect(consoleProps.Elements).to.equal(1)

packages/driver/cypress/e2e/e2e/origin/commands/aliasing.cy.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ context('cy.origin aliasing', { browser: '!webkit' }, () => {
5353
// make sure $el is in fact a jquery instance to keep the logs happy
5454
expect($el.jquery).to.be.ok
5555

56-
expect(alias).to.equal('buttonAlias')
56+
expect(alias).to.equal('@buttonAlias')
5757
expect(aliasType).to.equal('dom')
5858
expect(consoleProps.Command).to.equal('get')
5959
expect(consoleProps.Elements).to.equal(1)

packages/driver/src/cy/commands/aliasing.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
11
import _ from 'lodash'
22
import $dom from '../../dom'
3+
import $errUtils from '../../cypress/error_utils'
34

45
export default function (Commands, Cypress, cy) {
5-
Commands.addQuery('as', function asFn (alias) {
6+
Commands.addQuery('as', function asFn (alias, options = {} as Partial<Cypress.AsOptions>) {
67
Cypress.ensure.isChildCommand(this, [alias], cy)
78
cy.validateAlias(alias)
89

10+
if (!_.isPlainObject(options)) {
11+
$errUtils.throwErrByPath('as.invalid_options', { args: { arg: options } })
12+
}
13+
14+
if (options.type && !['query', 'static'].includes(options.type)) {
15+
$errUtils.throwErrByPath('as.invalid_options_type', { args: { type: options.type } })
16+
}
17+
918
const prevCommand = cy.state('current').get('prev')
1019

1120
prevCommand.set('alias', alias)
1221

1322
// Shallow clone of the existing subject chain, so that future commands running on the same chainer
1423
// don't apply here as well.
15-
const subjectChain = [...cy.subjectChain()]
24+
let subjectChain = [...cy.subjectChain()]
25+
26+
// If the user wants us to store a specific static value, rather than
27+
// requery it live, we replace the subject chain with a resolved value.
28+
// https://github.com/cypress-io/cypress/issues/25173
29+
if (options.type === 'static') {
30+
subjectChain = [cy.getSubjectFromChain(subjectChain)]
31+
}
1632

1733
const fileName = prevCommand.get('fileName')
1834

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

4359
if (!alreadyAliasedLog && log) {
4460
log.set({
45-
alias,
61+
alias: `@${alias}${options.type === 'static' ? ` (${ options.type })` : ''}`,
4662
aliasType: $dom.isElement(subject) ? 'dom' : 'primitive',
4763
})
4864
}

packages/driver/src/cypress/error_messages.ts

+2
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ export default {
141141
reserved_word: {
142142
message: `${cmd('as')} cannot be aliased as: \`{{alias}}\`. This word is reserved.`,
143143
},
144+
invalid_options: `${cmd('as')} only accepts an options object for its second argument. You passed: \`{{arg}}\``,
145+
invalid_options_type: `${cmd('as')} only accepts a \`type\` of \`'query'\` or \`'static'\`. You passed: \`{{type}}\``,
144146
},
145147

146148
blur: {

0 commit comments

Comments
 (0)