chore: Deprecate @cypress/xpath package (#26893)

This commit is contained in:
Jennifer Shehane
2023-06-01 11:09:32 -04:00
committed by GitHub
parent 0af6936c63
commit 7ef4300a6b
17 changed files with 1 additions and 584 deletions

View File

@@ -1944,20 +1944,6 @@ jobs:
command: yarn workspace @cypress/mount-utils build
- store-npm-logs
npm-xpath:
<<: *defaults
resource_class: small
steps:
- restore_cached_workspace
- run:
name: Run tests
command: yarn workspace @cypress/xpath cy:run
- store_test_results:
path: npm/xpath/test_results
- store_artifacts:
path: npm/xpath/test_results
- store-npm-logs
npm-grep:
<<: *defaults
resource_class: small

2
.gitignore vendored
View File

@@ -78,8 +78,6 @@ system-tests/lib/fixtureDirs.ts
# from npm/webpack-dev-server
/npm/webpack-dev-server/cypress/videos
# from npm/xpath
/npm/xpath/cypress/videos
# from npm/grep
/npm/grep/cypress/videos

View File

@@ -14,4 +14,3 @@
- [`@cypress/webpack-batteries-included-preprocessor`](https://github.com/cypress-io/cypress/blob/develop/npm/webpack-batteries-included-preprocessor/CHANGELOG.md)
- [`@cypress/webpack-dev-server`](https://github.com/cypress-io/cypress/blob/develop/npm/webpack-dev-server/CHANGELOG.md)
- [`@cypress/webpack-preprocessor`](https://github.com/cypress-io/cypress/blob/develop/npm/webpack-preprocessor/CHANGELOG.md)
- [`@cypress/xpath`](https://github.com/cypress-io/cypress/blob/develop/npm/xpath/CHANGELOG.md)

View File

@@ -197,7 +197,6 @@ Here is a list of the npm packages in this repository:
| [webpack-batteries-included-preprocessor](./npm/webpack-batteries-included-preprocessor) | `@cypress/webpack-batteries-included-preprocessor` | Cypress preprocessor for bundling JavaScript via webpack with dependencies included and support for various ES features, TypeScript, and CoffeeScript. |
| [webpack-dev-server](./npm/webpack-dev-server) | `@cypress/webpack-dev-server` | Webpack powered dev server for Component Testing. |
| [webpack-preprocessor](./npm/webpack-preprocessor) | `@cypress/webpack-preprocessor` | Cypress preprocessor for bundling JavaScript via webpack. |
| [xpath](./npm/xpath) | `@cypress/xpath` | Adds XPath command to Cypress.io test runner |
We try to tag all issues with a `pkg/` or `npm/` tag describing the appropriate package the work is required in. For public packages, we use their qualified package name: For example, issues relating to the webpack preprocessor are tagged under [`npm: @cypress/webpack-preprocessor`](https://github.com/cypress-io/cypress/labels/npm%3A%20%40cypress%2Fwebpack-preprocessor) label and issues related to the `driver` package are tagged with the [`pkg/driver`](https://github.com/cypress-io/cypress/labels/pkg%2Fdriver) label.

View File

@@ -1,14 +0,0 @@
{
"plugins": [
"cypress"
],
"extends": [
"plugin:@cypress/dev/tests"
],
"env": {
"cypress/globals": true
},
"rules": {
"no-console": "off"
}
}

View File

@@ -1,3 +0,0 @@
module.exports = {
...require('../../.releaserc'),
}

View File

@@ -1,13 +0,0 @@
# [@cypress/xpath-v2.0.3](https://github.com/cypress-io/cypress/compare/@cypress/xpath-v2.0.2...@cypress/xpath-v2.0.3) (2022-10-26)
### Bug Fixes
* **xpath:** update xpath main path ([#24259](https://github.com/cypress-io/cypress/issues/24259)) ([edf99c4](https://github.com/cypress-io/cypress/commit/edf99c41d6031c7a72ad2258f4fd29231823790c))
# [@cypress/xpath-v2.0.2](https://github.com/cypress-io/cypress/compare/@cypress/xpath-v2.0.1...@cypress/xpath-v2.0.2) (2022-09-29)
### Bug Fixes
* release svelte ([b86403f](https://github.com/cypress-io/cypress/commit/b86403fcbcc85ce5be1ca96bbf42357dd24c07dd))

View File

@@ -1,82 +1,3 @@
# @cypress/xpath
> Adds XPath command to [Cypress.io](https://www.cypress.io) test runner
## Install with npm
```shell
npm install -D @cypress/xpath
```
## Install with Yarn
```shell
yarn add @cypress/xpath --dev
```
Then include in your project's [support file](https://on.cypress.io/support-file)
```js
require('@cypress/xpath');
```
## Use
After installation your `cy` object will have `xpath` command.
```js
it('finds list items', () => {
cy.xpath('//ul[@class="todo-list"]//li').should('have.length', 3);
});
```
You can also chain `xpath` off of another command.
```js
it('finds list items', () => {
cy.xpath('//ul[@class="todo-list"]').xpath('./li').should('have.length', 3);
});
```
As with other cy commands, it is scoped by `cy.within()`.
```js
it('finds list items', () => {
cy.xpath('//ul[@class="todo-list"]').within(() => {
cy.xpath('./li').should('have.length', 3);
});
});
```
**note:** you can test XPath expressions from DevTools console using `$x(...)` function, for example `$x('//div')` to find all divs.
See [cypress/e2e/spec.cy.js](cypress/e2e/spec.cy.js)
## Beware the XPath // trap
In XPath the expression // means something very specific, and it might not be what you think. Contrary to common belief, // means "anywhere in the document" not "anywhere in the current context". As an example:
```js
cy.xpath('//body').xpath('//script');
```
You might expect this to find all script tags in the body, but actually, it finds all script tags in the entire document, not only those in the body! What you're looking for is the .// expression which means "any descendant of the current node":
```js
cy.xpath('//body').xpath('.//script');
```
The same thing goes for within:
```js
cy.xpath('//body').within(() => {
cy.xpath('.//script');
});
```
For more, see [Intelligent Code Completion](https://on.cypress.io/intellisense)
## License
This project is licensed under the terms of the [MIT license](/LICENSE.md).
> @cypress/xpath has been deprecated and is no longer supported.

View File

@@ -1,12 +0,0 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
excludeSpecPattern: '*.html',
supportFile: 'cypress/support/e2e.js',
},
component: {
excludeSpecPattern: '*.html',
supportFile: 'cypress/support/e2e.js',
},
})

View File

@@ -1,26 +0,0 @@
<body>
<main>
<h1>cypress-xpath</h1>
<button id="first-button">Click me</button>
<div>
<input id="name" type="text" placeholder="Your name?" />
<span id="greeting"></span>
</div>
</main>
<script>
document.getElementById('first-button').addEventListener('click', () => {
window.alert('you clicked first button')
})
setTimeout(() => {
document
.querySelector('main')
.insertAdjacentHTML(
'afterend',
'<div id="inserted">inserted text</div>'
)
}, 1000)
document.getElementById('name').addEventListener('input', e => {
document.getElementById('greeting').innerText = `Hello, ${e.target.value}`
})
</script>
</body>

View File

@@ -1,183 +0,0 @@
/// <reference types="cypress" />
/// <reference path="../../src/index.d.ts" />
describe('cypress-xpath', () => {
it('adds xpath command', () => {
expect(cy).property('xpath').to.be.a('function')
})
context('elements', () => {
beforeEach(() => {
cy.visit('cypress/e2e/index.html')
})
it('finds h1', () => {
cy.xpath('//h1').should('have.length', 1)
})
it('returns jQuery wrapped elements', () => {
cy.xpath('//h1').then((el$) => {
expect(el$).to.have.property('jquery')
})
})
it('returns primitives as is', () => {
cy.xpath('string(//h1)').then((el$) => {
expect(el$).to.not.have.property('jquery')
})
})
it('provides jQuery wrapped elements to assertions', () => {
cy.xpath('//h1').should((el$) => {
expect(el$).to.have.property('jquery')
})
})
it('gets h1 text', () => {
cy.xpath('//h1/text()')
.its('0.textContent')
.should('equal', 'cypress-xpath')
})
it('retries until element is inserted', () => {
// the element will be inserted after 1 second
cy.xpath('string(//*[@id="inserted"])').should('equal', 'inserted text')
})
describe('chaining', () => {
it('finds h1 within main', () => {
// first assert that h1 doesn't exist as a child of the implicit document subject
cy.xpath('./h1').should('not.exist')
cy.xpath('//main').xpath('./h1').should('exist')
})
it('finds body outside of main when succumbing to // trap', () => {
// first assert that body doesn't actually exist within main
cy.xpath('//main').xpath('.//body').should('not.exist')
cy.xpath('//main').xpath('//body').should('exist')
})
it('finds h1 within document', () => {
cy.document().xpath('//h1').should('exist')
})
it('throws when subject is more than a single element', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq(
'xpath() can only be called on a single element. Your subject contained 2 elements.',
)
done()
})
cy.get('main, div').xpath('foo')
})
})
describe('within()', () => {
it('finds h1 within within-subject', () => {
// first assert that h1 doesn't exist as a child of the implicit document subject
cy.xpath('./h1').should('not.exist')
cy.xpath('//main').within(() => {
cy.xpath('./h1').should('exist')
})
})
it('finds body outside of within-subject when succumbing to // trap', () => {
// first assert that body doesn't actually exist within main
cy.xpath('//main').within(() => {
cy.xpath('.//body').should('not.exist')
})
cy.xpath('//main').within(() => {
cy.xpath('//body').should('exist')
})
})
})
describe('primitives', () => {
it('counts h1 elements', () => {
cy.xpath('count(//h1)').should('equal', 1)
})
it('returns h1 text content', () => {
cy.xpath('string(//h1)').should('equal', 'cypress-xpath')
})
it('returns boolean', () => {
cy.xpath('boolean(//h1)').should('be.true')
cy.xpath('boolean(//h2)').should('be.false')
})
})
describe('typing', () => {
it('works on text input', () => {
cy.xpath('//*[@id="name"]').type('World')
cy.contains('span#greeting', 'Hello, World')
})
})
describe('clicking', () => {
it('on button', () => {
// this button invokes window.alert when clicked
const alert = cy.stub()
cy.on('window:alert', alert)
cy.xpath('//*[@id="first-button"]')
.click()
.then(() => {
expect(alert).to.have.been.calledOnce
})
})
})
})
context('logging', () => {
beforeEach(() => {
cy.visit('cypress/e2e/index.html')
})
it('should log by default', () => {
cy.spy(Cypress, 'log').log(false)
cy.xpath('//h1').then(() => {
expect(Cypress.log).to.be.calledWithMatch({ name: 'xpath' })
})
})
it('logs the selector when not found', (done) => {
cy.xpath('//h1') // does exist
cy.on('fail', (e) => {
const isExpectedErrorMessage = (message) => {
return message.includes('Timed out retrying') &&
message.includes(
'Expected to find element: `//h2`, but never found it.',
)
}
if (!isExpectedErrorMessage(e.message)) {
console.error('Cypress test failed with an unexpected error message')
console.error(e)
return done(e)
}
// no errors, the error message for not found selector is correct
done()
})
cy.xpath('//h2', { timeout: 100 }) // does not exist
})
it('should not log when provided log: false', () => {
cy.spy(Cypress, 'log').log(false)
cy.xpath('//h1', { log: false }).then(() => {
expect(Cypress.log).to.not.be.calledWithMatch({ name: 'xpath' })
})
})
})
})

View File

@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -1,16 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
import '../../src'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 KiB

View File

@@ -1,33 +0,0 @@
{
"name": "@cypress/xpath",
"version": "0.0.0-development",
"description": "Adds XPath command to Cypress.io test runner",
"main": "src",
"scripts": {
"lint": "eslint . --ext .ts,.js",
"cy:run": "node ../../scripts/cypress.js run --e2e",
"cy:open": "node ../../scripts/cypress.js open --e2e --project ${PWD}"
},
"files": [
"src"
],
"types": "src",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/cypress-io/cypress.git"
},
"homepage": "https://github.com/cypress-io/cypress/tree/develop/npm/xpath#readme",
"author": "Cypress Tools Team",
"bugs": {
"url": "https://github.com/cypress-io/cypress/issues"
},
"keywords": [
"cypress",
"cypress-io",
"xpath"
],
"publishConfig": {
"access": "public"
}
}

View File

@@ -1,16 +0,0 @@
/// <reference types="cypress" />
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
/**
* Get one or more DOM elements by an XPath selector.
* **Note:** you can test XPath expressions from DevTools console using $x(...) function, for example $x('//div') to find all divs.
* @see https://github.com/cypress-io/cypress-xpath
* @example
* cy.xpath(`//ul[@class="todo-list"]//li`)
* .should('have.length', 3)
*/
xpath<E extends Node = HTMLElement>(selector: string, options?: Partial<Loggable & Timeoutable>): Chainable<JQuery<E>>
}
}

View File

@@ -1,165 +0,0 @@
/* eslint-disable no-redeclare */
/// <reference types="cypress" />
/**
* Adds XPath support to Cypress using a custom command.
*
* @see https://devhints.io/xpath
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_using_XPath_in_JavaScript
* @example
```js
it('finds list items', () => {
cy.xpath('//ul[@class="todo-list"]//li')
.should('have.length', 3)
})
```
*/
const xpath = (subject, selector, options = {}) => {
/* global XPathResult */
const isNumber = (xpathResult) => {
return xpathResult.resultType === XPathResult.NUMBER_TYPE
}
const numberResult = (xpathResult) => xpathResult.numberValue
const isString = (xpathResult) => {
return xpathResult.resultType === XPathResult.STRING_TYPE
}
const stringResult = (xpathResult) => xpathResult.stringValue
const isBoolean = (xpathResult) => {
return xpathResult.resultType === XPathResult.BOOLEAN_TYPE
}
const booleanResult = (xpathResult) => xpathResult.booleanValue
const isPrimitive = (x) => {
return Cypress._.isNumber(x) || Cypress._.isString(x) || Cypress._.isBoolean(x)
}
// options to log later
const log = {
name: 'xpath',
message: selector,
}
if (Cypress.dom.isElement(subject) && subject.length > 1) {
throw new Error(
`xpath() can only be called on a single element. Your subject contained ${
subject.length
} elements.`,
)
}
const getValue = () => {
let nodes = []
let contextNode
let withinSubject = cy.state('withinSubject')
if (Cypress.dom.isElement(subject)) {
contextNode = subject[0]
} else if (Cypress.dom.isDocument(subject)) {
contextNode = subject
} else if (withinSubject) {
contextNode = withinSubject[0]
} else {
contextNode = cy.state('window').document
}
let iterator = (contextNode.ownerDocument || contextNode).evaluate(
selector,
contextNode,
)
if (isNumber(iterator)) {
const result = numberResult(iterator)
log.consoleProps = () => {
return {
XPath: selector,
type: 'number',
result,
}
}
return result
}
if (isString(iterator)) {
const result = stringResult(iterator)
log.consoleProps = () => {
return {
XPath: selector,
type: 'string',
result,
}
}
return result
}
if (isBoolean(iterator)) {
const result = booleanResult(iterator)
log.consoleProps = () => {
return {
XPath: selector,
type: 'boolean',
result,
}
}
return result
}
try {
let node = iterator.iterateNext()
while (node) {
nodes.push(node)
node = iterator.iterateNext()
}
log.consoleProps = () => {
return {
XPath: selector,
result: nodes.length === 1 ? nodes[0] : nodes,
}
}
return nodes
} catch (e) {
console.error('Document tree modified during iteration', e)
return null
}
}
const resolveValue = () => {
return Cypress.Promise.try(getValue).then((value) => {
if (!isPrimitive(value)) {
value = Cypress.$(value)
// Add the ".selector" property because Cypress uses it for error messages
value.selector = selector
}
return cy.verifyUpcomingAssertions(value, options, {
onRetry: resolveValue,
})
})
}
return resolveValue().then((value) => {
if (options.log !== false) {
// TODO set found elements on the command log?
Cypress.log(log)
}
return value
})
}
Cypress.Commands.add(
'xpath',
{ prevSubject: ['optional', 'element', 'document'] },
xpath,
)