This commit is contained in:
2024-11-28 23:08:17 +01:00
parent 8895fde030
commit 0dda8e760c
16116 changed files with 2866428 additions and 71 deletions

View File

@@ -0,0 +1,12 @@
name: 'Add issue/PR to Triage Board'
on:
issues:
types:
- opened
pull_request_target:
types:
- opened
jobs:
add-to-triage-project-board:
uses: cypress-io/cypress/.github/workflows/triage_add_to_project.yml@develop
secrets: inherit

View File

@@ -0,0 +1,9 @@
name: 'Handle Comment Workflow'
on:
issue_comment:
types:
- created
jobs:
closed-issue-comment:
uses: cypress-io/cypress/.github/workflows/triage_handle_new_comments.yml@develop
secrets: inherit

1
node_modules/eslint-plugin-cypress/.husky/pre-commit generated vendored Executable file
View File

@@ -0,0 +1 @@
npm run lint

1
node_modules/eslint-plugin-cypress/.node-version generated vendored Normal file
View File

@@ -0,0 +1 @@
20.12.2

1
node_modules/eslint-plugin-cypress/.nvmrc generated vendored Normal file
View File

@@ -0,0 +1 @@
20.12.2

50
node_modules/eslint-plugin-cypress/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,50 @@
# Contributing to cypress-io/eslint-plugin-cypress
Thanks for taking the time to contribute! :smile:
## Preparation
* Fork and clone this repository
* Branch from the default `master` branch using a descriptive new branch name
* Install dependencies with `npm ci`
## Rule references
* Refer to the [ESLint documentation](https://eslint.org/docs/latest/) and the [Custom Rules](https://eslint.org/docs/latest/extend/custom-rules) page
## New rule
To add a new rule:
* Follow the instructions in the ESLint [generator-eslint](https://www.npmjs.com/package/generator-eslint) documentation to install [Yeoman](https://www.npmjs.com/package/yo) and the generator
* Run the new rule generator `yo eslint:rule` and answer the questions
- select "ESLint Plugin"
- for "Type a short description of this rule" provide text which starts with one of "enforce", "require" or "disallow" (all lower case)
* Yeoman creates three boilerplate files:
- `docs/rules/<rule-id>.md`
- `lib/rules/<rule-id>.js`
- `test/rules/<rule-id>.js`
* Run `npm run lint-fix`
* Address the linting errors by editing `lib/rules/<rule-id>.js`
- Add a `meta.messages` property (see [MessageIds](https://eslint.org/docs/latest/extend/custom-rules#messageids))
- Select the appropriate `meta.type` property using `problem`, `suggestion`, or `layout`
* Complete the new rule by adding content to the three files previously created
* Run `eslint-doc-generator` to generate automated documentation sections (see [Document generation](#document-generation) below)
* Review documentation changes
* Run `npm run lint`
* Run `npm test` to run [Jest](https://jestjs.io/) (or run `npm start` to run [Jest](https://jestjs.io/) in [watchAll](https://jestjs.io/docs/cli#--watchall) mode where it remains active and reruns when source changes are made)
* Make sure all tests are passing
* Add the rule to [legacy.js](https://github.com/cypress-io/eslint-plugin-cypress/blob/master/legacy.js) and to [flat.js](https://github.com/cypress-io/eslint-plugin-cypress/blob/master/lib/flat.js)
* Create a git commit with a commit message similar to: `feat: add rule <description>` (see [commit message conventions](https://github.com/semantic-release/semantic-release#commit-message-format))
* Create a PR from your branch
## Document generation
This plugin uses the ESLint [eslint-doc-generator](https://www.npmjs.com/package/eslint-doc-generator) to generate consistent documentation.
* Install with `npm install eslint-doc-generator -g`
* Run `eslint-doc-generator` in the root directory of the plugin
## Legacy tests
* The directory [tests-legacy](https://github.com/cypress-io/eslint-plugin-cypress/tree/master/tests-legacy) contains tests which are compatible with the legacy [ESLint v8 RuleTester](https://eslint.org/docs/v8.x/integrate/nodejs-api#ruletester) utility. It is not expected to add new rules to this set of tests.
* The directory [tests](https://github.com/cypress-io/eslint-plugin-cypress/tree/master/tests) is for tests compatible with the current [ESLint RuleTester](https://eslint.org/docs/latest/integrate/nodejs-api#ruletester).

147
node_modules/eslint-plugin-cypress/ESLINTRC-CONFIG.md generated vendored Normal file
View File

@@ -0,0 +1,147 @@
# Cypress ESLint Plugin - Legacy Config
This document supplements the [README](./README.md) document and describes how to use the Cypress ESLint Plugin (`eslint-plugin-cypress`) in a [deprecated ESLint legacy config environment](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated). The use of flat configurations with this plugin is described in the [README](./README.md) document.
Usage with ESLint `9.x` is described.
## Deprecations
The use of `eslintrc` configurations with `eslint-plugin-cypress` is deprecated and support will be removed in a future version of this plugin. This is tied in to the ESLint announcement in the blog post [Flat config rollout plans](https://eslint.org/blog/2023/10/flat-config-rollout-plans/) from October 2023 which describes that the `eslintrc` configuration system is planned to be removed in the future ESLint `v10.0.0`. Users are encouraged to migrate to using a [flat configuration](https://eslint.org/docs/latest/use/configure/configuration-files).
## Installation
Use a minimum ESLint `9.x`.
```shell
npm install eslint eslint-plugin-cypress --save-dev
```
or
```shell
yarn add eslint eslint-plugin-cypress --dev
```
## Usage
To use a deprecated configuration with ESLint `v9`, such as `.eslintrc.json`, you must set the `ESLINT_USE_FLAT_CONFIG` environment variable to `false` (see [ESLint v9 > Configuration Files (Deprecated)](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated)). The following examples use `json` format for the content of the configuration file:
```json
{
"plugins": [
"cypress"
]
}
```
You can add rules - see [Rules](./README.md#rules) for a list of the available rules:
```json
{
"rules": {
"cypress/no-assigning-return-values": "error",
"cypress/no-unnecessary-waiting": "error",
"cypress/assertion-before-screenshot": "warn",
"cypress/no-force": "warn",
"cypress/no-async-tests": "error",
"cypress/no-async-before": "error",
"cypress/no-pause": "error",
"cypress/no-debug": "error"
}
}
```
You can allow certain globals provided by Cypress:
```json
{
"env": {
"cypress/globals": true
}
}
```
## Recommended configuration
Use the recommended configuration and you can forego configuring _plugins_, _rules_, and _env_ individually. See [Rules](./README.md#rules) for which rules are included in the recommended configuration.
```json
{
"extends": [
"plugin:cypress/recommended"
]
}
```
## Rules
See the [Rules](./README.md#rules) list in the main [README](./README.md) document.
## Mocha and Chai
Cypress is built on top of [Mocha](https://on.cypress.io/guides/references/bundled-libraries#Mocha) and [Chai](https://on.cypress.io/guides/references/bundled-libraries#Chai). See the following sections for information on using ESLint plugins [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) and [eslint-plugin-chai-friendly](https://www.npmjs.com/package/eslint-plugin-chai-friendly) together with `eslint-plugin-cypress`.
## Mocha `.only` and `.skip`
During test spec development, [Mocha exclusive tests](https://mochajs.org/#exclusive-tests) `.only` or [Mocha inclusive tests](https://mochajs.org/#inclusive-tests) `.skip` may be used to control which tests are executed, as described in the Cypress documentation [Excluding and Including Tests](https://on.cypress.io/guides/core-concepts/writing-and-organizing-tests#Excluding-and-Including-Tests). To apply corresponding rules, you can install and use [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha). The rule [mocha/no-exclusive-tests](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-exclusive-tests.md) detects the use of `.only` and the [mocha/no-skipped-tests](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-skipped-tests.md) rule detects the use of `.skip`:
```sh
npm install --save-dev eslint-plugin-mocha
```
In your `.eslintrc.json`:
```json
{
"plugins": [
"cypress",
"mocha"
],
"rules": {
"mocha/no-exclusive-tests": "warn",
"mocha/no-skipped-tests": "warn"
}
}
```
Or you can simply use the `cypress/recommended` and `mocha/recommended` configurations together, for example:
```json
{
"extends": [
"plugin:cypress/recommended",
"plugin:mocha/recommended"
]
}
```
## Chai and `no-unused-expressions`
Using an assertion such as `expect(value).to.be.true` can fail the ESLint rule `no-unused-expressions` even though it's not an error in this case. To fix this, you can install and use [eslint-plugin-chai-friendly](https://www.npmjs.com/package/eslint-plugin-chai-friendly).
```sh
npm install --save-dev eslint-plugin-chai-friendly
```
In your `.eslintrc.json`:
```json
{
"plugins": [
"cypress",
"chai-friendly"
],
"rules": {
"no-unused-expressions": 0,
"chai-friendly/no-unused-expressions": 2
}
}
```
Or you can simply add its `recommended` config:
```json
{
"extends": ["plugin:chai-friendly/recommended"]
}
```

5
node_modules/eslint-plugin-cypress/FLAT-CONFIG.md generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Cypress ESLint Plugin - Flat Config
Please refer to the [README](./README.md) document where the previous contents of this document, describing how to use `eslint-plugin-cypress` with an ESLint `v9` (default) [flat configuration](https://eslint.org/docs/latest/use/configure/configuration-files), can now be found.
For instructions on using a deprecated [eslintrc-type](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated) config file from previous ESLint `v8` versions and below, please refer to the [ESLINTRC-CONFIG](./ESLINTRC-CONFIG.md) document.

21
node_modules/eslint-plugin-cypress/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Cypress.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

221
node_modules/eslint-plugin-cypress/README.md generated vendored Normal file
View File

@@ -0,0 +1,221 @@
# Cypress ESLint Plugin [![CircleCI](https://circleci.com/gh/cypress-io/eslint-plugin-cypress/tree/master.svg?style=svg)](https://circleci.com/gh/cypress-io/eslint-plugin-cypress/tree/master)
An [ESLint](https://eslint.org) plugin for your [Cypress](https://cypress.io) tests.
Note: If you installed ESLint globally then you must also install `eslint-plugin-cypress` globally.
## Installation
Prerequisites: [ESLint](https://www.npmjs.com/package/eslint) `v9`. Lower versions are no longer supported.
```sh
npm install eslint eslint-plugin-cypress --save-dev
```
or
```sh
yarn add eslint eslint-plugin-cypress --dev
```
## Usage
ESLint `v9` uses a [Flat config file](https://eslint.org/docs/latest/use/configure/configuration-files) format with filename `eslint.config.*js` by default. For instructions on using a deprecated [eslintrc-type](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated) config file from previous ESLint versions, please refer to the [ESLINTRC-CONFIG](./ESLINTRC-CONFIG.md) document.
To set up a flat configuration, add a file `eslint.config.mjs` to the root directory of your Cypress project and include the following instructions to import the available flat configurations using:
```shell
import pluginCypress from 'eslint-plugin-cypress/flat'
```
## Configurations
There are two specific flat configurations available:
| Configuration | Content |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `configs.globals` | defines globals `cy`, `Cypress`, `expect`, `assert` and `chai` used in Cypress test specs as well as `globals.browser` and `globals.mocha` from [globals](https://www.npmjs.com/package/globals). This version no longer specifies `languageOptions` for `ecmaVersion` and `sourceType` - see ESLint [JavaScript languageOptions](https://eslint.org/docs/latest/use/configure/language-options#specifying-javascript-options). There are no default rules enabled in this configuration. |
| `configs.recommended` | enables [recommended Rules](#rules). It includes also `configs.global` (see above). |
## Rules
These rules enforce some of the [best practices recommended for using Cypress](https://on.cypress.io/best-practices).
<!-- begin auto-generated rules list -->
💼 Configurations enabled in.\
✅ Set in the `recommended` configuration.
| Name | Description | 💼 |
| :----------------------------------------------------------------------- | :--------------------------------------------------------- | :- |
| [assertion-before-screenshot](docs/rules/assertion-before-screenshot.md) | require screenshots to be preceded by an assertion | |
| [no-assigning-return-values](docs/rules/no-assigning-return-values.md) | disallow assigning return values of `cy` calls | ✅ |
| [no-async-before](docs/rules/no-async-before.md) | disallow using `async`/`await` in Cypress `before` methods | |
| [no-async-tests](docs/rules/no-async-tests.md) | disallow using `async`/`await` in Cypress test cases | ✅ |
| [no-debug](docs/rules/no-debug.md) | disallow using `cy.debug()` calls | |
| [no-force](docs/rules/no-force.md) | disallow using `force: true` with action commands | |
| [no-pause](docs/rules/no-pause.md) | disallow using `cy.pause()` calls | |
| [no-unnecessary-waiting](docs/rules/no-unnecessary-waiting.md) | disallow waiting for arbitrary time periods | ✅ |
| [require-data-selectors](docs/rules/require-data-selectors.md) | require `data-*` attribute selectors | |
| [unsafe-to-chain-command](docs/rules/unsafe-to-chain-command.md) | disallow actions within chains | ✅ |
<!-- end auto-generated rules list -->
## Usage examples
In the following sections, different examples of possible configuration file contents are given, together with some brief explanations. Adapt these examples according to your needs.
### Cypress
All rules from `eslint-plugin-cypress` are available through `eslint-plugin-cypress/flat` and can be individually activated.
- [cypress/unsafe-to-chain-command](https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/unsafe-to-chain-command.md) is activated and set to `error`
```js
import pluginCypress from 'eslint-plugin-cypress/flat'
export default [
{
plugins: {
cypress: pluginCypress
},
rules: {
'cypress/unsafe-to-chain-command': 'error'
}
}
]
```
### Cypress recommended
The `eslint-plugin-cypress` [recommended rules](#rules) `configs.recommended` are activated, except for
- [cypress/no-unnecessary-waiting](https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-unnecessary-waiting.md) which is set to `off`
```js
import pluginCypress from 'eslint-plugin-cypress/flat'
export default [
pluginCypress.configs.recommended,
{
rules: {
'cypress/no-unnecessary-waiting': 'off'
}
}
]
```
### Cypress globals
The `configs.globals` are activated.
```js
import pluginCypress from 'eslint-plugin-cypress/flat'
export default [
pluginCypress.configs.globals
]
```
## Disable rules
You can disable specific rules per file, for a portion of a file, or for a single line. See the [ESLint rules](https://eslint.org/docs/latest/use/configure/rules#disabling-rules) documentation. For example ...
Disable the `cypress/no-unnecessary-waiting` rule for the entire file by placing this at the start of the file:
```js
/* eslint-disable cypress/no-unnecessary-waiting */
```
Disable the `cypress/no-unnecessary-waiting` rule for only a portion of the file:
```js
it('waits for a second', () => {
...
/* eslint-disable cypress/no-unnecessary-waiting */
cy.wait(1000)
/* eslint-enable cypress/no-unnecessary-waiting */
...
})
```
Disable the `cypress/no-unnecessary-waiting` rule for a specific line:
```js
it('waits for a second', () => {
...
cy.wait(1000) // eslint-disable-line cypress/no-unnecessary-waiting
...
})
```
You can also disable a rule for the next line:
```js
it('waits for a second', () => {
...
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000)
...
})
```
## Mocha and Chai
Cypress is built on top of [Mocha](https://on.cypress.io/guides/references/bundled-libraries#Mocha) and [Chai](https://on.cypress.io/guides/references/bundled-libraries#Chai). See the following sections for information on using ESLint plugins [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) and [eslint-plugin-chai-friendly](https://www.npmjs.com/package/eslint-plugin-chai-friendly) together with `eslint-plugin-cypress`.
## Mocha `.only` and `.skip`
During test spec development, [Mocha exclusive tests](https://mochajs.org/#exclusive-tests) `.only` or [Mocha inclusive tests](https://mochajs.org/#inclusive-tests) `.skip` may be used to control which tests are executed, as described in the Cypress documentation [Excluding and Including Tests](https://on.cypress.io/guides/core-concepts/writing-and-organizing-tests#Excluding-and-Including-Tests). To apply corresponding rules, you can install and use [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha). The rule [mocha/no-exclusive-tests](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-exclusive-tests.md) detects the use of `.only` and the [mocha/no-skipped-tests](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-skipped-tests.md) rule detects the use of `.skip`.
### Cypress and Mocha recommended
[eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) is added to the example [Cypress recommended](#cypress-recommended). This plugin offers a flat file recommended option `configs.flat.recommended`.
The settings for individual `mocha` rules from the `configs.flat.recommended` option are changed.
- [mocha/no-exclusive-tests](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-exclusive-tests.md) and [mocha/no-skipped-tests](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-skipped-tests.md) are set to `error` instead of `warn`
- [mocha/no-mocha-arrows](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-mocha-arrows.md) is set to `off` instead of `error`
```shell
npm install eslint-plugin-mocha@^10.5.0 --save-dev
```
```js
import pluginMocha from 'eslint-plugin-mocha'
import pluginCypress from 'eslint-plugin-cypress/flat'
export default [
pluginMocha.configs.flat.recommended,
pluginCypress.configs.recommended,
{
rules: {
'mocha/no-exclusive-tests': 'error',
'mocha/no-skipped-tests': 'error',
'mocha/no-mocha-arrows': 'off',
'cypress/no-unnecessary-waiting': 'off'
}
}
]
```
### Cypress and Chai recommended
Using an assertion such as `expect(value).to.be.true` can fail the ESLint rule `no-unused-expressions` even though it's not an error in this case. To fix this, you can install and use [eslint-plugin-chai-friendly](https://www.npmjs.com/package/eslint-plugin-chai-friendly).
[eslint-plugin-chai-friendly](https://www.npmjs.com/package/eslint-plugin-chai-friendly) is combined with the Cypress plugin `eslint-plugin-cypress`.
The recommended rules for both plugins are used: `pluginCypress.configs.recommended` and `pluginChaiFriendly.configs.recommendedFlat`:
```shell
npm install eslint-plugin-chai-friendly@^1.0.1 --save-dev
```
```js
import pluginCypress from 'eslint-plugin-cypress/flat'
import pluginChaiFriendly from 'eslint-plugin-chai-friendly'
export default [
pluginCypress.configs.recommended,
pluginChaiFriendly.configs.recommendedFlat,
{
rules: {
'cypress/no-unnecessary-waiting': 'off',
},
}
]
```
## Contributing
Please see our [Contributing Guideline](./CONTRIBUTING.md) which explains how to contribute rules or other fixes and features to the repo.

82
node_modules/eslint-plugin-cypress/circle.yml generated vendored Normal file
View File

@@ -0,0 +1,82 @@
version: 2.1
workflows:
version: 2.1
main:
jobs:
- lint
- test-v8
- test-v9
- release:
requires:
- lint
- test-v8
- test-v9
filters:
branches:
only:
- master
jobs:
lint:
docker:
- image: cimg/node:22.11.0
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
- run:
name: Show ESLint version
command: npx eslint --version
- run:
name: Lint code
command: npm run lint
test-v8:
docker:
- image: cimg/node:22.11.0
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
- run:
name: Install ESLint 8
command: npm install eslint@8
- run:
name: Show ESLint version
command: npx eslint --version
- run:
name: Test ESLint 8
command: npm run test:legacy
test-v9:
docker:
- image: cimg/node:22.11.0
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
- run:
name: Install ESLint 9
command: npm install eslint@9
- run:
name: Show ESLint version
command: npx eslint --version
- run:
name: Test ESLint 9
command: npm test
release:
docker:
- image: cimg/node:22.11.0
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
- run:
name: Run semantic release
command: npm run semantic-release

View File

@@ -0,0 +1,25 @@
# Require screenshots to be preceded by an assertion (`cypress/assertion-before-screenshot`)
<!-- end auto-generated rule header -->
If you take screenshots without assertions then you may get different screenshots depending on timing.
For example, if clicking a button makes some network calls and upon success, renders something, then the screenshot may sometimes have the new render and sometimes not.
## Rule Details
This rule checks there is an assertion making sure your application state is correct before doing a screenshot. This makes sure the result of the screenshot will be consistent.
Examples of **incorrect** code for this rule:
```js
cy.visit('myUrl');
cy.screenshot();
```
Examples of **correct** code for this rule:
```js
cy.visit('myUrl');
cy.get('[data-test-id="my-element"]').should('be.visible');
cy.screenshot();
```

View File

@@ -0,0 +1,8 @@
# Disallow assigning return values of `cy` calls (`cypress/no-assigning-return-values`)
💼 This rule is enabled in the ✅ `recommended` config.
<!-- end auto-generated rule header -->
## Further Reading
See [the Cypress Best Practices guide](https://on.cypress.io/best-practices#Assigning-Return-Values).

View File

@@ -0,0 +1,51 @@
# Disallow using `async`/`await` in Cypress `before` methods (`cypress/no-async-before`)
<!-- end auto-generated rule header -->
Cypress commands that return a promise may cause side effects in `before`/`beforeEach` hooks, possibly causing unexpected behavior.
## Rule Details
This rule disallows using `async` `before` and `beforeEach` functions.
Examples of **incorrect** code for this rule:
```js
describe('my feature', () => {
before('my test case', async () => {
await cy.get('.myClass')
// other operations
})
})
```
```js
describe('my feature', () => {
before('my test case', async () => {
cy
.get('.myClass')
.click()
await someAsyncFunction()
})
})
```
Examples of **correct** code for this rule:
```js
describe('my feature', () => {
before('my test case', () => {
cy.get('.myClass')
// other operations
})
})
```
## When Not To Use It
If there are genuine use-cases for using `async/await` in your `before` hooks then you may not want to include this rule (or at least demote it to a warning).
## Further Reading
- [Mixing Async and Sync code](https://on.cypress.io/guides/core-concepts/introduction-to-cypress#Mixing-Async-and-Sync-code)
- [Commands Are Asynchronous](https://on.cypress.io/guides/core-concepts/introduction-to-cypress.html#Commands-Are-Asynchronous)

View File

@@ -0,0 +1,54 @@
# Disallow using `async`/`await` in Cypress test cases (`cypress/no-async-tests`)
💼 This rule is enabled in the ✅ `recommended` config.
<!-- end auto-generated rule header -->
Cypress tests [that return a promise will error](https://docs.cypress.io/guides/references/error-messages.html#Cypress-detected-that-you-returned-a-promise-from-a-command-while-also-invoking-one-or-more-cy-commands-in-that-promise) and cannot run successfully.
An `async` function returns a promise under the hood, so a test using an `async` function will also error.
## Rule Details
This rule disallows using `async` test functions.
Examples of **incorrect** code for this rule:
```js
describe('my feature', () => {
it('my test case', async () => {
await cy.get('.myClass')
// other operations
})
})
```
```js
describe('my feature', () => {
it('my test case', async () => {
cy
.get('.myClass')
.click()
await someAsyncFunction()
})
})
```
Examples of **correct** code for this rule:
```js
describe('my feature', () => {
it('my test case', () => {
cy.get('.myClass')
// other operations
})
})
```
## When Not To Use It
If there are genuine use-cases for using `async/await` in your test cases then you may not want to include this rule (or at least demote it to a warning).
## Further Reading
- [Mixing Async and Sync code](https://on.cypress.io/guides/core-concepts/introduction-to-cypress#Mixing-Async-and-Sync-code)
- [Commands Are Asynchronous](https://on.cypress.io/guides/core-concepts/introduction-to-cypress.html#Commands-Are-Asynchronous)

View File

@@ -0,0 +1,19 @@
# Disallow using `cy.debug()` calls (`cypress/no-debug`)
<!-- end auto-generated rule header -->
It is recommended to remove any [cy.debug](https://on.cypress.io/debug) commands before committing specs to avoid other developers getting unexpected results.
## Rule Details
Examples of **incorrect** code for this rule:
```js
cy.debug();
cy.get('selector').debug();
```
Examples of **correct** code for this rule:
```js
cy.get('selector')
```

View File

@@ -0,0 +1,48 @@
# Disallow using `force: true` with action commands (`cypress/no-force`)
<!-- end auto-generated rule header -->
Using `force: true` on inputs appears to be confusing rather than helpful.
It usually silences the actual problem instead of providing a way to overcome it.
See [Cypress Core Concepts](https://docs.cypress.io/guides/core-concepts/interacting-with-elements.html#Forcing).
If enabling this rule, it's recommended to set the severity to `warn`.
## Rule Details
This rule disallows using the `force` option on:[`.click()`](https://on.cypress.io/click),
[`.dblclick()`](https://on.cypress.io/dblclick), [`.type()`](https://on.cypress.io/type),
[`.rightclick()`](https://on.cypress.io/rightclick), [`.select()`](https://on.cypress.io/select),
[`.focus()`](https://on.cypress.io/focus), [`.check()`](https://on.cypress.io/check),
and [`.trigger()`](https://on.cypress.io/trigger).
Examples of **incorrect** code for this rule:
```js
cy.get('button').click({force: true})
cy.get('button').dblclick({force: true})
cy.get('input').type('somth', {force: true})
cy.get('div').find('.foo').find('.bar').trigger('change', {force: true})
cy.get('input').trigger('click', {force: true})
cy.get('input').rightclick({force: true})
cy.get('input').check({force: true})
cy.get('input').select({force: true})
cy.get('input').focus({force: true})
```
Examples of **correct** code for this rule:
```js
cy.get('button').click()
cy.get('button').click({multiple: true})
cy.get('button').dblclick()
cy.get('input').type('somth')
cy.get('input').trigger('click', {anyoption: true})
cy.get('input').rightclick({anyoption: true})
cy.get('input').check()
cy.get('input').select()
cy.get('input').focus()
```
## When Not To Use It
If you don't mind using `{ force: true }` with action commands, then turn this rule off.

View File

@@ -0,0 +1,19 @@
# Disallow using `cy.pause()` calls (`cypress/no-pause`)
<!-- end auto-generated rule header -->
It is recommended to remove any [cy.pause](https://on.cypress.io/pause) commands before committing specs to avoid other developers getting unexpected results.
## Rule Details
Examples of **incorrect** code for this rule:
```js
cy.pause();
cy.get('selector').pause();
```
Examples of **correct** code for this rule:
```js
cy.get('selector')
```

View File

@@ -0,0 +1,8 @@
# Disallow waiting for arbitrary time periods (`cypress/no-unnecessary-waiting`)
💼 This rule is enabled in the ✅ `recommended` config.
<!-- end auto-generated rule header -->
## Further Reading
See [the Cypress Best Practices guide](https://on.cypress.io/best-practices#Unnecessary-Waiting).

View File

@@ -0,0 +1,29 @@
# Require `data-*` attribute selectors (`cypress/require-data-selectors`)
<!-- end auto-generated rule header -->
Require `cy.get` to use only selectors that target `data-*` attributes.
> Note: If you use this rule, consider only using the `warn` error level, since using `data-*` attribute selectors may not always be possible.
## Rule Details
Examples of **incorrect** code for this rule:
```js
cy.get(".a")
cy.get('[daedta-cy=submit]').click()
cy.get('[d-cy=submit]')
cy.get(".btn-large").click()
cy.get(".btn-.large").click()
```
Examples of **correct** code for this rule:
```js
cy.get('[data-cy=submit]').click()
cy.get('[data-QA=submit]')
```
## Further Reading
See [the Cypress Best Practices guide](https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements).

View File

@@ -0,0 +1,18 @@
# Disallow actions within chains (`cypress/unsafe-to-chain-command`)
💼 This rule is enabled in the ✅ `recommended` config.
<!-- end auto-generated rule header -->
### Options
<!-- begin auto-generated rule options list -->
| Name | Description | Type | Default |
| :-------- | :---------------------------------------------------------- | :---- | :------ |
| `methods` | An additional list of methods to check for unsafe chaining. | Array | `[]` |
<!-- end auto-generated rule options list -->
## Further Reading
See [retry-ability guide](https://docs.cypress.io/guides/core-concepts/retry-ability#Actions-should-be-at-the-end-of-chains-not-the-middle).

29
node_modules/eslint-plugin-cypress/eslint.config.mjs generated vendored Normal file
View File

@@ -0,0 +1,29 @@
import globals from 'globals'
import pluginJs from '@eslint/js'
import eslintPlugin from 'eslint-plugin-eslint-plugin'
import nodePlugin from 'eslint-plugin-n'
import mochaPlugin from 'eslint-plugin-mocha'
export default [
pluginJs.configs.recommended,
eslintPlugin.configs['flat/recommended'],
nodePlugin.configs['flat/recommended-script'],
mochaPlugin.configs.flat.recommended,
{
languageOptions: {
globals: globals.node
},
rules: {
'no-redeclare': 'off',
'eslint-plugin/require-meta-docs-url':
['error', { 'pattern': 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/{{name}}.md' }],
'eslint-plugin/require-meta-docs-description': 'error',
'eslint-plugin/meta-property-ordering': 'error',
'eslint-plugin/test-case-property-ordering': 'error',
'n/no-extraneous-require':
['error', { 'allowModules': ['jest-config'] }],
'mocha/no-mocha-arrows': 'off',
'mocha/no-setup-in-describe': 'off'
}
},
]

View File

@@ -0,0 +1,6 @@
const { defaults } = require('jest-config')
module.exports = {
testMatch: ['**/tests-legacy/**/*.[jt]s?(x)'],
testPathIgnorePatterns: [...defaults.testPathIgnorePatterns, '.history'],
}

6
node_modules/eslint-plugin-cypress/jest.config.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
const { defaults } = require('jest-config')
module.exports = {
testMatch: ['**/tests/**/*.[jt]s?(x)'],
testPathIgnorePatterns: [...defaults.testPathIgnorePatterns, '.history'],
}

34
node_modules/eslint-plugin-cypress/legacy.js generated vendored Normal file
View File

@@ -0,0 +1,34 @@
const globals = require('globals')
module.exports = {
rules: {
'no-assigning-return-values': require('./lib/rules/no-assigning-return-values'),
'unsafe-to-chain-command': require('./lib/rules/unsafe-to-chain-command'),
'no-unnecessary-waiting': require('./lib/rules/no-unnecessary-waiting'),
'no-async-before': require('./lib/rules/no-async-before'),
'no-async-tests': require('./lib/rules/no-async-tests'),
'assertion-before-screenshot': require('./lib/rules/assertion-before-screenshot'),
'require-data-selectors': require('./lib/rules/require-data-selectors'),
'no-force': require('./lib/rules/no-force'),
'no-pause': require('./lib/rules/no-pause'),
'no-debug': require('./lib/rules/no-debug'),
},
configs: {
recommended: require('./lib/config/recommended'),
},
environments: {
globals: {
globals: Object.assign({
cy: false,
Cypress: false,
expect: false,
assert: false,
chai: false,
}, globals.browser, globals.mocha),
parserOptions: {
ecmaVersion: 2019,
sourceType: 'module',
},
},
},
}

View File

@@ -0,0 +1,14 @@
'use strict'
module.exports = {
plugins: ['cypress'],
env: {
'cypress/globals': true,
},
rules: {
'cypress/no-assigning-return-values': 'error',
'cypress/no-unnecessary-waiting': 'error',
'cypress/no-async-tests': 'error',
'cypress/unsafe-to-chain-command': 'error',
},
}

62
node_modules/eslint-plugin-cypress/lib/flat.js generated vendored Normal file
View File

@@ -0,0 +1,62 @@
const globals = require('globals')
const { name, version } = require('../package.json')
const plugin = {
meta: { name, version },
configs: {},
rules: {
'no-assigning-return-values': require('./rules/no-assigning-return-values'),
'unsafe-to-chain-command': require('./rules/unsafe-to-chain-command'),
'no-unnecessary-waiting': require('./rules/no-unnecessary-waiting'),
'no-async-before': require('./rules/no-async-before'),
'no-async-tests': require('./rules/no-async-tests'),
'assertion-before-screenshot': require('./rules/assertion-before-screenshot'),
'require-data-selectors': require('./rules/require-data-selectors'),
'no-force': require('./rules/no-force'),
'no-pause': require('./rules/no-pause'),
'no-debug': require('./rules/no-debug'),
},
}
const commonGlobals =
Object.assign({
cy: false,
Cypress: false,
expect: false,
assert: false,
chai: false,
}, globals.browser, globals.mocha)
Object.assign(plugin.configs, {
globals: {
name: 'cypress/globals',
plugins: {
cypress: plugin
},
languageOptions: {
globals:
commonGlobals,
}
}
})
Object.assign(plugin.configs, {
recommended: {
name: 'cypress/recommended',
plugins: {
cypress: plugin
},
rules: {
'cypress/no-assigning-return-values': 'error',
'cypress/no-unnecessary-waiting': 'error',
'cypress/no-async-tests': 'error',
'cypress/unsafe-to-chain-command': 'error',
},
languageOptions: {
globals:
commonGlobals,
}
}
})
module.exports = plugin

View File

@@ -0,0 +1,112 @@
'use strict'
const assertionCommands = [
// assertions
'should',
'and',
'contains',
// retries until it gets something
'get',
// not an assertion, but unlikely to require waiting for render
'scrollIntoView',
'scrollTo',
]
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'require screenshots to be preceded by an assertion',
category: 'Possible Errors',
recommended: false,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/assertion-before-screenshot.md',
},
schema: [],
messages: {
unexpected: 'Make an assertion on the page state before taking a screenshot',
},
},
create (context) {
return {
CallExpression (node) {
if (isCallingCyScreenshot(node) && !isPreviousAnAssertion(node)) {
context.report({ node, messageId: 'unexpected' })
}
},
}
},
}
function isRootCypress (node) {
while (node.type === 'CallExpression') {
if (node.callee.type !== 'MemberExpression') return false
if (node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'cy') {
return true
}
node = node.callee.object
}
return false
}
function getPreviousInChain (node) {
return node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'CallExpression' &&
node.callee.object.callee.type === 'MemberExpression' &&
node.callee.object.callee.property.type === 'Identifier' &&
node.callee.object.callee.property.name
}
function getCallExpressionCypressCommand (node) {
return isRootCypress(node) &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name
}
function isCallingCyScreenshot (node) {
return getCallExpressionCypressCommand(node) === 'screenshot'
}
function getPreviousCypressCommand (node) {
const previousInChain = getPreviousInChain(node)
if (previousInChain) {
return previousInChain
}
while (node.parent && !node.parent.body) {
node = node.parent
}
if (!node.parent || !node.parent.body) return null
const body = node.parent.body.type === 'BlockStatement' ? node.parent.body.body : node.parent.body
const index = body.indexOf(node)
// in the case of a function declaration it won't be found
if (index < 0) return null
if (index === 0) return getPreviousCypressCommand(node.parent)
const previousStatement = body[index - 1]
if (previousStatement.type !== 'ExpressionStatement' ||
previousStatement.expression.type !== 'CallExpression') {
return null
}
return getCallExpressionCypressCommand(previousStatement.expression)
}
function isPreviousAnAssertion (node) {
const previousCypressCommand = getPreviousCypressCommand(node)
return assertionCommands.indexOf(previousCypressCommand) >= 0
}

View File

@@ -0,0 +1,68 @@
'use strict'
// safely get nested object property
function get (obj, propertyString = '') {
const properties = propertyString.split('.')
for (let i = 0; i < properties.length; i++) {
const value = (obj || {})[properties[i]]
if (value == null) return value
obj = value
}
return obj
}
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow assigning return values of `cy` calls',
category: 'Possible Errors',
recommended: true,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-assigning-return-values.md',
},
schema: [],
messages: {
unexpected: 'Do not assign the return value of a Cypress command',
},
},
create (context) {
return {
VariableDeclaration (node) {
if (node.declarations.some(isCypressCommandDeclaration)) {
context.report({ node, messageId: 'unexpected' })
}
},
}
},
}
const allowedCommands = {
now: true,
spy: true,
state: true,
stub: true,
}
function isCypressCommandDeclaration (declarator) {
let object = get(declarator, 'init.callee.object')
if (!object) return
while (object.callee) {
object = object.callee.object
if (!object) return
}
const commandName = get(declarator, 'init.callee.property.name')
const parent = get(object, 'parent.property.name') || get(declarator, 'id.name')
if (commandName && (allowedCommands[commandName] || allowedCommands[parent])) return
return object.name === 'cy'
}

View File

@@ -0,0 +1,53 @@
'use strict'
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow using `async`/`await` in Cypress `before` methods',
category: 'Possible Errors',
recommended: true,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-async-before.md',
},
schema: [],
messages: {
unexpected: 'Avoid using async functions with Cypress before / beforeEach functions',
},
},
create (context) {
function isBeforeBlock (callExpressionNode) {
const { type, name } = callExpressionNode.callee
return type === 'Identifier'
&& name === 'before' || name === 'beforeEach'
}
function isBeforeAsync (node) {
return node.arguments
&& node.arguments.length >= 2
&& node.arguments[1].async === true
}
const sourceCode = context.sourceCode ?? context.getSourceCode()
return {
Identifier (node) {
if (node.name === 'cy' || node.name === 'Cypress') {
const ancestors = sourceCode.getAncestors
? sourceCode.getAncestors(node)
: context.getAncestors()
const asyncTestBlocks = ancestors
.filter((n) => n.type === 'CallExpression')
.filter(isBeforeBlock)
.filter(isBeforeAsync)
if (asyncTestBlocks.length >= 1) {
asyncTestBlocks.forEach((node) => {
context.report({ node, messageId: 'unexpected' })
})
}
}
},
}
},
}

View File

@@ -0,0 +1,53 @@
'use strict'
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow using `async`/`await` in Cypress test cases',
category: 'Possible Errors',
recommended: true,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-async-tests.md',
},
schema: [],
messages: {
unexpected: 'Avoid using async functions with Cypress tests',
},
},
create (context) {
function isTestBlock (callExpressionNode) {
const { type, name } = callExpressionNode.callee
return type === 'Identifier'
&& name === 'it' || name === 'test'
}
function isTestAsync (node) {
return node.arguments
&& node.arguments.length >= 2
&& node.arguments[1].async === true
}
const sourceCode = context.sourceCode ?? context.getSourceCode()
return {
Identifier (node) {
if (node.name === 'cy' || node.name === 'Cypress') {
const ancestors = sourceCode.getAncestors
? sourceCode.getAncestors(node)
: context.getAncestors()
const asyncTestBlocks = ancestors
.filter((n) => n.type === 'CallExpression')
.filter(isTestBlock)
.filter(isTestAsync)
if (asyncTestBlocks.length >= 1) {
asyncTestBlocks.forEach((node) => {
context.report({ node, messageId: 'unexpected' })
})
}
}
},
}
},
}

View File

@@ -0,0 +1,61 @@
'use strict'
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow using `cy.debug()` calls',
category: 'Possible Errors',
recommended: false,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-debug.md',
},
fixable: null, // or "code" or "whitespace"
schema: [],
messages: {
unexpected: 'Do not use cy.debug command',
},
},
create (context) {
// variables should be defined here
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
function isCallingDebug (node) {
return node.callee &&
node.callee.property &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'debug'
}
function isCypressCall (node) {
if (!node.callee || node.callee.type !== 'MemberExpression') {
return false;
}
if (node.callee.object.type === 'Identifier' && node.callee.object.name === 'cy') {
return true;
}
return isCypressCall(node.callee.object);
}
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
CallExpression (node) {
if (isCypressCall(node) && isCallingDebug(node)) {
context.report({ node, messageId: 'unexpected' })
}
},
}
},
}

View File

@@ -0,0 +1,80 @@
'use strict'
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow using `force: true` with action commands',
category: 'Possible Errors',
recommended: false,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-force.md',
},
fixable: null, // or "code" or "whitespace"
schema: [],
messages: {
unexpected: 'Do not use force on click and type calls',
},
},
create (context) {
// variables should be defined here
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
function isCallingClickOrType (node) {
const allowedMethods = ['click', 'dblclick', 'type', 'trigger', 'check', 'rightclick', 'focus', 'select']
return node.property && node.property.type === 'Identifier' &&
allowedMethods.includes(node.property.name)
}
function isCypressCall (node) {
return node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'cy'
}
function hasOptionForce (node) {
return node.arguments && node.arguments.length &&
node.arguments.some((arg) => {
return arg.type === 'ObjectExpression' && arg.properties.some((propNode) => propNode.key && propNode.key.name === 'force')
})
}
function deepCheck (node, checkFunc) {
let currentNode = node
while (currentNode.parent) {
if (checkFunc(currentNode.parent)) {
return true
}
currentNode = currentNode.parent
}
return false
}
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
CallExpression (node) {
if (isCypressCall(node) && deepCheck(node, isCallingClickOrType) && deepCheck(node, hasOptionForce)) {
context.report({ node, messageId: 'unexpected' })
}
},
}
},
}

View File

@@ -0,0 +1,61 @@
'use strict'
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow using `cy.pause()` calls',
category: 'Possible Errors',
recommended: false,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-pause.md',
},
fixable: null, // or "code" or "whitespace"
schema: [],
messages: {
unexpected: 'Do not use cy.pause command',
},
},
create (context) {
// variables should be defined here
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
function isCallingPause (node) {
return node.callee &&
node.callee.property &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'pause'
}
function isCypressCall (node) {
if (!node.callee || node.callee.type !== 'MemberExpression') {
return false;
}
if (node.callee.object.type === 'Identifier' && node.callee.object.name === 'cy') {
return true;
}
return isCypressCall(node.callee.object);
}
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
CallExpression (node) {
if (isCypressCall(node) && isCallingPause(node)) {
context.report({ node, messageId: 'unexpected' })
}
},
}
},
}

View File

@@ -0,0 +1,91 @@
'use strict'
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow waiting for arbitrary time periods',
category: 'Possible Errors',
recommended: true,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-unnecessary-waiting.md',
},
schema: [],
messages: {
unexpected: 'Do not wait for arbitrary time periods',
},
},
create (context) {
const sourceCode = context.sourceCode ?? context.getSourceCode()
return {
CallExpression (node) {
if (isCallingCyWait(node)) {
const scope = sourceCode.getScope
? sourceCode.getScope(node)
: context.getScope()
if (isIdentifierNumberConstArgument(node, scope) || isNumberArgument(node)) {
context.report({ node, messageId: 'unexpected' })
}
}
},
}
},
}
function nodeIsCalledByCy (node) {
if (node.type === 'Identifier' && node.name === 'cy') return true
if (typeof node.callee === 'undefined' || typeof node.callee.object === 'undefined') {
return false
}
return nodeIsCalledByCy(node.callee.object)
}
function isCallingCyWait (node) {
return node.callee.type === 'MemberExpression' &&
nodeIsCalledByCy(node) &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'wait'
}
function isNumberArgument (node) {
return node.arguments.length > 0 &&
node.arguments[0].type === 'Literal' &&
typeof (node.arguments[0].value) === 'number'
}
function isIdentifierNumberConstArgument (node, scope) {
if (node.arguments.length === 0) return false
if (node.arguments[0].type !== 'Identifier') return false
const identifier = node.arguments[0]
const resolvedIdentifier = scope.references.find((ref) => ref.identifier === identifier).resolved
const definition = resolvedIdentifier.defs[0]
const isVariable = definition.type === 'Variable'
// const amount = 1000 or const amount = '@alias'
// cy.wait(amount)
if (isVariable) {
if (!definition.node.init) return false
return typeof definition.node.init.value === 'number'
}
// import { WAIT } from './constants'
// cy.wait(WAIT)
// we don't know if WAIT is a number or alias '@someRequest', so don't fail
if (definition.type === 'ImportBinding') return false
const param = definition.node.params[definition.index]
// function wait (amount) { cy.wait(amount) }
// we can't know the type of value, so don't fail
if (!param || param.type !== 'AssignmentPattern') return false
// function wait (amount = 1) { cy.wait(amount) } or
// function wait (amount = '@alias') { cy.wait(amount) }
return typeof param.right.value === 'number'
}

View File

@@ -0,0 +1,49 @@
'use strict'
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'require `data-*` attribute selectors',
category: 'Possible Errors',
recommended: false,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/require-data-selectors.md',
},
schema: [],
messages: {
unexpected: 'use data-* attribute selectors instead of classes or tag names',
},
},
create (context) {
return {
CallExpression (node) {
if (isCallingCyGet(node) && !isDataArgument(node)) {
context.report({ node, messageId: 'unexpected' })
}
},
}
},
}
function isCallingCyGet (node) {
return node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'cy' &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'get'
}
function isDataArgument (node) {
return node.arguments.length > 0 &&
(
(node.arguments[0].type === 'Literal' && isAliasOrDataSelector(String(node.arguments[0].value))) ||
(node.arguments[0].type === 'TemplateLiteral' && isAliasOrDataSelector(String(node.arguments[0].quasis[0].value.cooked)))
)
}
function isAliasOrDataSelector (selector) {
return ['[data-', '@'].some(function (validValue) {
return selector.startsWith(validValue)
})
}

View File

@@ -0,0 +1,143 @@
'use strict'
const { basename } = require('path')
const NAME = basename(__dirname)
const DESCRIPTION = 'disallow actions within chains'
/**
* Commands listed in the documentation with text: 'It is unsafe to chain further commands that rely on the subject after xxx.'
* See {@link https://docs.cypress.io/guides/core-concepts/retry-ability#Actions-should-be-at-the-end-of-chains-not-the-middle Actions should be at the end of chains, not the middle}
* for more information.
*
* @type {string[]}
*/
const unsafeToChainActions = [
'blur',
'clear',
'click',
'check',
'dblclick',
'each',
'focus',
'rightclick',
'screenshot',
'scrollIntoView',
'scrollTo',
'select',
'selectFile',
'spread',
'submit',
'type',
'trigger',
'uncheck',
'within',
]
/**
* @type {import('eslint').Rule.RuleMetaData['schema']}
*/
const schema = {
title: NAME,
description: DESCRIPTION,
type: 'object',
properties: {
methods: {
type: 'array',
description:
'An additional list of methods to check for unsafe chaining.',
default: [],
},
},
}
/**
* @param {import('eslint').Rule.RuleContext} context
* @returns {Record<string, any>}
*/
const getDefaultOptions = (context) => {
return Object.entries(schema.properties).reduce((acc, [key, value]) => {
if (!(value.default in value)) return acc
return {
...acc,
[key]: value.default,
}
}, context.options[0] || {})
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
docs: {
description: DESCRIPTION,
category: 'Possible Errors',
recommended: true,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/unsafe-to-chain-command.md',
},
schema: [schema],
messages: {
unexpected:
'It is unsafe to chain further commands that rely on the subject after this command. It is best to split the chain, chaining again from `cy.` in a next command line.',
},
},
create (context) {
const { methods } = getDefaultOptions(context)
return {
CallExpression (node) {
if (
isRootCypress(node) &&
isActionUnsafeToChain(node, methods) &&
node.parent.type === 'MemberExpression'
) {
context.report({
node,
messageId: 'unexpected',
})
}
},
}
},
}
/**
* @param {import('estree').Node} node
* @returns {boolean}
*/
const isRootCypress = (node) => {
if (
node.type !== 'CallExpression' ||
node.callee.type !== 'MemberExpression'
) {
return false
}
if (
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'cy'
) {
return true
}
return isRootCypress(node.callee.object)
}
/**
* @param {import('estree').Node} node
* @param {(string | RegExp)[]} additionalMethods
*/
const isActionUnsafeToChain = (node, additionalMethods = []) => {
const unsafeActionsRegex = new RegExp([
...unsafeToChainActions.map((action) => `^${action}$`),
...additionalMethods.map((method) => method instanceof RegExp ? method.source : method),
].join('|'))
return (
node.callee &&
node.callee.property &&
node.callee.property.type === 'Identifier' &&
unsafeActionsRegex.test(node.callee.property.name)
)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
'use strict';
module.exports = require('./globals.json');

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,98 @@
{
"name": "globals",
"version": "15.12.0",
"description": "Global identifiers from different JavaScript environments",
"license": "MIT",
"repository": "sindresorhus/globals",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"sideEffects": false,
"engines": {
"node": ">=18"
},
"scripts": {
"test": "npm run build && xo && ava && tsd",
"prepare": "npm run build",
"update": "node scripts/update.mjs",
"update:browser": "node scripts/update.mjs --environment=browser",
"update:builtin": "node scripts/update.mjs --environment=builtin",
"update:nodeBuiltin": "node scripts/update.mjs --environment=nodeBuiltin",
"update:worker": "node scripts/update.mjs --environment=worker",
"update:shelljs": "node scripts/update.mjs --environment=shelljs",
"update:jest": "node scripts/update.mjs --environment=jest",
"build": "run-s build:data build:types",
"build:data": "node scripts/generate-data.mjs",
"build:types": "node scripts/generate-types.mjs"
},
"files": [
"index.js",
"index.d.ts",
"globals.json"
],
"keywords": [
"globals",
"global",
"identifiers",
"variables",
"vars",
"jshint",
"eslint",
"environments"
],
"devDependencies": {
"ava": "^6.1.3",
"cheerio": "^1.0.0-rc.12",
"eslint-plugin-jest": "^28.8.3",
"execa": "^9.4.0",
"get-port": "^7.1.0",
"npm-run-all2": "^6.2.3",
"outdent": "^0.8.0",
"puppeteer": "^23.4.1",
"shelljs": "^0.8.5",
"tsd": "^0.31.2",
"type-fest": "^4.26.1",
"xo": "^0.59.3"
},
"xo": {
"rules": {
"unicorn/prefer-module": "off"
},
"overrides": [
{
"files": [
"data/*.mjs"
],
"rules": {
"import/no-anonymous-default-export": "off",
"camelcase": "off",
"unicorn/filename-case": [
"error",
{
"cases": {
"camelCase": true,
"kebabCase": true
}
}
]
}
},
{
"files": [
"scripts/*.mjs"
],
"rules": {
"n/no-unsupported-features/node-builtins": "off"
}
}
]
},
"tsd": {
"compilerOptions": {
"resolveJsonModule": true
}
}
}

View File

@@ -0,0 +1,42 @@
# globals
> Global identifiers from different JavaScript environments
It's just a [JSON file](globals.json), so you can use it in any environment.
This package is used by ESLint 8 and earlier. For ESLint 9 and later, you should depend on this package directly in [your ESLint config](https://eslint.org/docs/latest/use/configure/language-options#predefined-global-variables).
## Install
```sh
npm install globals
```
## Usage
```js
import globals from 'globals';
console.log(globals.browser);
/*
{
addEventListener: false,
applicationCache: false,
ArrayBuffer: false,
atob: false,
}
*/
```
Each global is given a value of `true` or `false`. A value of `true` indicates that the variable may be overwritten. A value of `false` indicates that the variable should be considered read-only. This information is used by static analysis tools to flag incorrect behavior. We assume all variables should be `false` unless we hear otherwise.
For Node.js this package provides two sets of globals:
- `globals.nodeBuiltin`: Globals available to all code running in Node.js.
These will usually be available as properties on the `globalThis` object and include `process`, `Buffer`, but not CommonJS arguments like `require`.
See: https://nodejs.org/api/globals.html
- `globals.node`: A combination of the globals from `nodeBuiltin` plus all CommonJS arguments ("CommonJS module scope").
See: https://nodejs.org/api/modules.html#modules_the_module_scope
When analyzing code that is known to run outside of a CommonJS wrapper, for example, JavaScript modules, `nodeBuiltin` can find accidental CommonJS references.

50
node_modules/eslint-plugin-cypress/package.json generated vendored Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "eslint-plugin-cypress",
"version": "4.1.0",
"description": "An ESLint plugin for projects using Cypress",
"main": "legacy.js",
"exports": {
".": "./legacy.js",
"./flat": "./lib/flat.js"
},
"author": "Cypress-io",
"license": "MIT",
"keywords": [
"eslint",
"eslintplugin",
"cypress"
],
"repository": {
"type": "git",
"url": "git+https://github.com/cypress-io/eslint-plugin-cypress.git"
},
"bugs": {
"url": "https://github.com/cypress-io/eslint-plugin-cypress/issues"
},
"homepage": "https://github.com/cypress-io/eslint-plugin-cypress#readme",
"peerDependencies": {
"eslint": ">=9"
},
"dependencies": {
"globals": "^15.11.0"
},
"devDependencies": {
"eslint": "^9.13.0",
"eslint-plugin-eslint-plugin": "^6.3.1",
"eslint-plugin-mocha": "^10.5.0",
"eslint-plugin-n": "^17.11.1",
"husky": "^9.1.6",
"jest": "^29.7.0",
"semantic-release": "24.2.0"
},
"scripts": {
"lint": "eslint \"*.js\" \"**/**/*.js\"",
"lint-fix": "npm run lint -- --fix",
"semantic-release": "semantic-release",
"start": "npm run test-watch",
"test": "jest",
"test:legacy": "jest --config jest.config-legacy.js",
"test-watch": "jest --watchAll",
"prepare": "husky"
}
}

View File

@@ -0,0 +1,26 @@
/* global describe, it, expect */
'use strict'
const globals = require('globals')
const config = require('../legacy.js')
describe('environments globals', () => {
const env = config.environments.globals
it('should not mutate globals', () => {
expect(globals.browser).not.toHaveProperty('cy')
expect(globals.mocha).not.toHaveProperty('cy')
})
it('should include other globals', () => {
expect(env.globals).toEqual(expect.objectContaining(globals.browser))
expect(env.globals).toEqual(expect.objectContaining(globals.mocha))
})
it('should include cypress globals', () => {
expect(env.globals).toEqual(expect.objectContaining({
cy: false,
Cypress: false,
}))
})
})

View File

@@ -0,0 +1,35 @@
'use strict'
const rule = require('../../../lib/rules/assertion-before-screenshot')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
const parserOptions = { ecmaVersion: 6 }
ruleTester.run('assertion-before-screenshot', rule, {
valid: [
{ code: 'cy.get(".some-element"); cy.screenshot();', parserOptions },
{ code: 'cy.get(".some-element").should("exist").screenshot();', parserOptions },
{ code: 'cy.get(".some-element").should("exist").screenshot().click()', parserOptions, errors },
{ code: 'cy.get(".some-element").should("exist"); if(true) cy.screenshot();', parserOptions },
{ code: 'if(true) { cy.get(".some-element").should("exist"); cy.screenshot(); }', parserOptions },
{ code: 'cy.get(".some-element").should("exist"); if(true) { cy.screenshot(); }', parserOptions },
{ code: 'const a = () => { cy.get(".some-element").should("exist"); cy.screenshot(); }', parserOptions, errors },
{ code: 'cy.get(".some-element").should("exist").and("be.visible"); cy.screenshot();', parserOptions },
{ code: 'cy.get(".some-element").contains("Text"); cy.screenshot();', parserOptions },
],
invalid: [
{ code: 'cy.screenshot()', parserOptions, errors },
{ code: 'cy.visit("somepage"); cy.screenshot();', parserOptions, errors },
{ code: 'cy.custom(); cy.screenshot()', parserOptions, errors },
{ code: 'cy.get(".some-element").click(); cy.screenshot()', parserOptions, errors },
{ code: 'cy.get(".some-element").click().screenshot()', parserOptions, errors },
{ code: 'if(true) { cy.get(".some-element").click(); cy.screenshot(); }', parserOptions, errors },
{ code: 'cy.get(".some-element").click(); if(true) { cy.screenshot(); }', parserOptions, errors },
{ code: 'cy.get(".some-element"); function a() { cy.screenshot(); }', parserOptions, errors },
{ code: 'cy.get(".some-element"); const a = () => { cy.screenshot(); }', parserOptions, errors },
],
})

View File

@@ -0,0 +1,38 @@
'use strict'
const rule = require('../../../lib/rules/no-assigning-return-values')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
const parserOptions = { ecmaVersion: 6 }
ruleTester.run('no-assigning-return-values', rule, {
valid: [
{ code: 'var foo = true;', parserOptions },
{ code: 'let foo = true;', parserOptions },
{ code: 'const foo = true;', parserOptions },
{ code: 'const foo = bar();', parserOptions },
{ code: 'const foo = bar().baz();', parserOptions },
{ code: 'const spy = cy.spy();', parserOptions },
{ code: 'const spy = cy.spy().as();', parserOptions },
{ code: 'const stub = cy.stub();', parserOptions },
{ code: 'const result = cy.now();', parserOptions },
{ code: 'const state = cy.state();', parserOptions },
{ code: 'cy.get("foo");', parserOptions },
{ code: 'cy.contains("foo").click();', parserOptions },
],
invalid: [
{ code: 'let a = cy.get("foo")', parserOptions, errors },
{ code: 'const a = cy.get("foo")', parserOptions, errors },
{ code: 'var a = cy.get("foo")', parserOptions, errors },
{ code: 'let a = cy.contains("foo")', parserOptions, errors },
{ code: 'let a = cy.window()', parserOptions, errors },
{ code: 'let a = cy.wait("@something")', parserOptions, errors },
{ code: 'let a = cy.contains("foo").click()', parserOptions, errors },
],
})

View File

@@ -0,0 +1,25 @@
'use strict'
const rule = require('../../../lib/rules/no-async-before')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
// async functions are an ES2017 feature
const parserOptions = { ecmaVersion: 8 }
ruleTester.run('no-async-before', rule, {
valid: [
{ code: 'before(\'a before case\', () => { cy.get(\'.someClass\'); })', parserOptions },
{ code: 'before(\'a before case\', async () => { await somethingAsync(); })', parserOptions },
{ code: 'async function nonTestFn () { return await somethingAsync(); }', parserOptions },
{ code: 'const nonTestArrowFn = async () => { await somethingAsync(); }', parserOptions },
],
invalid: [
{ code: 'before(\'a test case\', async () => { cy.get(\'.someClass\'); })', parserOptions, errors },
{ code: 'beforeEach(\'a test case\', async () => { cy.get(\'.someClass\'); })', parserOptions, errors },
{ code: 'before(\'a test case\', async function () { cy.get(\'.someClass\'); })', parserOptions, errors },
{ code: 'beforeEach(\'a test case\', async function () { cy.get(\'.someClass\'); })', parserOptions, errors },
],
})

View File

@@ -0,0 +1,25 @@
'use strict'
const rule = require('../../../lib/rules/no-async-tests')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
// async functions are an ES2017 feature
const parserOptions = { ecmaVersion: 8 }
ruleTester.run('no-async-tests', rule, {
valid: [
{ code: 'it(\'a test case\', () => { cy.get(\'.someClass\'); })', parserOptions },
{ code: 'it(\'a test case\', async () => { await somethingAsync(); })', parserOptions },
{ code: 'async function nonTestFn () { return await somethingAsync(); }', parserOptions },
{ code: 'const nonTestArrowFn = async () => { await somethingAsync(); }', parserOptions },
],
invalid: [
{ code: 'it(\'a test case\', async () => { cy.get(\'.someClass\'); })', parserOptions, errors },
{ code: 'test(\'a test case\', async () => { cy.get(\'.someClass\'); })', parserOptions, errors },
{ code: 'it(\'a test case\', async function () { cy.get(\'.someClass\'); })', parserOptions, errors },
{ code: 'test(\'a test case\', async function () { cy.get(\'.someClass\'); })', parserOptions, errors },
],
})

View File

@@ -0,0 +1,49 @@
'use strict'
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const rule = require('../../../lib/rules/no-force')
const RuleTester = require('eslint').RuleTester
const errors = [{ messageId: 'unexpected' }]
const parserOptions = { ecmaVersion: 2018 }
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
let ruleTester = new RuleTester()
ruleTester.run('no-force', rule, {
valid: [
{ code: `cy.get('button').click()`, parserOptions },
{ code: `cy.get('button').click({multiple: true})`, parserOptions },
{ code: `cy.get('button').dblclick()`, parserOptions },
{ code: `cy.get('input').type('somth')`, parserOptions },
{ code: `cy.get('input').type('somth', {anyoption: true})`, parserOptions },
{ code: `cy.get('input').trigger('click', {anyoption: true})`, parserOptions },
{ code: `cy.get('input').rightclick({anyoption: true})`, parserOptions },
{ code: `cy.get('input').check()`, parserOptions },
{ code: `cy.get('input').select()`, parserOptions },
{ code: `cy.get('input').focus()`, parserOptions },
{ code: `cy.document().trigger("keydown", { ...event })`, parserOptions },
],
invalid: [
{ code: `cy.get('button').click({force: true})`, parserOptions, errors },
{ code: `cy.get('button').dblclick({force: true})`, parserOptions, errors },
{ code: `cy.get('input').type('somth', {force: true})`, parserOptions, errors },
{ code: `cy.get('div').find('.foo').type('somth', {force: true})`, parserOptions, errors },
{ code: `cy.get('div').find('.foo').find('.bar').click({force: true})`, parserOptions, errors },
{ code: `cy.get('div').find('.foo').find('.bar').trigger('change', {force: true})`, parserOptions, errors },
{ code: `cy.get('input').trigger('click', {force: true})`, parserOptions, errors },
{ code: `cy.get('input').rightclick({force: true})`, parserOptions, errors },
{ code: `cy.get('input').check({force: true})`, parserOptions, errors },
{ code: `cy.get('input').select({force: true})`, parserOptions, errors },
{ code: `cy.get('input').focus({force: true})`, parserOptions, errors },
],
})

View File

@@ -0,0 +1,37 @@
'use strict'
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const rule = require('../../../lib/rules/no-pause')
const RuleTester = require('eslint').RuleTester
const errors = [{ messageId: 'unexpected' }]
const parserOptions = { ecmaVersion: 2018 }
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester()
ruleTester.run('no-pause', rule, {
valid: [
{ code: `pause()`, parserOptions },
{ code: `cy.get('button').dblclick()`, parserOptions },
],
invalid: [
{ code: `cy.pause()`, parserOptions, errors },
{ code: `cy.pause({ log: false })`, parserOptions, errors },
{ code: `cy.get('button').pause()`, parserOptions, errors },
{
code: `cy.get('a').should('have.attr', 'href').and('match', /dashboard/).pause()`,
parserOptions,
errors
}
],
})

View File

@@ -0,0 +1,71 @@
'use strict'
const rule = require('../../../lib/rules/no-unnecessary-waiting')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
const parserOptions = { ecmaVersion: 6, sourceType: 'module' }
ruleTester.run('no-unnecessary-waiting', rule, {
valid: [
{ code: 'foo.wait(10)', parserOptions },
{ code: 'cy.wait("@someRequest")', parserOptions },
{ code: 'cy.wait("@someRequest", { log: false })', parserOptions },
{ code: 'cy.wait("@someRequest").then((xhr) => xhr)', parserOptions },
{ code: 'cy.wait(["@someRequest", "@anotherRequest"])', parserOptions },
{ code: 'cy.clock(5000)', parserOptions },
{ code: 'cy.scrollTo(0, 10)', parserOptions },
{ code: 'cy.tick(500)', parserOptions },
{ code: 'const someRequest="@someRequest"; cy.wait(someRequest)', parserOptions, errors },
{ code: 'function customWait (alias = "@someRequest") { cy.wait(alias) }', parserOptions, errors },
{ code: 'const customWait = (alias = "@someRequest") => { cy.wait(alias) }', parserOptions, errors },
{ code: 'function customWait (ms) { cy.wait(ms) }', parserOptions, errors },
{ code: 'const customWait = (ms) => { cy.wait(ms) }', parserOptions, errors },
{ code: 'import BAR_BAZ from "bar-baz"; cy.wait(BAR_BAZ)', parserOptions },
{ code: 'import { FOO_BAR } from "foo-bar"; cy.wait(FOO_BAR)', parserOptions },
{ code: 'import * as wildcard from "wildcard"; cy.wait(wildcard.value)', parserOptions },
{ code: 'import { NAME as OTHER_NAME } from "rename"; cy.wait(OTHER_NAME)', parserOptions },
// disable the eslint rule
{
code: `
cy.wait(100); // eslint-disable-line no-unnecessary-waiting
`,
parserOptions,
},
{
code: `
/* eslint-disable-next-line no-unnecessary-waiting */
cy.wait(100)
`,
parserOptions,
},
{
code: `
/* eslint-disable no-unnecessary-waiting */
cy.wait(100)
/* eslint-enable no-unnecessary-waiting */
`,
parserOptions,
},
],
invalid: [
{ code: 'cy.wait(0)', parserOptions, errors },
{ code: 'cy.wait(100)', parserOptions, errors },
{ code: 'cy.wait(5000)', parserOptions, errors },
{ code: 'const someNumber=500; cy.wait(someNumber)', parserOptions, errors },
{ code: 'function customWait (ms = 1) { cy.wait(ms) }', parserOptions, errors },
{ code: 'const customWait = (ms = 1) => { cy.wait(ms) }', parserOptions, errors },
{ code: 'cy.get(".some-element").wait(10)', parserOptions, errors },
{ code: 'cy.get(".some-element").contains("foo").wait(10)', parserOptions, errors },
{ code: 'const customWait = (ms = 1) => { cy.get(".some-element").wait(ms) }', parserOptions, errors },
],
})

View File

@@ -0,0 +1,31 @@
'use strict'
const rule = require('../../../lib/rules/require-data-selectors')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
const parserOptions = { ecmaVersion: 6 }
ruleTester.run('require-data-selectors', rule, {
valid: [
{ code: 'cy.get(\'[data-cy=submit]\').click()', parserOptions },
{ code: 'cy.get(\'[data-QA=submit]\')', parserOptions },
{ code: 'cy.clock(5000)', parserOptions },
{ code: 'cy.scrollTo(0, 10)', parserOptions },
{ code: 'cy.tick(500)', parserOptions },
{ code: 'cy.get(\`[data-cy=${1}]\`)', parserOptions }, // eslint-disable-line no-useless-escape
{ code: 'cy.get("@my-alias")', parserOptions, errors },
{ code: 'cy.get(`@my-alias`)', parserOptions, errors },
],
invalid: [
{ code: 'cy.get(\'[daedta-cy=submit]\').click()', parserOptions, errors },
{ code: 'cy.get(\'[d-cy=submit]\')', parserOptions, errors },
{ code: 'cy.get(".btn-large").click()', parserOptions, errors },
{ code: 'cy.get(".btn-.large").click()', parserOptions, errors },
{ code: 'cy.get(".a")', parserOptions, errors },
{ code: 'cy.get(\`[daedta-cy=${1}]\`)', parserOptions, errors }, // eslint-disable-line no-useless-escape
],
})

View File

@@ -0,0 +1,56 @@
'use strict'
const rule = require('../../../lib/rules/unsafe-to-chain-command')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
const parserOptions = { ecmaVersion: 6 }
ruleTester.run('action-ends-chain', rule, {
valid: [
{
code: 'cy.get("new-todo").type("todo A{enter}"); cy.get("new-todo").type("todo B{enter}"); cy.get("new-todo").should("have.class", "active");',
parserOptions,
},
{
code: 'cy.focused().should("be.visible");',
parserOptions,
},
{
code: 'cy.submitBtn().click();',
parserOptions,
},
],
invalid: [
{
code: 'cy.get("new-todo").type("todo A{enter}").should("have.class", "active");',
parserOptions,
errors,
},
{
code: 'cy.get("new-todo").type("todo A{enter}").type("todo B{enter}");',
parserOptions,
errors,
},
{
code: 'cy.get("new-todo").focus().should("have.class", "active");',
parserOptions,
errors,
},
{
code: 'cy.get("new-todo").customType("todo A{enter}").customClick();',
options: [{ methods: ['customType', 'customClick'] }],
parserOptions,
errors,
},
{
code: 'cy.get("new-todo").customPress("Enter").customScroll();',
options: [{ methods: [/customPress/, /customScroll/] }],
parserOptions,
errors,
},
],
})

26
node_modules/eslint-plugin-cypress/tests/config.js generated vendored Normal file
View File

@@ -0,0 +1,26 @@
/* global describe, it, expect */
'use strict'
const globals = require('globals')
const config = require('../lib/flat.js')
describe('globals languageOptions', () => {
const languageOptions = config.configs.globals.languageOptions
it('should not mutate globals', () => {
expect(globals.browser).not.toHaveProperty('cy')
expect(globals.mocha).not.toHaveProperty('cy')
})
it('should include other globals', () => {
expect(languageOptions.globals).toEqual(expect.objectContaining(globals.browser))
expect(languageOptions.globals).toEqual(expect.objectContaining(globals.mocha))
})
it('should include cypress globals', () => {
expect(languageOptions.globals).toEqual(expect.objectContaining({
cy: false,
Cypress: false,
}))
})
})

View File

@@ -0,0 +1,34 @@
'use strict'
const rule = require('../../../lib/rules/assertion-before-screenshot')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('assertion-before-screenshot', rule, {
valid: [
{ code: 'cy.get(".some-element"); cy.screenshot();' },
{ code: 'cy.get(".some-element").should("exist").screenshot();' },
{ code: 'cy.get(".some-element").should("exist").screenshot().click()' },
{ code: 'cy.get(".some-element").should("exist"); if(true) cy.screenshot();' },
{ code: 'if(true) { cy.get(".some-element").should("exist"); cy.screenshot(); }' },
{ code: 'cy.get(".some-element").should("exist"); if(true) { cy.screenshot(); }' },
{ code: 'const a = () => { cy.get(".some-element").should("exist"); cy.screenshot(); }' },
{ code: 'cy.get(".some-element").should("exist").and("be.visible"); cy.screenshot();' },
{ code: 'cy.get(".some-element").contains("Text"); cy.screenshot();' },
],
invalid: [
{ code: 'cy.screenshot()', errors },
{ code: 'cy.visit("somepage"); cy.screenshot();', errors },
{ code: 'cy.custom(); cy.screenshot()', errors },
{ code: 'cy.get(".some-element").click(); cy.screenshot()', errors },
{ code: 'cy.get(".some-element").click().screenshot()', errors },
{ code: 'if(true) { cy.get(".some-element").click(); cy.screenshot(); }', errors },
{ code: 'cy.get(".some-element").click(); if(true) { cy.screenshot(); }', errors },
{ code: 'cy.get(".some-element"); function a() { cy.screenshot(); }', errors },
{ code: 'cy.get(".some-element"); const a = () => { cy.screenshot(); }', errors },
],
})

View File

@@ -0,0 +1,37 @@
'use strict'
const rule = require('../../../lib/rules/no-assigning-return-values')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('no-assigning-return-values', rule, {
valid: [
{ code: 'var foo = true;' },
{ code: 'let foo = true;' },
{ code: 'const foo = true;' },
{ code: 'const foo = bar();' },
{ code: 'const foo = bar().baz();' },
{ code: 'const spy = cy.spy();' },
{ code: 'const spy = cy.spy().as();' },
{ code: 'const stub = cy.stub();' },
{ code: 'const result = cy.now();' },
{ code: 'const state = cy.state();' },
{ code: 'cy.get("foo");' },
{ code: 'cy.contains("foo").click();' },
],
invalid: [
{ code: 'let a = cy.get("foo")', errors },
{ code: 'const a = cy.get("foo")', errors },
{ code: 'var a = cy.get("foo")', errors },
{ code: 'let a = cy.contains("foo")', errors },
{ code: 'let a = cy.window()', errors },
{ code: 'let a = cy.wait("@something")', errors },
{ code: 'let a = cy.contains("foo").click()', errors },
],
})

View File

@@ -0,0 +1,23 @@
'use strict'
const rule = require('../../../lib/rules/no-async-before')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('no-async-before', rule, {
valid: [
{ code: 'before(\'a before case\', () => { cy.get(\'.someClass\'); })' },
{ code: 'before(\'a before case\', async () => { await somethingAsync(); })' },
{ code: 'async function nonTestFn () { return await somethingAsync(); }' },
{ code: 'const nonTestArrowFn = async () => { await somethingAsync(); }' },
],
invalid: [
{ code: 'before(\'a test case\', async () => { cy.get(\'.someClass\'); })', errors },
{ code: 'beforeEach(\'a test case\', async () => { cy.get(\'.someClass\'); })', errors },
{ code: 'before(\'a test case\', async function () { cy.get(\'.someClass\'); })', errors },
{ code: 'beforeEach(\'a test case\', async function () { cy.get(\'.someClass\'); })', errors },
],
})

View File

@@ -0,0 +1,23 @@
'use strict'
const rule = require('../../../lib/rules/no-async-tests')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('no-async-tests', rule, {
valid: [
{ code: 'it(\'a test case\', () => { cy.get(\'.someClass\'); })' },
{ code: 'it(\'a test case\', async () => { await somethingAsync(); })' },
{ code: 'async function nonTestFn () { return await somethingAsync(); }' },
{ code: 'const nonTestArrowFn = async () => { await somethingAsync(); }' },
],
invalid: [
{ code: 'it(\'a test case\', async () => { cy.get(\'.someClass\'); })', errors },
{ code: 'test(\'a test case\', async () => { cy.get(\'.someClass\'); })', errors },
{ code: 'it(\'a test case\', async function () { cy.get(\'.someClass\'); })', errors },
{ code: 'test(\'a test case\', async function () { cy.get(\'.someClass\'); })', errors },
],
})

View File

@@ -0,0 +1,23 @@
'use strict'
const rule = require('../../../lib/rules/no-debug')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('no-debug', rule, {
valid: [
{ code: `debug()` },
{ code: `cy.get('button').dblclick()` },
],
invalid: [
{ code: `cy.debug()`, errors },
{ code: `cy.debug({ log: false })`, errors },
{ code: `cy.get('button').debug()`, errors },
{ code: `cy.get('a').should('have.attr', 'href').and('match', /dashboard/).debug()`, errors }
],
})

View File

@@ -0,0 +1,39 @@
'use strict'
const rule = require('../../../lib/rules/no-force')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('no-force', rule, {
valid: [
{ code: `cy.get('button').click()` },
{ code: `cy.get('button').click({multiple: true})` },
{ code: `cy.get('button').dblclick()` },
{ code: `cy.get('input').type('somth')` },
{ code: `cy.get('input').type('somth', {anyoption: true})` },
{ code: `cy.get('input').trigger('click', {anyoption: true})` },
{ code: `cy.get('input').rightclick({anyoption: true})` },
{ code: `cy.get('input').check()` },
{ code: `cy.get('input').select()` },
{ code: `cy.get('input').focus()` },
{ code: `cy.document().trigger("keydown", { ...event })` },
],
invalid: [
{ code: `cy.get('button').click({force: true})`, errors },
{ code: `cy.get('button').dblclick({force: true})`, errors },
{ code: `cy.get('input').type('somth', {force: true})`, errors },
{ code: `cy.get('div').find('.foo').type('somth', {force: true})`, errors },
{ code: `cy.get('div').find('.foo').find('.bar').click({force: true})`, errors },
{ code: `cy.get('div').find('.foo').find('.bar').trigger('change', {force: true})`, errors },
{ code: `cy.get('input').trigger('click', {force: true})`, errors },
{ code: `cy.get('input').rightclick({force: true})`, errors },
{ code: `cy.get('input').check({force: true})`, errors },
{ code: `cy.get('input').select({force: true})`, errors },
{ code: `cy.get('input').focus({force: true})`, errors },
],
})

View File

@@ -0,0 +1,23 @@
'use strict'
const rule = require('../../../lib/rules/no-pause')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('no-pause', rule, {
valid: [
{ code: `pause()` },
{ code: `cy.get('button').dblclick()` },
],
invalid: [
{ code: `cy.pause()`, errors },
{ code: `cy.pause({ log: false })`, errors },
{ code: `cy.get('button').pause()`, errors },
{ code: `cy.get('a').should('have.attr', 'href').and('match', /dashboard/).pause()`, errors }
],
})

View File

@@ -0,0 +1,47 @@
'use strict'
const rule = require('../../../lib/rules/no-unnecessary-waiting')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('no-unnecessary-waiting', rule, {
valid: [
{ code: 'foo.wait(10)' },
{ code: 'cy.wait("@someRequest")' },
{ code: 'cy.wait("@someRequest", { log: false })' },
{ code: 'cy.wait("@someRequest").then((xhr) => xhr)' },
{ code: 'cy.wait(["@someRequest", "@anotherRequest"])' },
{ code: 'cy.clock(5000)' },
{ code: 'cy.scrollTo(0, 10)' },
{ code: 'cy.tick(500)' },
{ code: 'const someRequest="@someRequest"; cy.wait(someRequest)' },
{ code: 'function customWait (alias = "@someRequest") { cy.wait(alias) }' },
{ code: 'const customWait = (alias = "@someRequest") => { cy.wait(alias) }' },
{ code: 'function customWait (ms) { cy.wait(ms) }' },
{ code: 'const customWait = (ms) => { cy.wait(ms) }' },
{ code: 'import BAR_BAZ from "bar-baz"; cy.wait(BAR_BAZ)' },
{ code: 'import { FOO_BAR } from "foo-bar"; cy.wait(FOO_BAR)' },
{ code: 'import * as wildcard from "wildcard"; cy.wait(wildcard.value)' },
{ code: 'import { NAME as OTHER_NAME } from "rename"; cy.wait(OTHER_NAME)' },
],
invalid: [
{ code: 'cy.wait(0)', errors },
{ code: 'cy.wait(100)', errors },
{ code: 'cy.wait(5000)', errors },
{ code: 'const someNumber=500; cy.wait(someNumber)', errors },
{ code: 'function customWait (ms = 1) { cy.wait(ms) }', errors },
{ code: 'const customWait = (ms = 1) => { cy.wait(ms) }', errors },
{ code: 'cy.get(".some-element").wait(10)', errors },
{ code: 'cy.get(".some-element").contains("foo").wait(10)', errors },
{ code: 'const customWait = (ms = 1) => { cy.get(".some-element").wait(ms) }', errors },
],
})

View File

@@ -0,0 +1,30 @@
'use strict'
const rule = require('../../../lib/rules/require-data-selectors')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('require-data-selectors', rule, {
valid: [
{ code: 'cy.get(\'[data-cy=submit]\').click()' },
{ code: 'cy.get(\'[data-QA=submit]\')' },
{ code: 'cy.clock(5000)' },
{ code: 'cy.scrollTo(0, 10)' },
{ code: 'cy.tick(500)' },
{ code: 'cy.get(\`[data-cy=${1}]\`)' }, // eslint-disable-line no-useless-escape
{ code: 'cy.get("@my-alias")' },
{ code: 'cy.get(`@my-alias`)' },
],
invalid: [
{ code: 'cy.get(\'[daedta-cy=submit]\').click()', errors },
{ code: 'cy.get(\'[d-cy=submit]\')', errors },
{ code: 'cy.get(".btn-large").click()', errors },
{ code: 'cy.get(".btn-.large").click()', errors },
{ code: 'cy.get(".a")', errors },
{ code: 'cy.get(\`[daedta-cy=${1}]\`)', errors }, // eslint-disable-line no-useless-escape
],
})

View File

@@ -0,0 +1,34 @@
'use strict'
const rule = require('../../../lib/rules/unsafe-to-chain-command')
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester()
const errors = [{ messageId: 'unexpected' }]
ruleTester.run('action-ends-chain', rule, {
valid: [
{
code: 'cy.get("new-todo").type("todo A{enter}"); cy.get("new-todo").type("todo B{enter}"); cy.get("new-todo").should("have.class", "active");'
},
{ code: 'cy.focused().should("be.visible");' },
{ code: 'cy.submitBtn().click();' },
],
invalid: [
{ code: 'cy.get("new-todo").type("todo A{enter}").should("have.class", "active");', errors },
{ code: 'cy.get("new-todo").type("todo A{enter}").type("todo B{enter}");', errors },
{ code: 'cy.get("new-todo").focus().should("have.class", "active");', errors },
{
code: 'cy.get("new-todo").customType("todo A{enter}").customClick();',
options: [{ methods: ['customType', 'customClick'] }],
errors,
},
{
code: 'cy.get("new-todo").customPress("Enter").customScroll();',
options: [{ methods: ['customPress', 'customScroll'] }],
errors,
},
],
})