Skip to content

feat: Rename SelectorPlayground API to ElementSelector API #31889

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3e440ec
feat: Rename SelectorPriority API to ElementSelector API
jennifer-shehane Jun 13, 2025
139874d
Merge branch 'release/15.0.0' into selector-playground-api-to-element…
jennifer-shehane Jun 13, 2025
e07db39
bump unique-selector + remove restriction on type of prop
jennifer-shehane Jun 13, 2025
5b1eece
Merge branch 'selector-playground-api-to-element-selector' of https:/…
jennifer-shehane Jun 13, 2025
54538a8
rename file + remove test
jennifer-shehane Jun 13, 2025
2ce3309
rename and add types to files
jennifer-shehane Jun 13, 2025
080d9d6
save file
jennifer-shehane Jun 13, 2025
770af77
fix types
jennifer-shehane Jun 16, 2025
6866162
Add error when Cypress.SelectorPlayground is called to suggest renaming
jennifer-shehane Jun 16, 2025
e3c103e
Add better description of ElementSelector API
jennifer-shehane Jun 16, 2025
c31f27d
Error on invalid selector priority types
jennifer-shehane Jun 17, 2025
f002750
changelog entry
jennifer-shehane Jun 18, 2025
72e8260
Merge branch 'release/15.0.0' into selector-playground-api-to-element…
jennifer-shehane Jun 18, 2025
d792b66
Update types to match exact type of selectorPriority
jennifer-shehane Jun 20, 2025
ea5816d
remove semicolon
jennifer-shehane Jun 20, 2025
3fa5bdb
Add feature description for separate feature
jennifer-shehane Jun 20, 2025
c909ecb
remove the other semicolon
jennifer-shehane Jun 20, 2025
8d16f65
Update types to use official cypress types
jennifer-shehane Jun 23, 2025
8330fa3
Add other issue that this PR resolves.
jennifer-shehane Jun 24, 2025
54b71a7
add name to selector priority
jennifer-shehane Jun 24, 2025
bd5a7f9
alphabetize strings in selectorPriority types
jennifer-shehane Jun 24, 2025
d1c4948
Merge branch 'release/15.0.0' into selector-playground-api-to-element…
jennifer-shehane Jun 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ _Released 07/15/2025 (PENDING)_
- `@cypress/webpack-dev-server` no longer supports `webpack-dev-server` version 4. Addresses [#31605](https://github.com/cypress-io/cypress/issues/31605). If you still need to use `webpack-dev-server` version 4, please see our [migration guide](https://docs.cypress.io/app/references/migration-guide#Migrating-to-Cypress-150).
- In order to better align with best practices, `@cypress/webpack-batteries-included-preprocessor` no longer includes certain browser built-ins that were automatically provided by Webpack 4. The removed built-ins are `assert`, `constants`, `crypto`, `domain`, `events`, `http`, `https`, `punycode`, `querystring`, `string_decoder`, `sys`, `timers`, `tty`, `url`, `util`, `vm`, and `zlib`. However, we know that certain built-ins are popular, given that many users have files that are shared between their Cypress tests and node context. Because of this, `@cypress/webpack-batteries-included-preprocessor` will ship with built-in support for `buffer`, `path`, `process`, `os`, and `stream`. If there is a built-in that isn't supported be default and you need to add support, please refer to the Webpack [resolve.fallback](https://webpack.js.org/configuration/resolve/#resolvefallback) documentation and the [`@cypress/webpack-batteries-included-preprocessor` README](../npm/webpack-batteries-included-preprocessor/README.md). Addresses [#31039](https://github.com/cypress-io/cypress/issues/31039).
- The application under test's `pagehide` event in Chromium browsers will no longer trigger Cypress's `window:unload` event. Addressed in [#31853](https://github.com/cypress-io/cypress/pull/31853).
- The `Cypress.SelectorPlayground` API has been renamed to `Cypress.ElementSelector`. This API was renamed to accommodate its use for defining `selectorPriority` in Cypress Studio and our future [`cy.prompt` release](https://on.cypress.io/cy-prompt-early-access?utm_source=docs&utm_medium=app-changelog&utm_content=cy-prompt-release). Addresses [#31801](https://github.com/cypress-io/cypress/issues/31801). Addressed in [#31889](https://github.com/cypress-io/cypress/pull/31889).
- **Component Testing breaking changes:**
- Removed support for Angular 17. The minimum supported version is now `18.0.0`. Addresses [#31303](https://github.com/cypress-io/cypress/issues/31303).
- `@cypress/angular` now requires a minimum of `zone.js` `0.14.0`. Addresses [#31582](https://github.com/cypress-io/cypress/issues/31582).
Expand All @@ -20,6 +21,7 @@ _Released 07/15/2025 (PENDING)_
**Features:**

- [`cy.url()`](https://docs.cypress.io/api/commands/url), [`cy.hash()`](https://docs.cypress.io/api/commands/hash), [`cy.go()`](https://docs.cypress.io/api/commands/go), [`cy.reload()`](https://docs.cypress.io/api/commands/reload), [`cy.title()`](https://docs.cypress.io/api/commands/title), and [`cy.location()`](https://docs.cypress.io/api/commands/location) now use the automation client (CDP for Chromium browsers and WebDriver BiDi for Firefox) to return the appropriate values from the commands to the user instead of the window object. This is to avoid cross origin issues with [`cy.origin()`](https://docs.cypress.io/api/commands/origin) so these commands can be invoked anywhere inside a Cypress test without having to worry about origin access issues. Experimental Webkit still will use the window object to retrieve these values. Also, [`cy.window()`](https://docs.cypress.io/api/commands/window) will always return the current window object, regardless of origin restrictions. Not every property from the window object will be accessible depending on the origin context. Addresses [#31196](https://github.com/cypress-io/cypress/issues/31196).
- Selectors accepted in the `selectorPriority` of the `SelectorPlayground` (renamed to `ElementSelector`) API have been expanded to accept `name` and `attributes:*`. Additionally, the default selector priority used by Cypress now includes `name`. Addresses [#31801](https://github.com/cypress-io/cypress/issues/30309) and [#6876](https://github.com/cypress-io/cypress/issues/6876). Addressed in [#31889](https://github.com/cypress-io/cypress/pull/31889).
- [`tsx`](https://tsx.is/) is now used in all cases to run the Cypress config, replacing [ts-node](https://github.com/TypeStrong/ts-node) for TypeScript and Node for commonjs/ESM. This should allow for more interoperability for users who are using any variant of ES Modules. Addresses [#8090](https://github.com/cypress-io/cypress/issues/8090), [#15724](https://github.com/cypress-io/cypress/issues/15724), [#21805](https://github.com/cypress-io/cypress/issues/21805), [#22273](https://github.com/cypress-io/cypress/issues/22273), [#22747](https://github.com/cypress-io/cypress/issues/22747), [#23141](https://github.com/cypress-io/cypress/issues/23141), [#25958](https://github.com/cypress-io/cypress/issues/25958), [#25959](https://github.com/cypress-io/cypress/issues/25959), [#26606](https://github.com/cypress-io/cypress/issues/26606), [#27359](https://github.com/cypress-io/cypress/issues/27359), [#27450](https://github.com/cypress-io/cypress/issues/27450), [#28442](https://github.com/cypress-io/cypress/issues/28442), [#30318](https://github.com/cypress-io/cypress/issues/30318), [#30718](https://github.com/cypress-io/cypress/issues/30718), [#30907](https://github.com/cypress-io/cypress/issues/30907), [#30915](https://github.com/cypress-io/cypress/issues/30915), [#30925](https://github.com/cypress-io/cypress/issues/30925), [#30954](https://github.com/cypress-io/cypress/issues/30954) and [#31185](https://github.com/cypress-io/cypress/issues/31185).

**Misc:**
Expand Down
32 changes: 26 additions & 6 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -696,10 +696,21 @@ declare namespace Cypress {
}

/**
* @see https://on.cypress.io/selector-playground-api
* Element selector logic used for generating selectors for elements
* in the Selector Playground and Cypress Studio.
* @see https://on.cypress.io/element-selector-api
*/
ElementSelector: {
defaults(options: Partial<ElementSelectorDefaultsOptions>): void
getSelector($el: JQuery): JQuery.Selector
}

/**
* @deprecated Use ElementSelector instead
* @see https://on.cypress.io/element-selector-api
*/
SelectorPlayground: {
defaults(options: Partial<SelectorPlaygroundDefaultsOptions>): void
defaults(options: Partial<ElementSelectorDefaultsOptions>): void
getSelector($el: JQuery): JQuery.Selector
}

Expand Down Expand Up @@ -2971,7 +2982,7 @@ declare namespace Cypress {
*/
requestTimeout: number
/**
* Time, in milliseconds, to wait until a response in a [cy.request()](https://on.cypress.io/request), [cy.wait()](https://on.cypress.io/wait), [cy.fixture()](https://on.cypress.io/fixture), [cy.getCookie()](https://on.cypress.io/getcookie), [cy.getCookies()](https://on.cypress.io/getcookies), [cy.setCookie()](https://on.cypress.io/setcookie), [cy.clearCookie()](https://on.cypress.io/clearcookie), [cy.clearCookies()](https://on.cypress.io/clearcookies), and [cy.screenshot()](https://on.cypress.io/screenshot) commands
* Time, in milliseconds, to wait for a response in a [cy.request()](https://on.cypress.io/request), [cy.wait()](https://on.cypress.io/wait), [cy.fixture()](https://on.cypress.io/fixture), [cy.getCookie()](https://on.cypress.io/getcookie), [cy.getCookies()](https://on.cypress.io/getcookies), [cy.setCookie()](https://on.cypress.io/setcookie), [cy.clearCookie()](https://on.cypress.io/clearcookie), [cy.clearCookies()](https://on.cypress.io/clearcookies), and [cy.screenshot()](https://on.cypress.io/screenshot) commands
* @default 30000
*/
responseTimeout: number
Expand Down Expand Up @@ -3714,9 +3725,18 @@ declare namespace Cypress {
screenshotOnRunFailure: boolean
}

interface SelectorPlaygroundDefaultsOptions {
selectorPriority: string[]
onElement: ($el: JQuery) => string | null | undefined
type SelectorPriority =
| `attribute:${string}`
| 'attributes'
| 'class'
| `data-${string}`
| 'id'
| 'name'
| 'nth-child'
| 'tag'

interface ElementSelectorDefaultsOptions {
selectorPriority?: SelectorPriority[]
}

interface ScrollToOptions extends Loggable, Timeoutable {
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/runner/aut-iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ export class AutIframe {

const Cypress = this.eventManager.getCypress()

const selector = Cypress.SelectorPlayground.getSelector($el)
const selector = Cypress.ElementSelector.getSelector($el)
const selectorPlaygroundStore = useSelectorPlaygroundStore()

this._addOrUpdateSelectorPlaygroundHighlight({
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/store/studio-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ export const useStudioStore = defineStore('studioRecorder', {
if (name === 'click' && this._matchPreviousMouseEvent($el)) {
selector = this._previousMouseEvent?.selector
} else {
selector = getCypress().SelectorPlayground.getSelector($el)
selector = getCypress().ElementSelector.getSelector($el)
}

this._clearPreviousMouseEvent()
Expand Down Expand Up @@ -440,7 +440,7 @@ export const useStudioStore = defineStore('studioRecorder', {

_addAssertion ($el: HTMLElement | JQuery<HTMLElement>, ...args: AssertionArgs) {
const id = this._getId()
const selector = getCypress().SelectorPlayground.getSelector($el)
const selector = getCypress().ElementSelector.getSelector($el)

const log: StudioLog = {
id,
Expand Down Expand Up @@ -666,7 +666,7 @@ export const useStudioStore = defineStore('studioRecorder', {
if (!this._matchPreviousMouseEvent(target)) {
this._previousMouseEvent = {
element: target,
selector: getCypress().SelectorPlayground.getSelector(window.UnifiedRunner.CypressJQuery(target)),
selector: getCypress().ElementSelector.getSelector(window.UnifiedRunner.CypressJQuery(target)),
}
}
},
Expand Down
150 changes: 150 additions & 0 deletions packages/driver/cypress/e2e/cypress/element_selector.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/// <reference types="cypress" />
Copy link
Member Author

Choose a reason for hiding this comment

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

Converted to TypeScript - the tests are the same except for removing tests around onElement

import type { ElementSelectorAPI } from '../../../src/cypress/element_selector'
import { DEFAULT_SELECTOR_PRIORITIES } from '../../../src/cypress/element_selector'
const { $: $cypress } = Cypress.$Cypress
const ElementSelector = Cypress.ElementSelector as ElementSelectorAPI

const SELECTOR_DEFAULTS: Cypress.SelectorPriority[] = [...DEFAULT_SELECTOR_PRIORITIES]

describe('src/cypress/element_selector', () => {
beforeEach(() => {
ElementSelector.reset()
})

it('has defaults', () => {
expect(ElementSelector.getSelectorPriority()).to.deep.eq(SELECTOR_DEFAULTS)
})

context('.defaults', () => {
it('is noop if not called with selectorPriority', () => {
ElementSelector.defaults({})
expect(ElementSelector.getSelectorPriority()).to.deep.eq(SELECTOR_DEFAULTS)
})

it('sets element:selector:priority if selectorPriority specified', () => {
const selectorPriority: Cypress.SelectorPriority[] = [
'data-1',
'data-2',
'id',
'class',
'tag',
'attributes',
'nth-child',
'name',
'attribute:aria-label',
'attribute:aria-labelledby',
]

ElementSelector.defaults({
selectorPriority,
})

expect(ElementSelector.getSelectorPriority()).to.eql(selectorPriority)
})

it('throws if not passed an object', () => {
const fn = () => {
ElementSelector.defaults(undefined as any)
}

expect(fn).to.throw()
.with.property('message')
.and.include('`Cypress.ElementSelector.defaults()` must be called with an object. You passed: ')

expect(fn).to.throw()
.with.property('docsUrl')
.and.include('https://on.cypress.io/element-selector-api')
})

it('throws if selectorPriority is not an array', () => {
const fn = () => {
ElementSelector.defaults({ selectorPriority: 'foo' as any })
}

expect(fn).to.throw()
.with.property('message')
.and.include('`Cypress.ElementSelector.defaults()` called with invalid `selectorPriority` property. It must be an array. You passed: `foo`')

expect(fn).to.throw()
.with.property('docsUrl')
.and.include('https://on.cypress.io/element-selector-api')
})

it('throws if selectorPriority contains an unsupported priority', () => {
const fn = () => {
ElementSelector.defaults({
selectorPriority: [
'id',
// @ts-expect-error - invalid priority
'attr',
],
})
}

expect(fn).to.throw()
.with.property('message')
.and.include('`Cypress.ElementSelector.defaults()` called with invalid `selectorPriority` property. It must be one of: `data-*`, `attribute:*`, `id`, `class`, `tag`, `name`,`attributes`, or `nth-child`. You passed: `attr`')
})

it('throws if selectorPriority has an unsupported priority that contains a substring of a valid priority', () => {
const fn = () => {
ElementSelector.defaults({
selectorPriority: [
// @ts-expect-error - invalid priority
'idIsNotValid',
],
})
}

expect(fn).to.throw()
.with.property('message')
.and.include('`Cypress.ElementSelector.defaults()` called with invalid `selectorPriority` property. It must be one of: `data-*`, `attribute:*`, `id`, `class`, `tag`, `name`,`attributes`, or `nth-child`. You passed: `idIsNotValid`')
})
})

context('.getSelector', () => {
it('uses defaults.selectorPriority', () => {
const $div = $cypress('<div data-cy=\'main button 123\' data-foo-bar-baz=\'quux\' data-test=\'qwerty\' data-foo=\'bar\' />')

Cypress.$('body').append($div)

expect(ElementSelector.getSelector($div)).to.eq('[data-cy="main button 123"]')

ElementSelector.defaults({
selectorPriority: ['data-foo'],
})

expect(ElementSelector.getSelector($div)).to.eq('[data-foo="bar"]')
})
})

describe('Cypress.SelectorPlayground (renamed)', () => {
it('throws error when calling defaults()', () => {
const fn = () => {
Cypress.SelectorPlayground.defaults({})
}

expect(fn).to.throw()
.with.property('message')
.and.include('`Cypress.SelectorPlayground.defaults()` has been renamed to `Cypress.ElementSelector.defaults()`')

expect(fn).to.throw()
.with.property('message')
.and.include('Please update your code to use `Cypress.ElementSelector` instead')
})

it('throws error when calling getSelector()', () => {
const fn = () => {
Cypress.SelectorPlayground.getSelector($cypress('body'))
}

expect(fn).to.throw()
.with.property('message')
.and.include('`Cypress.SelectorPlayground.getSelector()` has been renamed to `Cypress.ElementSelector.getSelector()`')

expect(fn).to.throw()
.with.property('message')
.and.include('Please update your code to use `Cypress.ElementSelector` instead')
})
})
})
Loading
Loading