├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── documentation.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── ci.yaml
├── .gitignore
├── .prettierrc
├── .vscode
├── extentions.json
└── settings.json
├── CHANGELOG.md
├── DEVELOP.md
├── LICENSE
├── README.md
├── app
├── index.html
├── index.js
└── lib
│ └── jquery-3.2.1.js
├── cypress.config.js
├── cypress
├── e2e
│ ├── attribute.cy.js
│ ├── copied
│ │ └── request.cy.js
│ ├── request.cy.js
│ ├── text.cy.js
│ ├── then.cy.js
│ ├── to.cy.js
│ └── utils
│ │ └── whitespace.cy.js
└── support
│ ├── bundle.js
│ ├── common.js
│ └── source.js
├── docs
├── attribute.md
├── request.md
├── text.md
├── then.md
└── to.md
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── attribute.js
├── index.js
├── request.js
├── text.js
├── then.js
├── to.js
└── utils
│ ├── commandError.js
│ ├── commandQueue.js
│ ├── errorMessages.js
│ ├── isJquery.js
│ ├── optionValidator.js
│ └── whitespace.js
├── tsconfig.json
└── types
├── attribute.d.ts
├── generic.d.ts
├── index.d.ts
├── text.d.ts
├── then.d.ts
└── to.d.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | # Used by various tools. Among which: VS Code, Prettier
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 4
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 | end_of_line = lf
12 |
13 | [*.ts]
14 | quote_type = single
15 |
16 | [*.js]
17 | quote_type = single
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 | indent_style = space
22 | indent_size = 2
23 |
24 | [*.yaml]
25 | indent_style = space
26 | indent_size = 2
27 |
28 | [{package.json,package-lock.json}]
29 | indent_style = space
30 | indent_size = 2
31 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | app
3 | dist
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable sonarjs/no-duplicate-string */
2 | module.exports = {
3 | plugins: ['sonarjs', 'cypress'],
4 | extends: ['google', 'plugin:sonarjs/recommended', 'plugin:cypress/recommended'],
5 | parserOptions: {
6 | sourceType: 'module',
7 | ecmaVersion: 8,
8 | },
9 | env: {
10 | node: true,
11 | es6: true,
12 | },
13 | rules: {
14 | 'indent': ['error', 4],
15 | 'max-len': ['error', 100],
16 | 'linebreak-style': 'off',
17 | 'no-multi-spaces': [
18 | 'error',
19 | {
20 | exceptions: {
21 | VariableDeclarator: true,
22 | },
23 | },
24 | ],
25 | 'object-curly-spacing': ['error', 'always'],
26 | 'comma-dangle': [
27 | 'error',
28 | {
29 | arrays: 'always-multiline',
30 | objects: 'always-multiline',
31 | imports: 'always-multiline',
32 | exports: 'always-multiline',
33 | functions: 'never',
34 | },
35 | ],
36 | 'space-before-function-paren': [
37 | 'error',
38 | {
39 | anonymous: 'always',
40 | named: 'never',
41 | asyncArrow: 'always',
42 | },
43 | ],
44 | 'quotes': ['error', 'single', { avoidEscape: true }],
45 | },
46 | overrides: [
47 | {
48 | files: ['cypress/e2e/**/*'],
49 | rules: {
50 | 'sonarjs/no-identical-functions': 'warn',
51 | 'sonarjs/no-duplicate-string': ['warn', 5],
52 | 'no-invalid-this': 'off',
53 | },
54 | },
55 | ],
56 | };
57 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | # Describe the bug
11 | A clear and concise description of what the bug is.
12 |
13 | # To Reproduce
14 | Steps or code to reproduce the behavior.
15 |
16 | # Expected behavior
17 | A clear and concise description of what you expected to happen.
18 |
19 | # Screenshots
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | # Versions and such:
23 | - OS:
24 | - Cypress version:
25 | - Cypress browser:
26 | - Cypress-commands version:
27 |
28 | # Additional context
29 | Add any other context about the problem here.
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Documentation
3 | about: Suggest improvements to the documentation
4 | title: ''
5 | labels: documentation
6 | assignees: ''
7 |
8 | ---
9 |
10 | # Changes should be made to
11 |
12 | - [ ] General documentation (ex. README.md)
13 | - [ ] Command documentation (ex. docs/then.md)
14 | - [ ] Command type definitions / Intellisense
15 |
16 | # Describe the improvement
17 | Which file and which part should be changed?
18 |
19 | # Why?
20 | Why do you think there should be a change?
21 |
22 | # Possible improvements
23 | How can we improve it? You probably don't need to answer this question if you are writing the improvements yourself.
24 |
25 | # Additional context
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest a command or feature
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | # Type of feature
11 |
12 | - [ ] Add a new command
13 | - [ ] Extend a default Cypress command
14 | - [ ] Change a Cypress-commands command
15 | - [ ] Other
16 |
17 | # What do you want to accomplish?
18 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
19 |
20 |
21 | # Why?
22 | A short description of why you want to accomplish the things mentioned above.
23 |
24 |
25 | # Describe possible implementations
26 |
27 | A clear and concise description of what you want to happen.
28 |
29 |
30 | # Additional context
31 | Add any other context or screenshots about the request here.
32 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Closes #
2 |
3 | - [ ] Tests
4 | - [ ] Documentation
5 | - [ ] Type definitions
6 | - [ ] Ready for merge
7 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [develop]
9 | pull_request:
10 | branches: [develop]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
20 | node-version: [16.x, 18.x]
21 |
22 | # Runs with `package.json` use the version defined in `~/package.lock.json`.
23 | cypress-version: [package.json]
24 |
25 | steps:
26 | - uses: actions/checkout@v2
27 | - name: Use Node.js ${{ matrix.node-version }}
28 | uses: actions/setup-node@v2
29 | with:
30 | node-version: ${{ matrix.node-version }}
31 | cache: 'npm'
32 | - run: npm ci
33 | - if: ${{ matrix.cypress-version != 'package.json' }}
34 | run: npm install cypress@${{ matrix.cypress-version }}
35 | - run: npm run lint
36 | - run: npm run test:source
37 | - run: npm run bundle
38 | - run: npm run test:bundle
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Cypress things
2 | screenshots
3 | videos
4 |
5 | # MacOS
6 | .DS_Store
7 |
8 | # Logs
9 | logs
10 | *.log
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | # Runtime data
16 | pids
17 | *.pid
18 | *.seed
19 | *.pid.lock
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # TypeScript v1 declaration files
47 | typings/
48 |
49 | # Optional npm cache directory
50 | .npm
51 |
52 | # Optional eslint cache
53 | .eslintcache
54 |
55 | # Optional REPL history
56 | .node_repl_history
57 |
58 | # Output of 'npm pack'
59 | *.tgz
60 |
61 | # Yarn Integrity file
62 | .yarn-integrity
63 |
64 | # dotenv environment variables file
65 | .env
66 |
67 | # next.js build output
68 | .next
69 |
70 | # Generated files
71 | dist
72 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": true,
4 | "bracketSpacing": true,
5 | "trailingComma": "es5",
6 | "proseWrap": "always",
7 | "printWidth": 100,
8 | "quoteProps": "consistent"
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/extentions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["esbenp.prettier-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "editor.defaultFormatter": "esbenp.prettier-vscode"
4 | }
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## 3.0.0 - 2022-06-06
6 |
7 | ## Breaking changes
8 |
9 | ### Dropped support for node@10
10 |
11 | Cypress 10 supports node@16 or up, to simplify CI we're no longer testing with Node@10. It should
12 | still work, but it's no longer guaranteed.
13 |
14 | ### Dropped support for Cypress@<=9
15 |
16 | Cypress 10 introduces a new configuration format. To simplify CI we're no longer testing with
17 | Cypress@9 or lower. It should still work, but it's no longer guaranteed.
18 |
19 | If you're not ready to upgrade to Cypress 10 yet, stick to cypress-commands@2.
20 |
21 | ## Changes
22 |
23 | ### Added support for Cypress@10
24 |
25 | Cypress 10 introduces breaking changes to the plugin system. None of these breaking changes have
26 | impact on `cypress-commands`. Changes to ensure support for Cypress@10 are mainly documentation
27 | changes.
28 |
29 | ## 2.0.1 - 2021-10-08
30 |
31 | ### Fixes
32 |
33 | - Type definition for `.attribute()` no longer requires you to pass an `options` object.
34 |
35 | ## 2.0.0 - 2021-09-13
36 |
37 | ### Breaking changes
38 |
39 | #### Whitespace handling for zero-width whitespace in `.text()` and `.attribute()`
40 |
41 | Whitespace handling in `.text()` and `.attribute()` has been changed to no longer consider
42 | zero-width whitespace to be whitespace in modes `{whitespace: 'simplify'}` and
43 | `{whitespace: 'keep-newline'}`. Mode `{whitespace: 'keep'}` has not changed.
44 |
45 |
46 | ```html
47 |
super\u200Bcalifragilistic\u200Bexpialidocious
48 | ```
49 |
50 | ```javascript
51 | // Old situation
52 | cy.get('div').text().should('equal', 'super califragilistic expialidocious');
53 | ```
54 |
55 | ```javascript
56 | // New situation
57 | cy.get('div').text().should('equal', 'supercalifragilisticexpialidocious');
58 | ```
59 |
60 | When using `.text()` on elements containing the `` tag: `` is now considered a zero-width
61 | space and will thus be removed with whitespace `simplify` and `keep-newline` as described above.
62 |
63 |
64 | ```html
65 |
supercalifragilisticexpialidocious
66 | ```
67 |
68 | ```javascript
69 | // Old situation
70 | cy.get('div').text().should('equal', 'super califragilistic expialidocious');
71 | ```
72 |
73 | ```javascript
74 | // New situation
75 | cy.get('div').text().should('equal', 'supercalifragilisticexpialidocious');
76 | ```
77 |
78 | #### Output order of `.text()`
79 |
80 | When using `.text({ depth: Number })` the order of texts has been changed to better reflect what the
81 | user sees. It will now first traverse all the way to the deepest point, before going sideways. This
82 | will make `.text()` behave much better with inline styling and links.
83 |
84 |
85 | ```html
86 |
87 | parent div top
88 |
89 | child div
90 |
91 | parent div middle
92 |
93 | second-child div
94 |
95 | parent div bottom
96 |
97 | ```
98 |
99 | ```javascript
100 | // Old situation
101 | // Note how the first part of the string is the various parts of `div.parent`
102 | cy.get('parent')
103 | .text({ depth: 1 })
104 | .should('equal', 'parent div top parent div middle parent div bottom child div second-child div');
105 | ```
106 |
107 | ```javascript
108 | // New situation
109 | cy.get('div')
110 | .text({ depth: 1 })
111 | .should('equal', 'parent div top child div parent div middle second-child div parent div bottom');
112 | ```
113 |
114 | Inline text formatting:
115 |
116 |
117 | ```html
118 |
121 | ```
122 |
123 | ```javascript
124 | // Old situation
125 | cy.get('div').text({ depth: 1 }).should('equal', 'Text with styling and . some a link');
126 | ```
127 |
128 | ```javascript
129 | // New situation
130 | cy.get('div').text({ depth: 1 }).should('equal', 'Text with some styling and a link.');
131 | ```
132 |
133 | #### Stricter types
134 |
135 | Types have been made stricter for `.attribute()`, `text()`, and `.to()`. This is a great improvement
136 | for TypeScript users as it reduces any manual casting. It allows for things like:
137 |
138 | ```typescript
139 | cy.get('div')
140 | .text() // yields type 'string | string[]'
141 | .to('array') // yields type 'string[]'
142 | .then((texts: string[]) => ...);
143 | ```
144 |
145 | ```typescript
146 | cy.get('div')
147 | .attribute('class') // yields type 'string | string[]'
148 | .to('array') // yields type 'string[]'
149 | .then((texts: string[]) => ...);
150 | ```
151 |
152 | ### Fixes
153 |
154 | - Support for Cypress 8.3.0 and above. There was a change in an internal API used for the
155 | `.attribute()` command. This internal API allows us to do some complex stuff with
156 | `{strict: true}`. The fix does not impact versions <= 8.2.0. See #60 for details.
157 |
158 | - `.attribute()` would not work properly in situations where it finds one attribute with a string
159 | length longer than the number of elements. For example:
160 |
161 |
162 | ```html
163 |
164 |
165 |
166 | ```
167 |
168 | ```javascript
169 | cy.get('div').attribute('data-foo'); // Throws error because `hello`.length > $elements.length
170 | ```
171 |
172 | This change also prompted some refactoring.
173 |
174 | - Updated docs based on changed made upstream in the Cypress docs.
175 |
176 | - Added config for Prettier/editorconfig and Eslint rules to match them. Reformatted a lot of files
177 | because of this.
178 |
179 | - Moved CI from Travis to Github. Now tests on multiple versions of NodeJS and multiple versions of
180 | Cypress.
181 |
182 | - Updated a lot of dependencies. It was over due.
183 |
184 | - Switched use of `path` to `path-browserify` to reduce config overhead for TypeScript users.
185 |
--------------------------------------------------------------------------------
/DEVELOP.md:
--------------------------------------------------------------------------------
1 | # How to develop
2 |
3 | ## Node version
4 |
5 | Anything above Node 8
6 |
7 | ## Running tests
8 |
9 | There are many ways to run tests. Here is a list
10 |
11 | | Command | Headless | Starts server | Code under test |
12 | | ------------------------------ | ------------------ | ------------------ | ------------------- |
13 | | `npm test` | :heavy_check_mark: | :heavy_check_mark: | Source files |
14 | | `npm run test:source` | :heavy_check_mark: | :heavy_check_mark: | Source files |
15 | | `npm run test:bundle` | :heavy_check_mark: | :heavy_check_mark: | Distribution bundle |
16 | | `npm run run:cypress` | :heavy_check_mark: | :x: | Source files |
17 | | `npm run run:cypress:source` | :heavy_check_mark: | :x: | Source files |
18 | | `npm run run:cypress:bundle` | :heavy_check_mark: | :x: | Distribution bundle |
19 | | `npm start` | :x: | :heavy_check_mark: | Source files |
20 | | `npm run start:source` | :x: | :heavy_check_mark: | Source files |
21 | | `npm run start:bundle` | :x: | :heavy_check_mark: | Distribution bundle |
22 | | `npm run start:cypress` | :x: | :x: | Source files |
23 | | `npm run start:cypress:source` | :x: | :x: | Source files |
24 | | `npm run start:cypress:bundle` | :x: | :x: | Distribution bundle |
25 |
26 | For commands that don't start their own server you'll need to run `npm run start:server` in a second
27 | command line.
28 |
29 | ## Publishing
30 |
31 | `npm publish` will run tests that have to pass before allowing you to publish.
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Sander van Beek
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cypress commands
2 |
3 | [](https://badge.fury.io/js/cypress-commands)
4 |
5 | A collection of high-quality Cypress commands to complement and extend the defaults.
6 |
7 | This repository is not maintained by the Cypress developers. This means we can choose to ignore
8 | parts of their vision.
9 |
10 | Documentation is a cornerstone of Cypress, the commands in this repository will try to keep these
11 | documentation standards.
12 |
13 | ## Cypress version
14 |
15 | `cypress-commands` should work with the latest version of Cypress. If this is not the case, please
16 | open an issue.
17 |
18 | It's tested against multiple versions of Cypress. See the
19 | [CI definition](./.github/workflows/ci.yaml) for the most up-to-date list.
20 |
21 | ## Installation
22 |
23 | Install the module.
24 |
25 | ```shell
26 | npm install cypress-commands
27 | ```
28 |
29 | Add the following line to `cypress/support/index.js`.
30 |
31 | ```javascript
32 | require('cypress-commands');
33 | ```
34 |
35 | ### Type definitions
36 |
37 | Import typescript definitions by adding them to your `tsconfig.json`. Add the cypress-commands types
38 | before the Cypress types so intellisense will prefer the cypress-commands versions of extended
39 | commands.
40 |
41 | ```json
42 | {
43 | "compilerOptions": {
44 | "types": ["cypress-commands", "cypress"]
45 | }
46 | }
47 | ```
48 |
49 | #### Known issue: `cypress.config.ts` limitation
50 |
51 | Due to the way Cypress defines its types, it's currently not possible for plugin authors to extend
52 | the Cypress config types.
53 |
54 | Because of this limitation, it's not possible to set the `requestBaseUrl` option in
55 | `cypress.config.ts`. For the time being, you can work around this limitation by using
56 | `cypress.config.js` instead.
57 |
58 | See https://github.com/cypress-io/cypress/issues/22127 for more details.
59 |
60 | ## Extended commands
61 |
62 | These commands have been extended to be able to do more than originally intended. For these
63 | commands, all tests that exist in the Cypress repository are copied to make sure the default
64 | behaviour stays identical unless we want it changed.
65 |
66 | - [`.request()`](./docs/request.md)
67 | - [`.then()`](./docs/then.md)
68 |
69 | ## Added commands
70 |
71 | These commands do not exist in Cypress by default.
72 |
73 | - [`.attribute()`](./docs/attribute.md)
74 | - [`.text()`](./docs/text.md)
75 | - [`.to()`](./docs/to.md)
76 |
77 | ## Contributing
78 |
79 | Contributors are always welcome! I don't care if you are a beginner or an expert, all help is
80 | welcome.
81 |
82 | ## Running tests
83 |
84 | First clone the repository and install the dependencies.
85 |
86 | ### GUI mode
87 |
88 | ```shell
89 | npm start
90 | ```
91 |
92 | ### CLI mode
93 |
94 | ```shell
95 | npm test
96 | ```
97 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | jQuery 3.2.1 Fixture
5 |
6 |
7 |
14 |
15 |
16 | foo
17 |
`);
5 | }
6 |
7 | const elems = [$('.counter'), $('input')];
8 |
9 | elems.forEach((elem) => {
10 | const val = +elem.text();
11 | if (val < 5) {
12 | elem.text(val + 1);
13 | elem.val(val + 1);
14 | elem.attr('data-attr', val + 1);
15 | }
16 | });
17 | }, 100);
18 |
--------------------------------------------------------------------------------
/cypress.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'cypress';
2 |
3 | export default defineConfig({
4 | video: false,
5 |
6 | e2e: {
7 | baseUrl: 'http://localhost:1337/',
8 | requestBaseUrl: 'http://api.localhost:1337',
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/cypress/e2e/attribute.cy.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable sonarjs/no-duplicate-string */
2 |
3 | const COMMAND_TIMEOUT = 4000;
4 |
5 | describe('The added command `attribute`', function () {
6 | before(function () {
7 | cy.visit('/');
8 | });
9 |
10 | beforeEach(function () {
11 | Cypress.config('defaultCommandTimeout', COMMAND_TIMEOUT);
12 | });
13 |
14 | it('considers empty attributes to be existing, but empty', function () {
15 | cy.get('.parent')
16 | .attribute('data-foo')
17 | .should('equal', '')
18 | .should('exist')
19 | .should('be.empty');
20 | });
21 |
22 | it('retries', function () {
23 | cy.get('form input').attribute('data-attr').should('equal', '5');
24 | });
25 |
26 | describe('handles implicit assertions correctly', function () {
27 | let __logs;
28 | let __lastLog;
29 |
30 | beforeEach(function () {
31 | Cypress.config('defaultCommandTimeout', 50);
32 |
33 | __logs = [];
34 |
35 | cy.on('log:added', (_, log) => {
36 | __lastLog = log;
37 | __logs.push(log);
38 | });
39 |
40 | return null;
41 | });
42 |
43 | describe('implicit assertion `to.exist`', function () {
44 | it('throws when the attribute does not exist', function (done) {
45 | cy.on('fail', (err) => {
46 | const lastLog = __lastLog;
47 |
48 | expect(__logs.length).to.eq(2);
49 | expect(lastLog.get('error')).to.eq(err);
50 | expect(err.message).to.include(
51 | "Expected element to have attribute 'id', but never found it."
52 | );
53 | done();
54 | });
55 |
56 | cy.get('.whitespace').attribute('id');
57 | });
58 |
59 | it('does not throw when the attribute does exist', function () {
60 | cy.get('.whitespace').attribute('class');
61 | });
62 | });
63 |
64 | describe('Overwriting implicit assertions', function () {
65 | it('can explicitly assert existence', function (done) {
66 | cy.on('fail', (err) => {
67 | expect(__logs.length).to.eq(3);
68 | expect(err.message).to.include(
69 | "Expected element to have attribute 'id', but never found it."
70 | );
71 | done();
72 | });
73 |
74 | cy.get('.whitespace').attribute('id').should('exist');
75 | });
76 |
77 | it('overwrites implicit assertion when testing for non-existence', function (done) {
78 | cy.on('fail', (err) => {
79 | expect(__logs.length).to.eq(3);
80 | expect(err.message).to.include(
81 | "Expected element to not have attribute 'class', " +
82 | 'but it was continuously found.'
83 | );
84 | done();
85 | });
86 |
87 | cy.get('.whitespace').attribute('class').should('not.exist');
88 | });
89 | });
90 | });
91 |
92 | describe('The attribute of a single element', function () {
93 | it('yields a string', function () {
94 | cy.get('.whitespace').attribute('class').should('equal', 'whitespace');
95 | });
96 |
97 | it('yields the first when an attribute exists twice', function () {
98 | cy.get('.great-great-grandchild').attribute('data-foo').should('equal', 'bar');
99 | });
100 | });
101 |
102 | describe('The attribute of a multiple elements', function () {
103 | it('yields an array of strings', function () {
104 | cy.get('.parent > div')
105 | .attribute('data-relation')
106 | .should('have.lengthOf', 2)
107 | .should('deep.equal', ['child', 'child']);
108 | });
109 | });
110 |
111 | describe('options', function () {
112 | let __logs;
113 | let __lastLog;
114 |
115 | beforeEach(function () {
116 | Cypress.config('defaultCommandTimeout', 50);
117 |
118 | __logs = [];
119 |
120 | cy.on('log:added', (_, log) => {
121 | __lastLog = log;
122 | __logs.push(log);
123 | });
124 |
125 | return null;
126 | });
127 |
128 | it('does not mind flipping the order of properties', function () {
129 | cy.get('.whitespace').attribute({}, 'class').should('equal', 'whitespace');
130 |
131 | cy.get('.whitespace').attribute('class', {}).should('equal', 'whitespace');
132 | });
133 |
134 | it('does not log with `log: false`', function () {
135 | cy.get('.whitespace')
136 | .attribute('class', { log: false })
137 | .then(() => {
138 | const lastLog = __lastLog;
139 |
140 | expect(__logs.length).to.equal(1);
141 | expect(lastLog.get().name).to.equal('get');
142 | })
143 | .should('equal', 'whitespace');
144 | });
145 |
146 | describe('strict', function () {
147 | it('throws when not all subjects have the attribute', function (done) {
148 | cy.on('fail', (err) => {
149 | expect(__logs.length).to.eq(2);
150 | expect(err.message).to.include(
151 | 'Expected all 4 elements to have attribute ' +
152 | "'data-relation', but never found it on 1 elements."
153 | );
154 | done();
155 | });
156 |
157 | cy.get('.parent > div > div, .parent > div')
158 | // strict: true is default
159 | .attribute('data-relation', {});
160 | });
161 |
162 | it('throws when only 1 subject has the attribute', function (done) {
163 | cy.on('fail', (err) => {
164 | expect(__logs.length).to.eq(2);
165 | expect(err.message).to.include(
166 | 'Expected all 4 elements to have attribute ' +
167 | "'data-hello', but never found it on 3 elements."
168 | );
169 | done();
170 | });
171 |
172 | cy.get('.parent > div > div, .parent > div')
173 | // strict: true is default
174 | .attribute('data-hello', {});
175 | });
176 |
177 | it(
178 | 'does not throw when not all subjects have the attribute ' + 'and `strict: false`',
179 | function () {
180 | cy.get('.parent div').attribute('data-relation', { strict: false });
181 | }
182 | );
183 |
184 | it(
185 | 'only yields the values of elements with the attribute when' + '`strict: false`',
186 | function () {
187 | cy.get('.parent div')
188 | .attribute('data-relation', { strict: false })
189 | .should('be.lengthOf', 3)
190 | .and('deep.equal', ['child', 'grandchild', 'child']);
191 | }
192 | );
193 |
194 | context('Upcoming assertions', function () {
195 | describe('should exist', function () {
196 | it('throws when not all subjects have the attribute', function (done) {
197 | cy.on('fail', (err) => {
198 | expect(__logs.length).to.eq(3);
199 | expect(err.message).to.include(
200 | 'Expected all 4 elements to have attribute ' +
201 | "'data-relation', but never found it on 1 elements."
202 | );
203 | done();
204 | });
205 |
206 | cy.get('.parent > div > div, .parent > div')
207 | .attribute('data-relation', { strict: true })
208 | .should('exist');
209 | });
210 |
211 | it('does not throw when all subjects have the attribute', function () {
212 | cy.get('.parent > div')
213 | .attribute('data-relation', { strict: true })
214 | .should('exist');
215 | });
216 | });
217 |
218 | describe('should not exist', function () {
219 | it('does not throw when none of the subjects have attribute', function () {
220 | cy.get('.parent > div > div, .parent > div')
221 | .attribute('data-nonExistent', { strict: true })
222 | .should('not.exist');
223 | });
224 |
225 | it('throws when some of the subjects have attribute', function (done) {
226 | cy.on('fail', (err) => {
227 | expect(__logs.length).to.eq(3);
228 | expect(err.message).to.include(
229 | 'Expected all 4 elements to not have attribute ' +
230 | "'data-relation', but it was continuously found on 3 " +
231 | 'elements.'
232 | );
233 | done();
234 | });
235 |
236 | cy.get('.parent > div > div, .parent > div')
237 | .attribute('data-relation', { strict: true })
238 | .should('not.exist');
239 | });
240 |
241 | /**
242 | * Initial support for negating existence in strict mode depended on Cypress'
243 | * logging framework. This resulted in unexpected behaviour when `log: false`.
244 | */
245 | it(
246 | 'throws when some of the subjects have attribute and ' + '`log: false`',
247 | function (done) {
248 | cy.on('fail', (err) => {
249 | expect(__logs.length).to.eq(2);
250 | expect(err.message).to.include(
251 | 'Expected all 4 elements to not have attribute ' +
252 | "'data-relation', but it was continuously found on 3 " +
253 | 'elements.'
254 | );
255 | done();
256 | });
257 |
258 | cy.get('.parent > div > div, .parent > div')
259 | .attribute('data-relation', { strict: true, log: false })
260 | .should('not.exist');
261 | }
262 | );
263 |
264 | /**
265 | * Bug with checking if the upcoming assertions negate existence
266 | */
267 | it(
268 | 'throws when in the second call to attribute some of ' +
269 | 'the subjects have attribute',
270 | function (done) {
271 | cy.on('fail', (err) => {
272 | expect(__logs.length).to.eq(6);
273 | expect(err.message).to.include(
274 | 'Expected all 4 elements to have attribute ' +
275 | "'data-relation', but never found it on 1 elements"
276 | );
277 | done();
278 | });
279 |
280 | cy.get('.parent > div > div, .parent > div')
281 | .attribute('data-rel', { strict: true })
282 | .should('not.exist');
283 |
284 | cy.get('.parent > div > div, .parent > div')
285 | .attribute('data-relation', { strict: true })
286 | .should('exist');
287 | }
288 | );
289 | });
290 | });
291 | });
292 |
293 | describe('whitespace', function () {
294 | it('`keep` is the default value', function () {
295 | cy.get('div.whitespace')
296 | .attribute('data-complex')
297 | .should('equal', ' some \t very\n complex\twhitespace');
298 | });
299 |
300 | it('`simplify` simplifies all whitespace', function () {
301 | cy.get('div.whitespace')
302 | .attribute('data-complex', { whitespace: 'simplify' })
303 | .should('equal', 'some very complex whitespace');
304 | });
305 |
306 | it('`keep-newline` simplifies all whitespace except newlines', function () {
307 | cy.get('div.whitespace')
308 | .attribute('data-complex', { whitespace: 'keep-newline' })
309 | .should('equal', 'some very\ncomplex whitespace');
310 | });
311 |
312 | it('`keep` does not change whitespace at all', function () {
313 | cy.get('div.whitespace')
314 | .attribute('data-complex', { whitespace: 'keep' })
315 | .should('equal', ' some \t very\n complex\twhitespace');
316 | });
317 | });
318 | });
319 | });
320 |
--------------------------------------------------------------------------------
/cypress/e2e/request.cy.js:
--------------------------------------------------------------------------------
1 | const _ = Cypress._;
2 | const RESPONSE_TIMEOUT = 22222;
3 | const initialBaseUrl = Cypress.config().baseUrl;
4 | const initialRequestBaseUrl = Cypress.config().requestBaseUrl;
5 |
6 | describe('Overwritten command request', function () {
7 | afterEach(function () {
8 | Cypress.config('baseUrl', initialBaseUrl);
9 | Cypress.config('requestBaseUrl', initialRequestBaseUrl);
10 | });
11 |
12 | beforeEach(function () {
13 | cy.stub(Cypress, 'backend').callThrough();
14 | Cypress.config('responseTimeout', RESPONSE_TIMEOUT);
15 | });
16 |
17 | describe('argument signature', function () {
18 | beforeEach(function () {
19 | const backend = Cypress.backend.withArgs('http:request').resolves({
20 | isOkStatusCode: true,
21 | status: 200,
22 | });
23 |
24 | this.expectOptionsToBe = function (opts) {
25 | _.defaults(opts, {
26 | failOnStatusCode: true,
27 | retryOnNetworkFailure: true,
28 | retryOnStatusCodeFailure: false,
29 | gzip: true,
30 | followRedirect: true,
31 | timeout: RESPONSE_TIMEOUT,
32 | method: 'GET',
33 | encoding: 'utf8',
34 | retryIntervals: [0, 100, 200, 200],
35 | });
36 |
37 | const options = backend.firstCall.args[1];
38 |
39 | _.each(options, function (value, key) {
40 | expect(options[key]).to.deep.eq(opts[key], `failed on property: (${key})`);
41 | });
42 |
43 | _.each(opts, function (value, key) {
44 | expect(opts[key]).to.deep.eq(options[key], `failed on property: (${key})`);
45 | });
46 | };
47 | });
48 |
49 | it('prefixes with requestBaseUrl set in cypress config, origin url is empty', function () {
50 | cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('');
51 |
52 | // Use requestBaseUrl from Cypress config
53 | Cypress.config('baseUrl', 'http://localhost:8080/app');
54 |
55 | cy.request('/foo/bar?cat=1').then(() => {
56 | this.expectOptionsToBe({
57 | url: 'http://api.localhost:1337/foo/bar?cat=1',
58 | method: 'GET',
59 | gzip: true,
60 | followRedirect: true,
61 | timeout: RESPONSE_TIMEOUT,
62 | });
63 | });
64 | });
65 |
66 | it('prefixes with requestBaseUrl when origin url is empty', function () {
67 | cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('');
68 |
69 | Cypress.config('requestBaseUrl', 'http://api.localhost:8080/app');
70 | Cypress.config('baseUrl', 'http://localhost:8080/app');
71 |
72 | cy.request('/foo/bar?cat=1').then(() => {
73 | this.expectOptionsToBe({
74 | url: 'http://api.localhost:8080/app/foo/bar?cat=1',
75 | method: 'GET',
76 | gzip: true,
77 | followRedirect: true,
78 | timeout: RESPONSE_TIMEOUT,
79 | });
80 | });
81 | });
82 |
83 | it('prefixes baseUrl when originUrl is empty and the requestBaseUrl is empty', function () {
84 | cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('');
85 |
86 | Cypress.config('requestBaseUrl', '');
87 | Cypress.config('baseUrl', 'http://localhost:8080/app');
88 |
89 | cy.request('/foo/bar?cat=1').then(() => {
90 | this.expectOptionsToBe({
91 | url: 'http://localhost:8080/app/foo/bar?cat=1',
92 | method: 'GET',
93 | gzip: true,
94 | followRedirect: true,
95 | timeout: RESPONSE_TIMEOUT,
96 | });
97 | });
98 | });
99 |
100 | it('prefixes baseUrl when originUrl is empty and the requestBaseUrl is null', function () {
101 | cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('');
102 |
103 | Cypress.config('requestBaseUrl', null);
104 | Cypress.config('baseUrl', 'http://localhost:8080/app');
105 |
106 | cy.request('/foo/bar?cat=1').then(() => {
107 | this.expectOptionsToBe({
108 | url: 'http://localhost:8080/app/foo/bar?cat=1',
109 | method: 'GET',
110 | gzip: true,
111 | followRedirect: true,
112 | timeout: RESPONSE_TIMEOUT,
113 | });
114 | });
115 | });
116 |
117 | it('prefixes baseUrl when originUrl is empty and requestBaseUrl is undefined', function () {
118 | cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('');
119 |
120 | Cypress.config('requestBaseUrl', undefined);
121 | Cypress.config('baseUrl', 'http://localhost:8080/app');
122 |
123 | cy.request('/foo/bar?cat=1').then(() => {
124 | this.expectOptionsToBe({
125 | url: 'http://localhost:8080/app/foo/bar?cat=1',
126 | method: 'GET',
127 | gzip: true,
128 | followRedirect: true,
129 | timeout: RESPONSE_TIMEOUT,
130 | });
131 | });
132 | });
133 | });
134 | });
135 |
--------------------------------------------------------------------------------
/cypress/e2e/text.cy.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable sonarjs/no-duplicate-string */
2 |
3 | const $ = Cypress.$;
4 | const COMMAND_TIMEOUT = 4000;
5 |
6 | describe('The added command `text`', function () {
7 | let body;
8 |
9 | before(function () {
10 | cy.visit('/').then((win) => {
11 | body = win.document.body.outerHTML;
12 | });
13 | });
14 |
15 | beforeEach(function () {
16 | Cypress.config('defaultCommandTimeout', COMMAND_TIMEOUT);
17 |
18 | const doc = cy.state('document');
19 | $(doc.body).empty().html(body);
20 | });
21 |
22 | it('yields text from a generic DOM element', function () {
23 | cy.get('div').first().text().should('equal', 'div');
24 | });
25 |
26 | it('yields the value of a button element', function () {
27 | cy.get('button').text().should('equal', 'Some button');
28 | });
29 |
30 | it('yields the value of a textarea element', function () {
31 | cy.get('textarea').text().should('equal', 'A filled textarea');
32 | });
33 |
34 | it('retries the value of a generic DOM element', function () {
35 | cy.get('.counter').text().should('equal', '5');
36 | });
37 |
38 | it('retries the value of an input element', function () {
39 | cy.get('input').text().should('equal', '5');
40 | });
41 |
42 | describe('returns', function () {
43 | it('returns a string if the input is a single element', function () {
44 | cy.get('#foo').text().should('equal', 'foo');
45 | });
46 |
47 | it('returns an array if the input is multiple elements', function () {
48 | cy.get('.parent')
49 | .children()
50 | .text()
51 | .should((texts) => {
52 | expect(texts).to.be.lengthOf(2);
53 | expect(texts[0]).to.equal('child div');
54 | expect(texts[1]).to.equal('second-child div');
55 | });
56 | });
57 | });
58 |
59 | describe('The option `depth`', function () {
60 | it('`0` is the default value', function () {
61 | cy.get('div.parent')
62 | .text()
63 | .should(
64 | 'equal',
65 | ['parent div top', 'parent div middle', 'parent div bottom'].join(' ')
66 | );
67 | });
68 |
69 | it('`0` results in only the contents of the element itself', function () {
70 | cy.get('div.parent')
71 | .text({ depth: 0 })
72 | .should(
73 | 'equal',
74 | ['parent div top', 'parent div middle', 'parent div bottom'].join(' ')
75 | );
76 | });
77 |
78 | it('`1` results in the contents of the element and its direct children', function () {
79 | cy.get('div.parent')
80 | .text({ depth: 1 })
81 | .should(
82 | 'equal',
83 | [
84 | 'parent div top',
85 | 'child div',
86 | 'parent div middle',
87 | 'second-child div',
88 | 'parent div bottom',
89 | ].join(' ')
90 | );
91 | });
92 |
93 | it('`2` results in the contents of the element and its direct children', function () {
94 | cy.get('div.parent')
95 | .text({ depth: 2 })
96 | .should(
97 | 'equal',
98 | [
99 | 'parent div top',
100 | 'child div',
101 | 'grandchild div',
102 | 'parent div middle',
103 | 'second-child div',
104 | 'second-grand-child div',
105 | 'parent div bottom',
106 | ].join(' ')
107 | );
108 | });
109 |
110 | it('`Infinity` results in the contents of the element and all its children', function () {
111 | cy.get('div.parent')
112 | .text({ depth: Infinity })
113 | .should(
114 | 'equal',
115 | [
116 | 'parent div top',
117 | 'child div',
118 | 'grandchild div',
119 | 'great-grandchild div',
120 | 'great-great-grandchild div',
121 | 'parent div middle',
122 | 'second-child div',
123 | 'second-grand-child div',
124 | 'parent div bottom',
125 | ].join(' ')
126 | );
127 | });
128 |
129 | it('gets all values of form elements', function () {
130 | cy.get('form').text({ depth: 1 }).should('equal', '5 A filled textarea Some button');
131 | });
132 | });
133 |
134 | describe('The option `whitespace`', function () {
135 | it('`simplify` is the default value', function () {
136 | cy.get('div.whitespace')
137 | .text()
138 | .should('equal', 'div containing some complex whitespace');
139 |
140 | cy.get('div.formatted')
141 | .text({ depth: 9 })
142 | .should('equal', 'Some text with inline formatting applied to it.');
143 | });
144 |
145 | it('`simplify` simplifies all whitespace', function () {
146 | cy.get('div.whitespace')
147 | .text({ whitespace: 'simplify' })
148 | .should('equal', 'div containing some complex whitespace');
149 |
150 | cy.get('div.formatted')
151 | .text({ depth: 9, whitespace: 'simplify' })
152 | .should('equal', 'Some text with inline formatting applied to it.');
153 | });
154 |
155 | it('`keep-newline` simplifies all whitespace except newlines', function () {
156 | cy.get('div.whitespace')
157 | .text({ whitespace: 'keep-newline' })
158 | .should('equal', 'div containing some\ncomplex whitespace');
159 |
160 | cy.get('div.formatted')
161 | .text({ depth: 9, whitespace: 'keep-newline' })
162 | .should('equal', 'Some text with inline\nformatting applied to it.');
163 | });
164 |
165 | it('`keep` does not change whitespace at all', function () {
166 | cy.get('div.whitespace')
167 | .text({ whitespace: 'keep' })
168 | .should((text) => {
169 | const lines = text.split('\n');
170 | console.log(lines);
171 |
172 | expect(lines[0]).to.equal('div cont\u200Baining\xa0 \xa0 \t some ');
173 | expect(lines[1]).to.equal(' complex\twhite\u200Bspace');
174 | });
175 |
176 | cy.get('div.formatted')
177 | .text({ depth: 9, whitespace: 'keep' })
178 | .should((text) => {
179 | expect(text.trim()).to.equal(
180 | 'Some text with inline\n formatting applied to it.'
181 | );
182 | });
183 | });
184 | });
185 |
186 | describe('errors', function () {
187 | let __logs;
188 | let __lastLog;
189 |
190 | beforeEach(function () {
191 | Cypress.config('defaultCommandTimeout', 50);
192 |
193 | __logs = [];
194 |
195 | cy.on('log:added', (_, log) => {
196 | __lastLog = log;
197 | __logs.push(log);
198 | });
199 |
200 | return null;
201 | });
202 |
203 | it('is called as a parent command', function (done) {
204 | cy.on('fail', (err) => {
205 | const lastLog = __lastLog;
206 |
207 | expect(__logs.length).to.eq(1);
208 | expect(lastLog.get('error')).to.eq(err);
209 | expect(err.message).to.include(
210 | 'Oops, it looks like you are trying to call a child ' +
211 | 'command before running a parent command.'
212 | );
213 | done();
214 | });
215 |
216 | cy.text();
217 | });
218 |
219 | it('not preceded with an element', function (done) {
220 | cy.on('fail', (err) => {
221 | const lastLog = __lastLog;
222 |
223 | expect(__logs.length).to.eq(2);
224 | expect(lastLog.get('error')).to.eq(err);
225 | expect(err.message).to.include(
226 | '`cy.text()` failed because it requires a DOM element.'
227 | );
228 | done();
229 | });
230 |
231 | cy.wrap('foo').text();
232 | });
233 |
234 | it('wrong value for the option `depth`', function (done) {
235 | cy.on('fail', (err) => {
236 | const lastLog = __lastLog;
237 |
238 | expect(__logs.length).to.eq(2);
239 | expect(lastLog.get('error')).to.eq(err);
240 | expect(err.message).to.include(
241 | 'Bad value for the option "depth" of the command "text"'
242 | );
243 | done();
244 | });
245 |
246 | cy.get('#foo').text({ depth: -1 });
247 | });
248 |
249 | it('wrong value for the option `whitespace`', function (done) {
250 | cy.on('fail', (err) => {
251 | const lastLog = __lastLog;
252 |
253 | expect(__logs.length).to.eq(2);
254 | expect(lastLog.get('error')).to.eq(err);
255 | expect(err.message)
256 | .to.include('Bad value for the option "whitespace" of the command "text"')
257 | .and.include('["simplify","keep","keep-newline"]');
258 | done();
259 | });
260 |
261 | cy.get('#foo').text({ whitespace: 1 });
262 | });
263 |
264 | it('wrong value for the option `log`', function (done) {
265 | cy.on('fail', (err) => {
266 | const lastLog = __lastLog;
267 |
268 | expect(__logs.length).to.eq(2);
269 | expect(lastLog.get('error')).to.eq(err);
270 | expect(err.message)
271 | .to.include('Bad value for the option "log" of the command "text"')
272 | .and.include('[true,false]');
273 | done();
274 | });
275 |
276 | cy.get('#foo').text({ log: 1 });
277 | });
278 | });
279 | });
280 |
--------------------------------------------------------------------------------
/cypress/e2e/then.cy.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable cypress/no-assigning-return-values */
2 | /* eslint-disable cypress/no-unnecessary-waiting */
3 |
4 | const $ = Cypress.$;
5 | const Promise = Cypress.Promise;
6 |
7 | describe('The overwritten command `then`', function () {
8 | let body;
9 |
10 | before(function () {
11 | cy.visit('/').then((win) => {
12 | body = win.document.body.outerHTML;
13 | });
14 | });
15 |
16 | describe('Tests copied from Cypress repo', function () {
17 | beforeEach(function () {
18 | const doc = cy.state('document');
19 | $(doc.body).empty().html(body);
20 | });
21 |
22 | it('converts raw DOM elements', function () {
23 | const div = cy.$$('div:first').get(0);
24 |
25 | cy.wrap(div).then(($div) => {
26 | expect($div.get(0)).to.eq(div);
27 | });
28 | });
29 |
30 | it('does not insert a mocha callback', function () {
31 | cy.noop().then(() => {
32 | expect(cy.queue.length).to.eq(2);
33 | });
34 | });
35 |
36 | it('passes timeout option to then', function () {
37 | cy.timeout(50);
38 |
39 | cy.then({ timeout: 150 }, function () {
40 | return Promise.delay(100);
41 | });
42 | });
43 |
44 | it('can resolve nested thens', function () {
45 | cy.get('div:first').then(() => {
46 | cy.get('div:first').then(() => {
47 | cy.get('div:first');
48 | });
49 | });
50 | });
51 |
52 | it('can resolve cypress commands inside of a promise', function () {
53 | let _then = false;
54 |
55 | cy.wrap(null)
56 | .then(() => {
57 | return Promise.delay(10).then(() => {
58 | cy.then(() => {
59 | _then = true;
60 | });
61 | });
62 | })
63 | .then(() => {
64 | expect(_then).to.be.true;
65 | });
66 | });
67 |
68 | it('can resolve chained cypress commands inside of a promise', function () {
69 | let _then = false;
70 |
71 | cy.wrap(null)
72 | .then(() => {
73 | return Promise.delay(10).then(() => {
74 | cy.get('div:first').then(() => {
75 | _then = true;
76 | });
77 | });
78 | })
79 | .then(() => {
80 | expect(_then).to.be.true;
81 | });
82 | });
83 |
84 | it('can resolve cypress instance inside of a promise', function () {
85 | cy.then(() => {
86 | Promise.delay(10).then(() => cy);
87 | });
88 | });
89 |
90 | it('passes values to the next command', function () {
91 | cy.wrap({ foo: 'bar' })
92 | .then((obj) => obj.foo)
93 | .then((val) => {
94 | expect(val).to.eq('bar');
95 | });
96 | });
97 |
98 | it('does not throw when returning thenables with cy command', function () {
99 | cy.wrap({ foo: 'bar' }).then((obj) => {
100 | return new Promise((resolve) => {
101 | cy.wait(10);
102 |
103 | resolve(obj.foo);
104 | });
105 | });
106 | });
107 |
108 | it('should pass the eventual resolved thenable value downstream', function () {
109 | cy.wrap({ foo: 'bar' })
110 | .then((obj) => {
111 | cy.wait(10)
112 | .then(() => obj.foo)
113 | .then((value) => {
114 | expect(value).to.eq('bar');
115 |
116 | return value;
117 | });
118 | })
119 | .then((val) => {
120 | expect(val).to.eq('bar');
121 | });
122 | });
123 |
124 | it(
125 | 'should not pass the eventual resolve thenable value downstream because ' +
126 | 'thens are not connected',
127 | function () {
128 | cy.wrap({ foo: 'bar' }).then((obj) => {
129 | cy.wait(10)
130 | .then(() => obj.foo)
131 | .then((value) => {
132 | expect(value).to.eq('bar');
133 |
134 | return value;
135 | });
136 | });
137 | cy.then((val) => {
138 | expect(val).to.be.undefined;
139 | });
140 | }
141 | );
142 |
143 | it('passes the existing subject if ret is undefined', function () {
144 | cy.wrap({ foo: 'bar' })
145 | .then(() => undefined)
146 | .then((obj) => {
147 | expect(obj).to.deep.eq({ foo: 'bar' });
148 | });
149 | });
150 |
151 | it('sets the subject to null when given null', function () {
152 | cy.wrap({ foo: 'bar' })
153 | .then(() => null)
154 | .then((obj) => {
155 | expect(obj).to.be.null;
156 | });
157 | });
158 |
159 | describe('errors', function () {
160 | let __logs;
161 | let __lastLog;
162 |
163 | beforeEach(function () {
164 | Cypress.config('defaultCommandTimeout', 50);
165 |
166 | __logs = [];
167 |
168 | cy.on('log:added', (_, log) => {
169 | __lastLog = log;
170 | __logs.push(log);
171 | });
172 |
173 | return null;
174 | });
175 |
176 | it('throws when promise timeout', function (done) {
177 | cy.on('fail', (err) => {
178 | const lastLog = __lastLog;
179 |
180 | expect(__logs.length).to.eq(1);
181 | expect(lastLog.get('error')).to.eq(err);
182 | expect(err.message).to.include('`cy.then()` timed out after waiting `150ms`.');
183 | done();
184 | });
185 |
186 | cy.then({ timeout: 150 }, () => {
187 | return new Promise(() => {});
188 | });
189 | });
190 |
191 | it('throws when mixing up async + sync return values', function (done) {
192 | cy.on('fail', (err) => {
193 | const lastLog = __lastLog;
194 |
195 | expect(__logs.length).to.eq(1);
196 | expect(lastLog.get('error')).to.eq(err);
197 | expect(err.message).to.include(
198 | '`cy.then()` failed because you are mixing up async and sync code.'
199 | );
200 | done();
201 | });
202 |
203 | cy.then(() => {
204 | cy.wait(5000);
205 | return 'foo';
206 | });
207 | });
208 |
209 | it('unbinds command:enqueued in the case of an error thrown', function (done) {
210 | const listeners = [];
211 |
212 | cy.on('fail', () => {
213 | listeners.push(cy.listeners('command:enqueued').length);
214 |
215 | expect(__logs.length).to.eq(1);
216 | expect(listeners).to.deep.eq([1, 0]);
217 | done();
218 | });
219 |
220 | cy.then(() => {
221 | listeners.push(cy.listeners('command:enqueued').length);
222 |
223 | throw new Error('foo');
224 | });
225 | });
226 | });
227 |
228 | describe('yields to remote jQuery subject', function () {
229 | let __remoteWindow;
230 | beforeEach(function () {
231 | __remoteWindow = cy.state('window');
232 | });
233 |
234 | it('calls the callback function with the remote jQuery subject', function () {
235 | __remoteWindow.$.fn.foo = () => {};
236 |
237 | cy.get('div:first')
238 | .then(($div) => {
239 | expect($div).to.be.instanceof(__remoteWindow.$);
240 | return $div;
241 | })
242 | .then(($div) => {
243 | expect($div).to.be.instanceof(__remoteWindow.$);
244 | });
245 | });
246 |
247 | it('does not store the remote jQuery object as the subject', function () {
248 | cy.get('div:first')
249 | .then(($div) => {
250 | expect($div).to.be.instanceof(__remoteWindow.$);
251 | return $div;
252 | })
253 | .then(() => {
254 | expect(cy.state('subject')).to.not.be.instanceof(__remoteWindow.$);
255 | });
256 | });
257 | });
258 | });
259 |
260 | describe('Retryability', function () {
261 | before(function () {
262 | Cypress.config('defaultCommandTimeout', 1000);
263 | });
264 |
265 | beforeEach(function () {
266 | const doc = cy.state('document');
267 | $(doc.body).empty().html(body);
268 | });
269 |
270 | it('retries until the upcoming assertion passes', function () {
271 | let c = 0;
272 |
273 | cy.then(() => ++c, { retry: true }).should('equal', 5);
274 | });
275 |
276 | it('retries correctly when handling dom elements', function () {
277 | let initalResult = null;
278 |
279 | cy.get('ul#list')
280 | .then(
281 | (list) => {
282 | const result = list.children().length;
283 | if (initalResult === null) {
284 | initalResult = result;
285 | }
286 | return result;
287 | },
288 | { retry: true }
289 | )
290 | .should('equal', 3)
291 | .then((result) => {
292 | expect(initalResult).to.below(result);
293 | });
294 | });
295 |
296 | describe('errors', function () {
297 | let __logs;
298 | let __lastLog;
299 |
300 | beforeEach(function () {
301 | Cypress.config('defaultCommandTimeout', 50);
302 | __logs = [];
303 |
304 | cy.on('log:added', (_, log) => {
305 | __lastLog = log;
306 | __logs.push(log);
307 | });
308 | });
309 |
310 | it('throws on timeout', function (done) {
311 | cy.on('fail', (err) => {
312 | const lastLog = __lastLog;
313 |
314 | expect(__logs.length).to.eq(2);
315 | expect(err.message).to.contain(lastLog.get('error').message);
316 | expect(err.message).to.equal(
317 | 'Timed out retrying after 50ms: expected 5 to equal 4'
318 | );
319 | done();
320 | });
321 |
322 | cy.then({ retry: true }, () => 5).should('equal', 4);
323 | });
324 |
325 | it('logs and fails on a thrown error', function (done) {
326 | cy.on('fail', (err) => {
327 | const lastLog = __lastLog;
328 |
329 | expect(__logs.length).to.eq(2);
330 | expect(err.message).to.contain(lastLog.get('error').message);
331 | expect(err.message).to.equal('foo');
332 | done();
333 | });
334 |
335 | cy.then({ retry: true }, () => {
336 | throw new Error('foo');
337 | }).should('equal', 4);
338 | });
339 | });
340 | });
341 |
342 | describe('Callback context', function () {
343 | it('passes aliasses to the callback function (fat-arrow notation)', function () {
344 | const outerThis = this;
345 |
346 | cy.wrap('fooBar').as('someAlias');
347 |
348 | cy.then(() => {
349 | expect(this.someAlias).to.equal('fooBar');
350 | expect(this).to.equal(outerThis);
351 | });
352 | });
353 |
354 | it('passes aliasses to the callback function (function notation)', function () {
355 | const outerThis = this;
356 |
357 | cy.wrap('fooBar').as('someAlias');
358 |
359 | cy.then(function () {
360 | expect(this.someAlias).to.equal('fooBar');
361 | expect(this).to.equal(outerThis);
362 | });
363 | });
364 | });
365 | });
366 |
--------------------------------------------------------------------------------
/cypress/e2e/to.cy.js:
--------------------------------------------------------------------------------
1 | const COMMAND_TIMEOUT = 4000;
2 | const COMMAND_TIMEOUT_FAST = 100;
3 |
4 | describe('The added command `to`', function () {
5 | before(function () {
6 | cy.visit('/');
7 | });
8 |
9 | beforeEach(function () {
10 | Cypress.config('defaultCommandTimeout', COMMAND_TIMEOUT);
11 | });
12 |
13 | context('Input validation', function () {
14 | let __logs;
15 |
16 | beforeEach(function () {
17 | __logs = [];
18 |
19 | cy.on('log:added', (_, log) => {
20 | __logs.push(log);
21 | });
22 | });
23 |
24 | it('throws when the subject is undefined', function (done) {
25 | cy.on('fail', (err) => {
26 | expect(__logs.length).to.eq(2);
27 | expect(err.message).to.include("Can't cast subject of type undefined");
28 | done();
29 | });
30 |
31 | cy.wrap(undefined).to('string');
32 | });
33 |
34 | it('throws when the subject is null', function (done) {
35 | cy.on('fail', (err) => {
36 | expect(__logs.length).to.eq(2);
37 | expect(err.message).to.include("Can't cast subject of type null");
38 | done();
39 | });
40 |
41 | cy.wrap(null).to('string');
42 | });
43 |
44 | it('throws when the subject is NaN', function (done) {
45 | cy.on('fail', (err) => {
46 | expect(__logs.length).to.eq(2);
47 | expect(err.message).to.include("Can't cast subject of type NaN");
48 | done();
49 | });
50 |
51 | cy.wrap(NaN).to('string');
52 | });
53 |
54 | it('throws when a faulty value is provided for the option `log`', function (done) {
55 | cy.on('fail', (err) => {
56 | expect(__logs.length).to.eq(2);
57 | expect(err.message).to.include(
58 | 'Bad value for the option "log" of the command "to".'
59 | );
60 | done();
61 | });
62 |
63 | cy.wrap(123).to('string', { log: 'foo' });
64 | });
65 |
66 | it('throws when a faulty type is provided', function (done) {
67 | cy.on('fail', (err) => {
68 | expect(__logs.length).to.eq(2);
69 | expect(err.message).to.include("Can't cast subject to type badType.");
70 | done();
71 | });
72 |
73 | cy.wrap(123).to('badType');
74 | });
75 |
76 | it('requires types to be case sensitive', function (done) {
77 | cy.on('fail', (err) => {
78 | expect(__logs.length).to.eq(2);
79 | expect(err.message).to.include("Can't cast subject to type STRing.");
80 | done();
81 | });
82 |
83 | cy.wrap(123).to('STRing');
84 | });
85 | });
86 |
87 | context('String', function () {
88 | describe('In isolation', function () {
89 | /* eslint-disable-next-line sonarjs/no-unused-collection */
90 | let __logs;
91 |
92 | beforeEach(function () {
93 | Cypress.config('defaultCommandTimeout', COMMAND_TIMEOUT_FAST);
94 | __logs = [];
95 |
96 | cy.on('log:added', (_, log) => {
97 | __logs.push(log);
98 | });
99 | });
100 |
101 | it('casts a number', function () {
102 | cy.wrap(123456).to('string').should('equal', '123456');
103 | });
104 |
105 | it('passes a string unmodified', function () {
106 | cy.wrap('foo bar baz').to('string').should('equal', 'foo bar baz');
107 | });
108 |
109 | it('casts all items in an array', function () {
110 | cy.wrap([132, 7, { foo: 'bar' }])
111 | .to('string')
112 | .should('deep.equal', ['132', '7', '{"foo":"bar"}']);
113 | });
114 |
115 | it('casts an object to JSON', function () {
116 | cy.wrap({ foo: 'bar', baz: true })
117 | .to('string')
118 | .should('equal', '{"foo":"bar","baz":true}');
119 | });
120 | });
121 | });
122 |
123 | context('Number', function () {
124 | describe('In isolation', function () {
125 | let __logs;
126 |
127 | beforeEach(function () {
128 | Cypress.config('defaultCommandTimeout', COMMAND_TIMEOUT_FAST);
129 | __logs = [];
130 |
131 | cy.on('log:added', (_, log) => {
132 | __logs.push(log);
133 | });
134 | });
135 |
136 | it('casts a numberlike string', function () {
137 | cy.wrap('007').to('number').should('equal', 7);
138 | });
139 |
140 | it('throws on a non-numberlike string', function (done) {
141 | cy.on('fail', (err) => {
142 | expect(__logs.length).to.eq(2);
143 | expect(err.message).to.include("Can't cast 'Five' to type number");
144 | done();
145 | });
146 |
147 | cy.wrap('Five').to('number');
148 | });
149 |
150 | it('throws on a non-array object', function (done) {
151 | cy.on('fail', (err) => {
152 | expect(__logs.length).to.eq(2);
153 | expect(err.message).to.include(
154 | "Can't cast subject of type object to type number"
155 | );
156 | done();
157 | });
158 |
159 | cy.wrap({ number: '123' }).to('number');
160 | });
161 |
162 | it('passes a subject of type number without modification', function () {
163 | cy.wrap(7).to('number').should('equal', 7);
164 | });
165 |
166 | it('casts all items in an array of numberlike strings', function () {
167 | cy.wrap(['1234', '009', '564864'])
168 | .to('number')
169 | .should('deep.equal', [1234, 9, 564864]);
170 | });
171 |
172 | it('throws on an array containing a non-numberlike string', function (done) {
173 | cy.on('fail', (err) => {
174 | expect(__logs.length).to.eq(2);
175 | expect(err.message).to.include("Can't cast 'foo' to type number");
176 | done();
177 | });
178 |
179 | cy.wrap(['foo', '1234', '009', 'a125']).to('number');
180 | });
181 | });
182 | });
183 |
184 | context('Array', function () {
185 | describe('In isolation', function () {
186 | it('does not cast an Array', function () {
187 | cy.wrap(['lorum', 'ipsum']).to('array').should('deep.equal', ['lorum', 'ipsum']);
188 | });
189 |
190 | it('casts a string', function () {
191 | cy.wrap('foo').to('array').should('deep.equal', ['foo']);
192 | });
193 |
194 | it('casts a number', function () {
195 | cy.wrap(123).to('array').should('deep.equal', [123]);
196 | });
197 |
198 | it('casts an object', function () {
199 | cy.wrap({ foo: 123 })
200 | .to('array')
201 | .should('deep.equal', [{ foo: 123 }]);
202 | });
203 | });
204 |
205 | describe('When interacting with page', function () {
206 | beforeEach(function () {
207 | cy.get('#list > *').as('list').should('have.length', 5);
208 | });
209 |
210 | it('does not cast a single element', function () {
211 | // Elements are jQuery objects, which are iterable.
212 | cy.get('@list')
213 | .first()
214 | .to('array')
215 | .should((result) => {
216 | expect(Array.isArray(result)).to.be.false;
217 | expect(result.length).to.equal(1);
218 | })
219 | .each((val) => {
220 | expect(val.jquery).to.not.be.undefined;
221 | });
222 | });
223 |
224 | it('does not cast multiple elements', function () {
225 | // Elements are jQuery objects, which are iterable.
226 | cy.get('@list')
227 | .to('array')
228 | .should((result) => {
229 | expect(Array.isArray(result)).to.be.false;
230 | expect(result.length).to.equal(5);
231 | })
232 | .each((val) => {
233 | expect(val.jquery).to.not.be.undefined;
234 | });
235 | });
236 |
237 | it('does cast the text result of a single element', function () {
238 | cy.get('@list')
239 | .first()
240 | .text()
241 | .to('array')
242 | .should((result) => {
243 | expect(Array.isArray(result)).to.be.true;
244 | expect(result.length).to.equal(1);
245 | })
246 | .each((val) => {
247 | expect(typeof val).to.equal('string');
248 | });
249 | });
250 |
251 | it('does not cast the text result of multiple elements', function () {
252 | cy.get('@list')
253 | .text()
254 | .to('array')
255 | .should((result) => {
256 | expect(Array.isArray(result)).to.be.true;
257 | expect(result.length).to.equal(5);
258 | })
259 | .each((val) => {
260 | expect(typeof val).to.equal('string');
261 | });
262 | });
263 | });
264 | });
265 | });
266 |
--------------------------------------------------------------------------------
/cypress/e2e/utils/whitespace.cy.js:
--------------------------------------------------------------------------------
1 | import whitespace from '../../../src/utils/whitespace';
2 | const _ = Cypress._;
3 |
4 | describe('Whitespace options for commands yielding strings', function () {
5 | it('returns a function', function () {
6 | expect(_.isFunction(whitespace('mode'))).to.be.true;
7 | });
8 |
9 | context('mode = `simplify`', function () {
10 | beforeEach(function () {
11 | this.ws = whitespace('simplify');
12 | });
13 |
14 | it('simplifies whitespace in the middle of the string', function () {
15 | expect(this.ws('Lorum ipsum\n\xa0dolor\tsit \r \n\tamet')).to.equal(
16 | 'Lorum ipsum dolor sit amet'
17 | );
18 | });
19 |
20 | it('removes whitespace at the ends of the string', function () {
21 | expect(this.ws(' Lorum ipsum dolor sit amet\n')).to.equal('Lorum ipsum dolor sit amet');
22 |
23 | expect(this.ws('\tLorum ipsum dolor sit amet\r')).to.equal(
24 | 'Lorum ipsum dolor sit amet'
25 | );
26 |
27 | expect(this.ws('\t \r \n\xa0 Lorum ipsum dolor sit amet')).to.equal(
28 | 'Lorum ipsum dolor sit amet'
29 | );
30 | });
31 |
32 | it('removes zero-width whitespace', function () {
33 | expect(this.ws('Lorum\u200Bipsum dol\uFEFFor sit amet')).to.equal(
34 | 'Lorumipsum dolor sit amet'
35 | );
36 |
37 | expect(this.ws('Lorum\u200Cips\uFEFFum dolor sit amet')).to.equal(
38 | 'Lorumipsum dolor sit amet'
39 | );
40 |
41 | expect(this.ws('Lorum\u200Dipsum dolor sit am\uFEFFet')).to.equal(
42 | 'Lorumipsum dolor sit amet'
43 | );
44 | });
45 | });
46 |
47 | context('mode = `keep-newline`', function () {
48 | beforeEach(function () {
49 | this.ws = whitespace('keep-newline');
50 | });
51 |
52 | it('simplifies non-newline whitespace in the middle of the string', function () {
53 | expect(this.ws('Lorum ipsum dolor\tsit \r \tamet')).to.equal(
54 | 'Lorum ipsum dolor sit amet'
55 | );
56 | });
57 |
58 | it('keeps newline characters in the middle of a string', function () {
59 | expect(this.ws('Lorum \n ipsum\xa0 dolor\tsit \r \n\tamet')).to.equal(
60 | 'Lorum\nipsum dolor sit\namet'
61 | );
62 | });
63 |
64 | it('removes non-newline whitespace at the ends of the string', function () {
65 | expect(this.ws(' Lorum ipsum dolor sit amet')).to.equal('Lorum ipsum dolor sit amet');
66 |
67 | expect(this.ws('\tLorum ipsum dolor sit amet\r')).to.equal(
68 | 'Lorum ipsum dolor sit amet'
69 | );
70 |
71 | expect(this.ws('\t\xa0 \r Lorum ipsum dolor sit amet')).to.equal(
72 | 'Lorum ipsum dolor sit amet'
73 | );
74 | });
75 |
76 | it('keeps newline characters at the ends of a string', function () {
77 | expect(this.ws(' \n Lorum ipsum dolor sit amet\t\n')).to.equal(
78 | '\nLorum ipsum dolor sit amet\n'
79 | );
80 |
81 | expect(this.ws('\nLorum ipsum dolor sit amet')).to.equal(
82 | '\nLorum ipsum dolor sit amet'
83 | );
84 | });
85 |
86 | it('removes zero-width whitespace', function () {
87 | expect(this.ws('Lorum\u200Bipsum dol\uFEFFor sit amet')).to.equal(
88 | 'Lorumipsum dolor sit amet'
89 | );
90 |
91 | expect(this.ws('Lorum\u200Cips\uFEFFum dolor sit amet')).to.equal(
92 | 'Lorumipsum dolor sit amet'
93 | );
94 |
95 | expect(this.ws('Lorum\u200Dipsum dolor sit am\uFEFFet')).to.equal(
96 | 'Lorumipsum dolor sit amet'
97 | );
98 | });
99 | });
100 |
101 | context('mode = `keep`', function () {
102 | beforeEach(function () {
103 | this.ws = whitespace('keep');
104 | });
105 |
106 | it('does not change the string at all', function () {
107 | const string = 'Lorum \t\r\xa0 ipsum dolor \n\nsit amet\n\r';
108 |
109 | expect(this.ws(string)).to.equal(string);
110 | });
111 | });
112 | });
113 |
--------------------------------------------------------------------------------
/cypress/support/bundle.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | import './common';
17 | import '../../';
18 |
--------------------------------------------------------------------------------
/cypress/support/common.js:
--------------------------------------------------------------------------------
1 | import chaiString from 'chai-string';
2 | chai.use(chaiString);
3 |
--------------------------------------------------------------------------------
/cypress/support/source.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | import './common';
17 | import '../../src';
18 |
--------------------------------------------------------------------------------
/docs/attribute.md:
--------------------------------------------------------------------------------
1 | # attribute
2 |
3 | This is a command that does not exist as a default command.
4 |
5 | ---
6 |
7 | Enables you to get the value of an elements attributes.
8 |
9 | > **Note:** When using `.attribute()` you should be aware about how Cypress
10 | > [only retries the last command](https://docs.cypress.io/guides/core-concepts/retry-ability#Only-the-last-command-is-retried).
11 |
12 | ## Syntax
13 |
14 | ```javascript
15 | .attribute(attribute)
16 | .attribute(attribute, options)
17 | ```
18 |
19 | ## Usage
20 |
21 | ### :heavy_check_mark: Correct Usage
22 |
23 | ```javascript
24 | cy.get('a').attribute('href'); // Yields the value of the `href` attribute
25 | ```
26 |
27 | ### :x: Incorrect Usage
28 |
29 | ```javascript
30 | cy.attribute('foo'); // Errors, cannot be chained off 'cy'
31 | cy.location().attribute('foo'); // Errors, 'location' does not yield DOM element
32 | ```
33 |
34 | ## Arguments
35 |
36 | **> attribute** **_(String)_**
37 |
38 | The name of the attribute to be yielded by `.attribute()`
39 |
40 | **> options** **_(Object)_**
41 |
42 | Pass in an options object to change the default behavior of `.attribute()`.
43 |
44 | | Option | Default | Description |
45 | | ------------ | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
46 | | `timeout` | [`defaultCommandTimeout`](https://docs.cypress.io/guides/references/configuration.html#Timeouts) | Time to wait for `.attribute()` to resolve before [timing out](https://docs.cypress.io/api/commands/then.html#Timeouts) |
47 | | `log` | `false` | Displays the command in the [Command log](https://docs.cypress.io/guides/core-concepts/test-runner.html#Command-Log) |
48 | | `whitespace` | `keep` | Replace complex whitespace with a single regular space. Accepted values: `simplify`, `keep-newline` & `keep` |
49 | | `strict` | `true` | Implicitly assert that all subjects have the requested attribute |
50 |
51 | ## Yields
52 |
53 | - `.attribute()` yields the value of a subjects given attribute.
54 | - `.attribute()` yields an array of the values of multiple subjects given attribute.
55 |
56 | ## Examples
57 |
58 | ### An alt attribute
59 |
60 |
61 | ```html
62 |
63 | ```
64 |
65 | ```javascript
66 | // yields "Teriffic Tiger"
67 | cy.get('img').attribute('alt');
68 | ```
69 |
70 | ### Multiple subjects
71 |
72 |
73 | ```html
74 |
75 |
76 | ```
77 |
78 | ```javascript
79 | // yields [
80 | // "text",
81 | // "submit"
82 | // ]
83 | cy.get('input').attribute('type');
84 | ```
85 |
86 | ### Whitespace handling
87 |
88 | By default all whitespace will be kept intact.
89 |
90 |
91 | ```html
92 |
94 | ```
95 |
96 | #### Do not simplify whitespace (default)
97 |
98 | ```javascript
99 | // yields " Extravagant \n Eagle "
100 | cy.get('div').attribute('data-attribute');
101 | ```
102 |
103 | The default value of `whitespace` is `keep` so the following yields the same.
104 |
105 | ```javascript
106 | // yields " Extravagant \n Eagle "
107 | cy.get('div').attribute('data-attribute', { whitespace: 'keep' });
108 | ```
109 |
110 | #### Simplify whitespace
111 |
112 | ```javascript
113 | // yields "Extravagant Eagle"
114 | cy.get('div').attribute('data-attribute', { whitespace: 'simplify' });
115 | ```
116 |
117 | #### Simplify whitespace but keep new line characters
118 |
119 | ```javascript
120 | // yields "Extravagant\nEagle"
121 | cy.get('div').attribute('data-attribute', { whitespace: 'keep-newline' });
122 | ```
123 |
124 | ### Strict mode
125 |
126 | Strict mode comes into play when using `.attribute()` with multiple subjects. By default strict mode
127 | is enabled.
128 |
129 |
130 | ```html
131 | Amazing armadillo
132 | Everlasting eel
133 | ```
134 |
135 | #### Strict mode `true` (default)
136 |
137 | Throws an error, because some subjects don't have the `target` attribute.
138 |
139 | ```javascript
140 | // Throws error: Expected all 2 elements to have attribute 'target', but never found it on 1 elements.
141 | cy.get('a').attribute('target');
142 | ```
143 |
144 | Yields two values because both subjects have the `href` attribute.
145 |
146 | ```javascript
147 | // yields [
148 | // "#armadillo",
149 | // "#eel"
150 | // ]
151 | cy.get('a').attribute('href');
152 | ```
153 |
154 | #### Strict mode `false`
155 |
156 | Does not throw an error because it's possible to yield a value, even though not all subjects have a
157 | `target` attribute. Any subject that does not have the `target` attribute is ignored.
158 |
159 | ```javascript
160 | // yields "_blank"
161 | cy.get('a').attribute('target', { strict: false });
162 | ```
163 |
164 | ## Notes
165 |
166 | ### Empty attributes
167 |
168 | `.attribute()` considers an empty attribute like below as existing, but empty.
169 |
170 |
171 | ```html
172 |
Catastrophic Cat
173 | ```
174 |
175 | ```javascript
176 | cy.get('p').attribute('hidden').should('exist').should('be.empty');
177 | ```
178 |
179 | ## Rules
180 |
181 | ### Requirements
182 |
183 | - `.attribute()` requires being chained off a command that yields DOM element(s).
184 |
185 | ### Assertions
186 |
187 | - `.attribute()` will automatically retry until the attribute exist on the subject(s).
188 | - `.attribute()` will automatically retry itself until assertions you've chained all pass.
189 |
190 | ### Timeouts
191 |
192 | - `.attribute()` can time out waiting for a chained assertion to pass.
193 |
194 | ## Command Log
195 |
196 | `.attribute()` will output to the command log.
197 |
--------------------------------------------------------------------------------
/docs/request.md:
--------------------------------------------------------------------------------
1 | # request
2 |
3 | This command has been extended with:
4 |
5 | - `.request()` uses the global configuration `requestBaseUrl` over `baseUrl`. This allows you to set
6 | a base url for `.request()` that is ignored by `.visit()`. [See arguments](#arguments)
7 |
8 | > Note:
9 | > [It's not possible to use `requestBaseUrl` in `cypress.config.ts` due to a limitation](../README.md#cypressconfigts-limitation).
10 |
11 | See [original documentation](https://docs.cypress.io/api/commands/request)
12 |
13 | ---
14 |
15 | Make an HTTP request.
16 |
17 | ## Syntax
18 |
19 | ```javascript
20 | cy.request(url);
21 | cy.request(url, body);
22 | cy.request(method, url);
23 | cy.request(method, url, body);
24 | cy.request(options);
25 | ```
26 |
27 | ## Usage
28 |
29 | ### :heavy_check_mark: Correct Usage
30 |
31 | ```javascript
32 | cy.request('http://dev.local/seed');
33 | ```
34 |
35 | ## Arguments
36 |
37 | **> url** **_(string)_**
38 |
39 | The URL to make the request to.
40 |
41 | If you provide a non fully qualified domain name (FQDN), Cypress will make its best guess as to
42 | which host you want `cy.request()` to use in the URL.
43 |
44 | 1. If you make a `cy.request()` after visiting a page, Cypress assumes the url used for the
45 | `cy.visit()` is the host.
46 |
47 | ```javascript
48 | cy.visit('http://localhost:8080/app');
49 | cy.request('users/1.json'); // url is http://localhost:8080/users/1.json
50 | ```
51 |
52 | 2. If you make a `cy.request()` prior to visiting a page, Cypress uses the host configured as the
53 | `requestBaseUrl` property inside of your
54 | [configuration file](https://docs.cypress.io/guides/references/configuration).
55 |
56 | ```javascript
57 | // cypress.config.js
58 | const { defineConfig } = require('cypress');
59 |
60 | module.exports = defineConfig({
61 | e2e: {
62 | requestBaseUrl: 'http://localhost:1234',
63 | },
64 | });
65 | ```
66 |
67 | ```javascript
68 | cy.request('seed/admin'); // url is http://localhost:1234/seed/admin
69 | ```
70 |
71 | If the `requestBaseUrl` is empty Cypress will use `baseUrl` instead.
72 |
73 | ```javascript
74 | // cypress.config.js
75 | const { defineConfig } = require('cypress');
76 |
77 | module.exports = defineConfig({
78 | e2e: {
79 | requestBaseUrl: '',
80 | baseUrl: 'http://localhost:1234',
81 | },
82 | });
83 | ```
84 |
85 | ```javascript
86 | cy.request('seed/admin'); // url is http://localhost:1234/seed/admin
87 | ```
88 |
89 | 3. If Cypress cannot determine the host it will throw an error.
90 |
91 | **> body** **_(String, Object)_**
92 |
93 | A request `body` to be sent in the request. Cypress sets the `Accepts` request header and serializes
94 | the response body by the encoding option.
95 |
96 | **> method** **_(String)_**
97 |
98 | Make a request using a specific method. If no method is defined, Cypress uses the `GET` method by
99 | default.
100 |
101 | Supported methods include:
102 |
103 | - `GET`
104 | - `POST`
105 | - `PUT`
106 | - `DELETE`
107 | - `PATCH`
108 | - `HEAD`
109 | - `OPTIONS`
110 | - `TRACE`
111 | - `COPY`
112 | - `LOCK`
113 | - `MKCOL`
114 | - `MOVE`
115 | - `PURGE`
116 | - `PROPFIND`
117 | - `PROPPATCH`
118 | - `UNLOCK`
119 | - `REPORT`
120 | - `MKACTIVITY`
121 | - `CHECKOUT`
122 | - `MERGE`
123 | - `M-SEARCH`
124 | - `NOTIFY`
125 | - `SUBSCRIBE`
126 | - `UNSUBSCRIBE`
127 | - `SEARCH`
128 | - `CONNECT`
129 |
130 | **> options** **_(Object)_**
131 |
132 | Pass in an options object to change the default behavior of `cy.request`.
133 |
134 | | Option | Default | Description |
135 | | -------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
136 | | `log` | `true` | Displays the command in the [Command log](https://docs.cypress.io/guides/core-concepts/test-runner.html#Command-Log) |
137 | | `url` | `null` | The URL to make the request to |
138 | | `method` | `GET` | The HTTP method to use in the request |
139 | | `auth` | `null` | Adds Authorization headers. ['Accepts these options.'](https://github.com/request/request#http-authentication) |
140 | | `body` | `null` | Body to send along with the request |
141 | | `failOnStatusCode` | `true` | Whether to fail on response codes other than `2xx` and `3xx` |
142 | | `followRedirect` | `true` | Whether to automatically follow redirects |
143 | | `form` | `false` | Whether to convert the `body` values to url encoded content and set the `x-www-form-urlencoded` header |
144 | | `encoding` | `utf8` | The encoding to be used when serializing the response body. The following encodings are supported: `ascii`, `base64`, `binary`, `hex`, `latin1`, `utf8`, `utf-8`, `ucs2`, `ucs-2`, `utf16le`, `utf-16le` |
145 | | `gzip` | `true` | Whether to accept the `gzip` encoding |
146 | | `headers` | `null` | Additional headers to send; Accepts object literal |
147 | | `qs` | `null` | Query parameters to append to the `url` of the request |
148 | | `retryOnStatusCodeFailure` | `false` | Whether Cypress should automatically retry status code errors under the hood. Cypress will retry a request up to 4 times if this is set to true. |
149 | | `retryOnNetworkFailure` | `true` | Whether Cypress should automatically retry transient network errors under the hood. Cypress will retry a request up to 4 times if this is set to true. |
150 | | `timeout` | [`responseTimeout`](https://docs.cypress.io/guides/references/configuration.html#Timeouts) | Time to wait for `cy.request()` to resolve before [timing out](https://docs.cypress.io/api/commands/request.html#Timeouts) |
151 |
152 | You can also set options for `cy.request`'s `requestBaseUrl`, `baseUrl` and `responseTimeout`
153 | globally in the [Cypress configuration](https://docs.cypress.io/guides/references/configuration).
154 |
155 | ## Yields
156 |
157 | `cy.request()` yields the `response` as an object literal containing properties such as:
158 |
159 | - `status`
160 | - `body`
161 | - `headers`
162 | - `duration`
163 |
164 | ## Examples
165 |
166 | ### URL
167 |
168 | #### Make a `GET` request
169 |
170 | `cy.request()` is great for talking to an external endpoint before your tests to seed a database.
171 |
172 | ```javascript
173 | beforeEach(function () {
174 | cy.request('http://localhost:8080/db/seed');
175 | });
176 | ```
177 |
178 | #### Issue an HTTP request
179 |
180 | Sometimes it's quicker to test the contents of a page rather than
181 | [`cy.visit()`](https://docs.cypress.io/api/commands/visit.html) and wait for the entire page and all
182 | of its resources to load.
183 |
184 | ```javascript
185 | cy.request('/admin').its('body').should('include', '
Admin
');
186 | ```
187 |
188 | ### Method and URL
189 |
190 | #### Send a `DELETE` request
191 |
192 | ```javascript
193 | cy.request('DELETE', 'http://localhost:8888/users/827');
194 | ```
195 |
196 | #### Alias the request using [`.as()`](https://docs.cypress.io/api/commands/as)
197 |
198 | ```javascript
199 | cy.request('https://jsonplaceholder.cypress.io/comments').as('comments');
200 |
201 | cy.get('@comments').should((response) => {
202 | expect(response.body).to.have.length(500);
203 | expect(response).to.have.property('headers');
204 | expect(response).to.have.property('duration');
205 | });
206 | ```
207 |
208 | ### Method, URL, and Body
209 |
210 | #### Send a `POST` request with a JSON body
211 |
212 | ```javascript
213 | cy.request('POST', 'http://localhost:8888/users/admin', { name: 'Jane' }).then((response) => {
214 | // response.body is automatically serialized into JSON
215 | expect(response.body).to.have.property('name', 'Jane'); // true
216 | });
217 | ```
218 |
219 | ### Options
220 |
221 | #### Request a page while disabling auto redirect
222 |
223 | To test the redirection behavior of a login without a session, `cy.request` can be used to check the
224 | `status` and `redirectedToUrl` property.
225 |
226 | The `redirectedToUrl` property is a special Cypress property that normalizes the URL the browser
227 | would normally follow during a redirect.
228 |
229 | ```javascript
230 | cy.request({
231 | url: '/dashboard',
232 | followRedirect: false, // turn off following redirects
233 | }).then((resp) => {
234 | // redirect status code is 302
235 | expect(resp.status).to.eq(302);
236 | expect(resp.redirectedToUrl).to.eq('http://localhost:8082/unauthorized');
237 | });
238 | ```
239 |
240 | #### Download a PDF file
241 |
242 | By passing the `encoding: binary` option, the `response.body` will be serialized binary content of
243 | the file. You can use this to access various file types via `.request()` like `.pdf`, `.zip`, or
244 | `.doc` files.
245 |
246 | ```javascript
247 | cy.request({
248 | url: 'http://localhost:8080/some-document.pdf',
249 | encoding: 'binary',
250 | }).then((response) => {
251 | cy.writeFile('path/to/save/document.pdf', response.body, 'binary');
252 | });
253 | ```
254 |
255 | #### Get Data URL of an image
256 |
257 | By passing the `encoding: base64` option, the `response.body` will be base64-encoded content of the
258 | image. You can use this to construct a
259 | [Data URI](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) for use
260 | elsewhere.
261 |
262 | ```javascript
263 | cy.request({
264 | url: 'https://docs.cypress.io/img/logo.png',
265 | encoding: 'base64',
266 | }).then((response) => {
267 | const base64Content = response.body;
268 | const mime = response.headers['content-type']; // or 'image/png'
269 | // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
270 | const imageDataUrl = `data:${mime};base64,${base64Content}`;
271 | });
272 | ```
273 |
274 | #### HTML form submissions using form option
275 |
276 | Oftentimes, once you have a proper e2e test around logging in, there's no reason to continue to
277 | `cy.visit()` the login and wait for the entire page to load all associated resources before running
278 | any other commands. Doing so can slow down our entire test suite.
279 |
280 | Using `cy.request()`, we can bypass all of this because it automatically gets and sets cookies as if
281 | the requests had come from the browser.
282 |
283 | ```javascript
284 | cy.request({
285 | method: 'POST',
286 | url: '/login_with_form', // baseUrl is prepend to URL
287 | form: true, // indicates the body should be form urlencoded and sets Content-Type: application/x-www-form-urlencoded headers
288 | body: {
289 | username: 'jane.lane',
290 | password: 'password123',
291 | },
292 | });
293 |
294 | // to prove we have a session
295 | cy.getCookie('cypress-session-cookie').should('exist');
296 | ```
297 |
298 | #### Using `cy.request()` for HTML Forms
299 |
300 | > [Check out our example recipe using cy.request() for HTML web forms](https://docs.cypress.io/examples/examples/recipes#Logging-In)
301 |
302 | ### Request Polling
303 |
304 | #### Call `cy.request()` over and over again
305 |
306 | This is useful when you're polling a server for a response that may take awhile to complete.
307 |
308 | All we're really doing here is creating a recursive function. Nothing more complicated than that.
309 |
310 | ```javascript
311 | // a regular ol' function folks
312 | function req () {
313 | cy
314 | .request(...)
315 | .then((resp) => {
316 | // if we got what we wanted
317 |
318 | if (resp.status === 200 && resp.body.ok === true)
319 | // break out of the recursive loop
320 | return
321 |
322 | // else recurse
323 | req()
324 | })
325 | }
326 |
327 | cy
328 | // do the thing causing the side effect
329 | .get('button').click()
330 |
331 | // now start the requests
332 | .then(req)
333 | ```
334 |
335 | ## Notes
336 |
337 | ### Debugging
338 |
339 | #### Request is not displayed in the Network Tab of Developer Tools
340 |
341 | Cypress does not _actually_ make an XHR request from the browser. We are actually making the HTTP
342 | request from the Cypress Test Runner (in Node). So, you won't see the request inside of your
343 | Developer Tools.
344 |
345 | ### Cors
346 |
347 | #### CORS is bypassed
348 |
349 | Normally when the browser detects a cross-origin HTTP request, it will send an `OPTIONS` preflight
350 | check to ensure the server allows cross-origin requests, but `cy.request()` bypasses CORS entirely.
351 |
352 | ```javascript
353 | // we can make requests to any external server, no problem.
354 | cy.request('https://www.google.com/webhp?#q=cypress.io+cors')
355 | .its('body')
356 | .should('include', 'Testing, the way it should be'); // true
357 | ```
358 |
359 | ### Cookies
360 |
361 | #### Cookies are automatically sent and received
362 |
363 | Before sending the HTTP request, we automatically attach cookies that would have otherwise been
364 | attached had the request come from the browser. Additionally, if a response has a `Set-Cookie`
365 | header, these are automatically set back on the browser cookies.
366 |
367 | In other words, `cy.request()` transparently performs all of the underlying functions as if it came
368 | from the browser.
369 |
370 | ### [`cy.intercept()`](https://docs.cypress.io/api/commands/intercept), [`cy.server()`](https://docs.cypress.io/api/commands/server), and [`cy.route()`](https://docs.cypress.io/api/commands/route)
371 |
372 | #### `cy.request()` sends requests to actual endpoints, bypassing those defined using `cy.route()` or `cy.intercept()`
373 |
374 | `cy.server()` and any configuration passed to
375 | [`cy.server()`](https://docs.cypress.io/api/commands/server) has no effect on `cy.request()`.
376 |
377 | The intention of `cy.request()` is to be used for checking endpoints on an actual, running server
378 | without having to start the front end application.
379 |
380 | ## Rules
381 |
382 | ### Requirements
383 |
384 | - `cy.request()` requires being chained off of `cy`.
385 | - `cy.request()` requires that the server send a response.
386 | - `cy.request()` requires that the response status code be `2xx` or `3xx` when `failOnStatusCode` is
387 | `true`.
388 |
389 | ### Assertions
390 |
391 | - `cy.request()` will only run assertions you've chained once, and will not retry.
392 |
393 | ### Timeouts
394 |
395 | - `cy.request()` can time out waiting for the server to respond.
396 |
397 | ## Command Log
398 |
399 | ### Request comments endpoint and test response
400 |
401 | ```javascript
402 | cy.request('https://jsonplaceholder.typicode.com/comments').then((response) => {
403 | expect(response.status).to.eq(200);
404 | expect(response.body).to.have.length(500);
405 | expect(response).to.have.property('headers');
406 | expect(response).to.have.property('duration');
407 | });
408 | ```
409 |
410 | The commands above will display in the Command Log as:
411 |
412 | 
413 |
414 | When clicking on `request` within the command log, the console outputs the following:
415 |
416 | 
417 |
418 | ## History
419 |
420 | | Version | Changes |
421 | | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
422 | | 4.7.0 | Added support for `encoding` option. |
423 | | 3.3.0 | Added support for options `retryOnStatusCodeFailure` and `retryOnNetworkFailure`. |
424 | | 3.2.0 | Added support for any valid HTTP `method` argument including `TRACE`, `COPY`, `LOCK`, `MKCOL`, `MOVE`, `PURGE`, `PROPFIND`, `PROPPATCH`, `UNLOCK`, `REPORT`, `MKACTIVITY`, `CHECKOUT`, `MERGE`, `M-SEARCH`, `NOTIFY`, `SUBSCRIBE`, `UNSUBSCRIBE`, `SEARCH`, and `CONNECT`. |
425 |
426 | ## See also
427 |
428 | - [`cy.exec()`](https://docs.cypress.io/api/commands/exec)
429 | - [`cy.task()`](https://docs.cypress.io/api/commands/task)
430 | - [`cy.visit()`](https://docs.cypress.io/api/commands/visit)
431 | - [Recipe: Logging In - Single Sign on](https://docs.cypress.io/examples/examples/recipes#Logging-In)
432 | - [Recipe: Logging In - HTML Web Form](https://docs.cypress.io/examples/examples/recipes#Logging-In)
433 | - [Recipe: Logging In - XHR Web Form](https://docs.cypress.io/examples/examples/recipes#Logging-In)
434 | - [Recipe: Logging In - CSRF Tokens](https://docs.cypress.io/examples/examples/recipes#Logging-In)
435 |
--------------------------------------------------------------------------------
/docs/text.md:
--------------------------------------------------------------------------------
1 | # text
2 |
3 | This is a command that does not exist as a default command.
4 |
5 | ---
6 |
7 | Enables you to get the text contents of the subject yielded from the previous command.
8 |
9 | `.text()` allows you to be more specific than you can be with `.contains()` or `.should('contain')`.
10 |
11 | > **Note:** When using `.text()` you should be aware about how Cypress
12 | > [only retries the last command](https://docs.cypress.io/guides/core-concepts/retry-ability#Only-the-last-command-is-retried).
13 |
14 | ## Syntax
15 |
16 | ```javascript
17 | .text()
18 | .text(options)
19 | ```
20 |
21 | ## Usage
22 |
23 | ### :heavy_check_mark: Correct Usage
24 |
25 | ```javascript
26 | cy.get('nav').text(); // Yields the text inside the `nav` element
27 | ```
28 |
29 | ### :x: Incorrect Usage
30 |
31 | ```javascript
32 | cy.text(); // Errors, cannot be chained off 'cy'
33 | cy.location().text(); // Errors, 'location' does not yield DOM element
34 | ```
35 |
36 | ## Arguments
37 |
38 | **> options** **_(Object)_**
39 |
40 | Pass in an options object to change the default behavior of `.text()`.
41 |
42 | | Option | Default | Description |
43 | | ------------ | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
44 | | `timeout` | [`defaultCommandTimeout`](https://docs.cypress.io/guides/references/configuration.html#Timeouts) | Time to wait for `.text()` to resolve before [timing out](https://docs.cypress.io/api/commands/then.html#Timeouts) |
45 | | `log` | `true` | Displays the command in the [Command log](https://docs.cypress.io/guides/core-concepts/test-runner.html#Command-Log) |
46 | | `whitespace` | `simplify` | Replace complex whitespace with a single regular space. Accepted values: `simplify`, `keep-newline` & `keep` |
47 | | `depth` | `0` | Include the text contents of child elements upto `n` levels |
48 |
49 | ## Yields
50 |
51 | - `.text()` yields the text inside the subject.
52 | - `.text()` yields an array of the texts inside multiple subjects.
53 |
54 | ## Examples
55 |
56 | ### No Args
57 |
58 | #### Get the text of a div
59 |
60 |
61 | ```html
62 |
Teriffic Tiger
63 | ```
64 |
65 | ```javascript
66 | // yields "Teriffic Tiger"
67 | cy.get('div').text();
68 | ```
69 |
70 | #### Get the text of multiple divs
71 |
72 |
73 | ```html
74 |
96 | ```
97 |
98 | #### Simplify whitespace by default
99 |
100 | ```javascript
101 | // yields "Extravagant Eagle"
102 | cy.get('div').text();
103 | ```
104 |
105 | The default value of `whitespace` is `simplify` so the following yields the same.
106 |
107 | ```javascript
108 | // yields "Extravagant Eagle"
109 | cy.get('div').text({ whitespace: 'simplify' });
110 | ```
111 |
112 | #### Simplify whitespace but keep newline characters
113 |
114 | ```javascript
115 | // yields "Extravagant\nEagle"
116 | cy.get('div').text({ whitespace: 'keep-newline' });
117 | ```
118 |
119 | #### Do not simplify whitespace
120 |
121 | ```javascript
122 | // yields "Extravagant \n Eagle"
123 | cy.get('div').text({ whitespace: 'keep' });
124 | ```
125 |
126 | Note that the whitespace at the beginning and end of the string is still removed.
127 |
128 | ### Depth of elements
129 |
130 | By default only the text of the subject itself will be yielded. Use this option to also get the text
131 | of underlying elements.
132 |
133 |
134 | ```html
135 |
136 | Grandma Gazelle
137 |
138 | Mother Meerkat
139 |
140 | Son Scorpion
141 |
142 |
143 |
144 | Father Fox
145 |
146 |
147 | ```
148 |
149 | #### Only the subject by default
150 |
151 | ```javascript
152 | // yields "Grandma Gazelle"
153 | cy.get('.grandparent').text();
154 | ```
155 |
156 | The default value of `depth` is `0` so the following yields the same.
157 |
158 | ```javascript
159 | // yields "Grandma Gazelle"
160 | cy.get('.grandparent').text({ depth: 0 });
161 | ```
162 |
163 | #### Include the direct children
164 |
165 | The text of the child elements are concatenated and yielded as a single string.
166 |
167 | ```javascript
168 | // yields "Grandma Gazelle Mother Meerkat Father Fox"
169 | cy.get('.grandparent').text({ depth: 1 });
170 | ```
171 |
172 | #### Multiple elements with depth
173 |
174 | Selecting multiple elements will yield an array of concatenated strings.
175 |
176 | ```javascript
177 | // yields [
178 | // "Mother Meerkat Son Scorpion",
179 | // "Father Fox"
180 | // ]
181 | cy.get('.parent').text({ depth: 1 });
182 | ```
183 |
184 | #### Remove all depth limitations
185 |
186 | To infinity and beyond!
187 |
188 | ```javascript
189 | // yields "Grandma Gazelle Mother Meerkat Son Scorpion Father Fox"
190 | cy.get('.grandparent').text({ depth: Infinity });
191 | ```
192 |
193 | ## Notes
194 |
195 | ### Form elements
196 |
197 | `.text()` also gets text from form elements like `input` and `textarea`.
198 |
199 | ```javascript
200 | cy.get('input').text();
201 | ```
202 |
203 | ## Rules
204 |
205 | ### Requirements
206 |
207 | - `.text()` requires being chained off a command that yields DOM element(s).
208 |
209 | ### Assertions
210 |
211 | - `.text()` will automatically retry itself until assertions you've chained all pass.
212 |
213 | ### Timeouts
214 |
215 | - `.text()` can time out waiting for a chained assertion to pass.
216 |
217 | ## Command Log
218 |
219 | `.text()` will output to the command log.
220 |
221 | ## See also
222 |
223 | - [`.contains()`](https://docs.cypress.io/api/commands/contains.html)
224 |
--------------------------------------------------------------------------------
/docs/then.md:
--------------------------------------------------------------------------------
1 | # then
2 |
3 | This command has been extended with:
4 |
5 | - The option `retry` which allows you to retry the function passed to `.then()` untill all
6 | assertions pass.
7 | - The option `log` which allows you to output `.then()` to the command log.
8 |
9 | See [original documentation](https://docs.cypress.io/api/commands/then)
10 |
11 | ---
12 |
13 | Enables you to work with the subject yielded from the previous command.
14 |
15 | > **Note:** `.then()` assumes you are already familiar with core concepts such as
16 | > [closures](https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Closures).
17 |
18 | > **Note:** Prefer
19 | > ['`.should()` with callback](https://docs.cypress.io/api/commands/should#Function) over `.then()`
20 | > for assertions as they are automatically rerun until no assertions throw within it but be aware of
21 | > [differences](https://docs.cypress.io/api/commands/should#Differences).
22 |
23 | ## Syntax
24 |
25 | ```javascript
26 | .then(callbackFn)
27 | .then(options, callbackFn)
28 | ```
29 |
30 | ## Usage
31 |
32 | ### :heavy_check_mark: Correct Usage
33 |
34 | ```javascript
35 | cy.get('.nav').then(($nav) => {}); // Yields .nav as first arg
36 | cy.location().then((loc) => {}); // Yields location object as first arg
37 | ```
38 |
39 | ## Arguments
40 |
41 | **> options** **_(Object)_**
42 |
43 | Pass in an options object to change the default behavior of `.then()`.
44 |
45 | | Option | Default | Description |
46 | | --------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
47 | | `timeout` | [`defaultCommandTimeout`](https://docs.cypress.io/guides/references/configuration.html#Timeouts) | Time to wait for `.then()` to resolve before [timing out](https://docs.cypress.io/api/commands/then.html#Timeouts) |
48 | | `retry` | `false` | Retry itself until chained assertions pass |
49 | | `log` | `false` | Displays the command in the [Command log](https://docs.cypress.io/guides/core-concepts/test-runner.html#Command-Log) |
50 |
51 | **> callbackFn** **_(Function)_**
52 |
53 | Pass a function that takes the previously yielded subject as its first argument.
54 |
55 | ## Yields
56 |
57 | `.then()` is modeled identically to the way Promises work in JavaScript. Whatever is returned from
58 | the callback function becomes the new subject and will flow into the next command (with the
59 | exception of `undefined`).
60 |
61 | Additionally, the result of the last Cypress command in the callback function will be yielded as the
62 | new subject and flow into the next command if there is no `return`.
63 |
64 | When `undefined` is returned by the callback function, the subject will not be modified and will
65 | instead carry over to the next command.
66 |
67 | Just like Promises, you can return any compatible "thenable" (anything that has a `.then()`
68 | interface) and Cypress will wait for that to resolve before continuing forward through the chain of
69 | commands.
70 |
71 | ## Examples
72 |
73 | > We have several more examples in our
74 | > [Core Concepts Guide](https://docs.cypress.io/guides/core-concepts/variables-and-aliases) which go
75 | > into the various ways you can use `.then()` to store, compare, and debug values.
76 |
77 | ### DOM element
78 |
79 | #### The `button` element is yielded
80 |
81 | ```javascript
82 | cy.get('button').then(($btn) => {
83 | const cls = $btn.attr('class');
84 |
85 | cy.wrap($btn).click().should('not.have.class', cls);
86 | });
87 | ```
88 |
89 | #### The number is yielded from previous command
90 |
91 | ```javascript
92 | cy.wrap(1)
93 | .then((num) => {
94 | cy.wrap(num).should('equal', 1); // true
95 | })
96 | .should('equal', 1); // true
97 | ```
98 |
99 | ### Change subject
100 |
101 | #### The el subject is changed with another command
102 |
103 | ```javascript
104 | cy.get('button')
105 | .then(($btn) => {
106 | const cls = $btn.attr('class');
107 |
108 | cy.wrap($btn).click().should('not.have.class', cls).find('i');
109 | // since there is no explicit return
110 | // the last Cypress command's yield is yielded
111 | })
112 | .should('have.class', 'spin'); // assert on i element
113 | ```
114 |
115 | #### The number subject is changed with another command
116 |
117 | ```javascript
118 | cy.wrap(1).then((num) => {
119 | cy.wrap(num)).should('equal', 1) // true
120 | cy.wrap(2)
121 | }).should('equal', 2) // true
122 | ```
123 |
124 | #### The number subject is changed by returning
125 |
126 | ```javascript
127 | cy.wrap(1)
128 | .then((num) => {
129 | cy.wrap(num).should('equal', 1); // true
130 |
131 | return 2;
132 | })
133 | .should('equal', 2); // true
134 | ```
135 |
136 | #### Returning `undefined` will not modify the yielded subject
137 |
138 | ```javascript
139 | cy.get('form')
140 | .then(($form) => {
141 | console.log('form is:', $form);
142 | // undefined is returned here, but $form will be
143 | // yielded to allow for continued chaining
144 | })
145 | .find('input')
146 | .then(($input) => {
147 | // we have our $input element here since
148 | // our form element was yielded and we called
149 | // .find('input') on it
150 | });
151 | ```
152 |
153 | ### Raw HTMLElements are wrapped with jQuery
154 |
155 | ```javascript
156 | cy.get('div')
157 | .then(($div) => {
158 | return $div[0]; // type => HTMLDivElement
159 | })
160 | .then(($div) => {
161 | $div; // type => JQuery
162 | });
163 | ```
164 |
165 | ### Promises
166 |
167 | Cypress waits for Promises to resolve before continuing
168 |
169 | #### Example using Q
170 |
171 | ```javascript
172 | cy.get('button')
173 | .click()
174 | .then(($button) => {
175 | const p = Q.defer();
176 |
177 | setTimeout(() => {
178 | p.resolve();
179 | }, 1000);
180 |
181 | return p.promise;
182 | });
183 | ```
184 |
185 | #### Example using bluebird
186 |
187 | ```javascript
188 | cy.get('button')
189 | .click()
190 | .then(($button) => {
191 | return Promise.delay(1000);
192 | });
193 | ```
194 |
195 | #### Example using jQuery deferred's
196 |
197 | ```javascript
198 | cy.get('button')
199 | .click()
200 | .then(($button) => {
201 | const df = $.Deferred();
202 |
203 | setTimeout(() => {
204 | df.resolve();
205 | }, 1000);
206 |
207 | return df;
208 | });
209 | ```
210 |
211 | ### Retryability
212 |
213 | The default Cypress command has been extended to allow you to retry a function until chained
214 | assertions pass. Use this sparsely. If you find yourself using this all the time you are probably
215 | doing it wrong. In most cases there are more suitable commands to get what you need.
216 |
217 | ```javascript
218 | cy.get('form')
219 | .then({ retry: true }, ($form) => {
220 | // We have acces to the jQuery object describing the form
221 | // here. We can now do some operations on it without using
222 | // Cypress commands, while we maintain retryability.
223 |
224 | // Note that this function might be executed multiple
225 | // times. Keep it light and make sure you know what
226 | // you're doing.
227 |
228 | return 'foo';
229 | })
230 | .should('equal', 'foo');
231 | ```
232 |
233 | ## Notes
234 |
235 | ### Differences
236 |
237 | #### What’s the difference between `.then()` and `.should()`/`.and()`?
238 |
239 | Using `.then()` allows you to use the yielded subject in a callback function and should be used when
240 | you need to manipulate some values or do some actions.
241 |
242 | When using a callback function with `.should()`, `.and()` or `.then({ retry: true })`, on the other
243 | hand, there is special logic to rerun the callback function until no assertions throw within it. You
244 | should be careful of side affects in a `.should()`, `.and()`, or `.then({ retry: true })` callback
245 | function that you would not want performed multiple times.
246 |
247 | ## Rules
248 |
249 | ### Requirements
250 |
251 | - `.then()` requires being chained off a previous command.
252 |
253 | ### Assertions
254 |
255 | - `.then()` will only run assertions you've chained once, and will not
256 | [retry](https://docs.cypress.io/guides/core-concepts/retry-ability) (unless you use the
257 | `retry: true` option).
258 |
259 | ### Timeouts
260 |
261 | - `.then()` can time out waiting for a promise you've returned to resolve.
262 | - `.then()` can time out waiting for a chained assertion to pass. (when using the `retry: true`
263 | option)
264 |
265 | ## Command Log
266 |
267 | - `.then()` only logs in the Command Log in the following situations:
268 | - the option `log` is set to `true`
269 | - the option `retry` is set to `true` and the option `log` is not set
270 |
271 | ## History
272 |
273 | | Version | Changes |
274 | | ------- | ----------------------- |
275 | | 0.14.0 | Added timeout option |
276 | | < 0.3.3 | `.then()` command added |
277 |
278 | ## See also
279 |
280 | - [`.and()`](https://docs.cypress.io/api/commands/and)
281 | - [`.each()`](https://docs.cypress.io/api/commands/each)
282 | - [`.invoke()`](https://docs.cypress.io/api/commands/invoke)
283 | - [`.its()`](https://docs.cypress.io/api/commands/its)
284 | - [`.should()`](https://docs.cypress.io/api/commands/should)
285 | - [`.spread()`](https://docs.cypress.io/api/commands/spread)
286 | - [Guide: Using Closures to compare values](https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Closures)
287 | - [Guide: Chains of Commands](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Chains-of-Commands)
288 |
--------------------------------------------------------------------------------
/docs/to.md:
--------------------------------------------------------------------------------
1 | # to
2 |
3 | This is a command that does not exist as a default command.
4 |
5 | ---
6 |
7 | Cast the subject to another type. Will do nothing if the subject is already of that type.
8 |
9 | > **Note:** When using `.to()` you should be aware about how Cypress
10 | > [only retries the last command](https://docs.cypress.io/guides/core-concepts/retry-ability#Only-the-last-command-is-retried).
11 |
12 | ## Syntax
13 |
14 | ```javascript
15 | .to(type)
16 | .to(type, options)
17 | ```
18 |
19 | ## Usage
20 |
21 | ### :heavy_check_mark: Correct Usage
22 |
23 | ```javascript
24 | cy.wrap('00123').to('number'); // Yields 123
25 | cy.wrap(42).to('string'); // Yields '42'
26 | cy.wrap({ passive: 'Parakeet' }).to('string'); // Yields '{"passive":"Parakeet"}'
27 | cy.wrap('Underwhelming Uakari').to('array'); // Yields ['Underwhelming Uakari']
28 | ```
29 |
30 | ### :x: Incorrect Usage
31 |
32 | ```javascript
33 | cy.to('string'); // Errors, cannot be chained off 'cy'
34 | cy.wrap('Dangerous dog').to('number'); // Errors, string can't be casted to number
35 | ```
36 |
37 | ## Arguments
38 |
39 | **> type** **_(string)_**
40 |
41 | The type you want to cast the subject to. Must be one of `number`, `string` or `array`.
42 |
43 | **> options** **_(Object)_**
44 |
45 | Pass in an options object to change the default behavior of `.to()`.
46 |
47 | | Option | Default | Description |
48 | | --------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
49 | | `timeout` | [`defaultCommandTimeout`](https://docs.cypress.io/guides/references/configuration.html#Timeouts) | Time to wait for `.to()` to resolve before [timing out](https://docs.cypress.io/api/commands/then.html#Timeouts) |
50 | | `log` | `true` | Displays the command in the [Command log](https://docs.cypress.io/guides/core-concepts/test-runner.html#Command-Log) |
51 |
52 | ## Yields
53 |
54 | - `.to('array')` yields an array
55 | - `.to('string')` yields a string or array of strings
56 | - `.to('number')` yields a number or array of numbers
57 |
58 | ## Examples
59 |
60 | ### Casting a string to a number
61 |
62 | ```javascript
63 | // yields 42
64 | cy.wrap('042').to('number');
65 | ```
66 |
67 | ### Casting a sring to an array
68 |
69 | ```javascript
70 | // yields ['042']
71 | cy.wrap('042').to('array');
72 | ```
73 |
74 | ### Casting an object to a string
75 |
76 | Uses `JSON.stringify`.
77 |
78 | ```javascript
79 | // yields '{"foo":"bar"}'
80 | cy.wrap({ foo: 'bar' }).to('string');
81 | ```
82 |
83 | ### Casting an array of numbers to an array of strings
84 |
85 | ```javascript
86 | // yields [ '123', '456', '789' ]
87 | cy.wrap([123, 456, 789]).to('string');
88 | ```
89 |
90 | ### Casting an array to an array
91 |
92 | When trying to cast to the type of the subject `.to()` will do nothing.
93 |
94 | ```javascript
95 | // yields [ 'foo' ]
96 | cy.wrap(['foo']).to('array');
97 | ```
98 |
99 | ## Notes
100 |
101 | ### Ensuring iterability
102 |
103 | Some commands, like `.text()`, yield a string when there is only a single subject, but an array when
104 | there are multiple subjects. You can use `.to('array')` to ensure you can loop over the results of
105 | `.text()` without the risk of an error.
106 |
107 | ```javascript
108 | cy.get('.maybeOneElement')
109 | .text()
110 | .to('array')
111 | .each((text) => {
112 | // ...
113 | });
114 | ```
115 |
116 | ## Rules
117 |
118 | ### Requirements
119 |
120 | - `.to()` requires being chained off a previous command.
121 |
122 | ### Assertions
123 |
124 | - `.to()` will automatically retry itself until the subject can be casted.
125 | - `.to()` will automatically retry itself until assertions you've chained all pass.
126 |
127 | ### Timeouts
128 |
129 | - `.to()` can time out waiting for a chained assertion to pass.
130 |
131 | ## Command Log
132 |
133 | `.to()` will output to the command log.
134 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cypress-commands",
3 | "version": "3.0.0",
4 | "description": "A collection of Cypress commands to extend and complement the default commands",
5 | "license": "MIT",
6 | "main": "dist/cypress-commands.js",
7 | "module": "dist/cypress-commands.mjs",
8 | "types": "dist/types/index.d.ts",
9 | "files": [
10 | "dist"
11 | ],
12 | "scripts": {
13 | "test": "npm run test:source",
14 | "test:source": "start-server-and-test start:server http://localhost:1337 run:cypress",
15 | "test:bundle": "start-server-and-test start:server http://localhost:1337 run:cypress:bundle",
16 | "run:cypress": "npm run run:cypress:source",
17 | "run:cypress:source": "cypress run --config supportFile=cypress/support/source.js",
18 | "run:cypress:bundle": "cypress run --config supportFile=cypress/support/bundle.js",
19 | "start": "npm run start:source",
20 | "start:server": "static-server app -p 1337",
21 | "start:source": "start-server-and-test start:server http://localhost:1337 start:cypress",
22 | "start:bundle": "start-server-and-test start:server http://localhost:1337 start:cypress:bundle",
23 | "start:cypress": "npm run start:cypress:source",
24 | "start:cypress:source": "cypress open --config supportFile=cypress/support/source.js",
25 | "start:cypress:bundle": "cypress open --config supportFile=cypress/support/bundle.js",
26 | "lint": "eslint ./",
27 | "bundle": "rollup -c",
28 | "prepublishOnly": "npm run lint && npm run bundle && npm run test:bundle"
29 | },
30 | "author": {
31 | "name": "Sander van Beek"
32 | },
33 | "repository": {
34 | "type": "git",
35 | "url": "https://github.com/Lakitna/cypress-commands"
36 | },
37 | "bugs": {
38 | "url": "https://github.com/Lakitna/cypress-commands/issues"
39 | },
40 | "keywords": [
41 | "Cypress",
42 | "command",
43 | "attribute",
44 | "text",
45 | "to"
46 | ],
47 | "devDependencies": {
48 | "chai-string": "^1.5.0",
49 | "cypress": "^11.0.1",
50 | "eslint": "^8.27.0",
51 | "eslint-config-google": "^0.14.0",
52 | "eslint-plugin-cypress": "^2.12.1",
53 | "eslint-plugin-import": "^2.26.0",
54 | "eslint-plugin-sonarjs": "^0.16.0",
55 | "request": "^2.88.2",
56 | "rollup": "^2.75.6",
57 | "rollup-plugin-commonjs": "^10.1.0",
58 | "rollup-plugin-copy": "^3.4.0",
59 | "rollup-plugin-delete": "^2.0.0",
60 | "rollup-plugin-json": "^4.0.0",
61 | "start-server-and-test": "^1.14.0",
62 | "static-server": "^2.2.1",
63 | "typescript": "^4.7.3"
64 | },
65 | "dependencies": {
66 | "path-browserify": "^1.0.1"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json';
2 | import del from 'rollup-plugin-delete';
3 | import commonjs from 'rollup-plugin-commonjs';
4 | import json from 'rollup-plugin-json';
5 | import copy from 'rollup-plugin-copy';
6 |
7 | const config = [
8 | {
9 | input: './src/index.js',
10 | output: [
11 | {
12 | file: pkg.main,
13 | format: 'cjs',
14 | globals: {
15 | Cypress: 'cypress',
16 | },
17 | },
18 | { file: pkg.module, format: 'es' },
19 | ],
20 | plugins: [
21 | // Delete contents of target folder
22 | del({
23 | targets: pkg.files,
24 | }),
25 |
26 | // Resolve JSON files
27 | json(),
28 |
29 | // Compile to commonjs and bundle
30 | commonjs(),
31 |
32 | // Copy type definitions to target folder
33 | copy({
34 | targets: [{ src: './types/**/*.d.ts', dest: './dist/types' }],
35 | }),
36 | ],
37 |
38 | /**
39 | * Mark all dependencies as external to prevent Rollup from
40 | * including them in the bundle. We'll let the package manager
41 | * take care of dependency resolution and stuff so we don't
42 | * have to download the exact same code multiple times, once
43 | * in this bundle and also as a dependency of another package.
44 | */
45 | external: [...Object.keys(pkg.dependencies || {})],
46 | },
47 | ];
48 |
49 | module.exports = config;
50 |
--------------------------------------------------------------------------------
/src/attribute.js:
--------------------------------------------------------------------------------
1 | const _ = Cypress._;
2 | const $ = Cypress.$;
3 |
4 | import { markCurrentCommand, upcomingAssertionNegatesExistence } from './utils/commandQueue';
5 | import { command } from './utils/errorMessages';
6 | const errMsg = command.attribute;
7 |
8 | import whitespace from './utils/whitespace';
9 | import OptionValidator from './utils/optionValidator';
10 | const validator = new OptionValidator('attribute');
11 |
12 | /**
13 | * Get the value of an attribute of the subject
14 | *
15 | * @example
16 | * cy.get('a').attribute('href');
17 | *
18 | * @param {Object} [options]
19 | * @param {boolean} [options.log=true]
20 | * Log the command to the Cypress command log
21 | *
22 | * @yields {string|string[]}
23 | * @since 0.2.0
24 | */
25 | Cypress.Commands.add(
26 | 'attribute',
27 | { prevSubject: 'element' },
28 | (subject, attribute, options = {}) => {
29 | subject = $(subject);
30 |
31 | // Make sure the order of input can be flipped
32 | if (_.isObject(attribute)) {
33 | [attribute, options] = [options, attribute];
34 | }
35 |
36 | // Handle options
37 | validator.check('log', options.log, [true, false]);
38 | validator.check('whitespace', options.whitespace, ['simplify', 'keep', 'keep-newline']);
39 | validator.check('strict', options.strict, [true, false]);
40 | _.defaults(options, {
41 | log: true,
42 | strict: true,
43 | whitespace: 'keep',
44 | });
45 |
46 | options._whitespace = whitespace(options.whitespace);
47 |
48 | const consoleProps = {
49 | 'Applied to': subject,
50 | };
51 | if (options.log) {
52 | options._log = Cypress.log({
53 | $el: subject,
54 | name: 'attribute',
55 | message: attribute,
56 | consoleProps: () => consoleProps,
57 | });
58 | }
59 |
60 | // Mark this newly invoked command in the command queue to be able to find it later.
61 | markCurrentCommand('attribute');
62 |
63 | /**
64 | * @param {Array.|string} result
65 | */
66 | function updateLog(result) {
67 | consoleProps.Yielded = result;
68 | }
69 |
70 | /**
71 | * Get the attribute and do the upcoming assertion
72 | * @return {Promise}
73 | */
74 | function resolveAttribute() {
75 | const attr = subject
76 | .map((i, element) => {
77 | return $(element).attr(attribute);
78 | })
79 | // Deconstruct jQuery object to normal array
80 | .toArray()
81 | .map(options._whitespace);
82 |
83 | let result = attr;
84 |
85 | if (options.strict && subject.length > result.length) {
86 | const negate = upcomingAssertionNegatesExistence();
87 | if (!negate) {
88 | // Empty result to we fail on missing attributes
89 | result = [];
90 | }
91 | }
92 |
93 | if (result.length === 0) {
94 | // Empty JQuery object is Cypress' way of saying that something does not exist
95 | result = $();
96 | } else if (result.length === 1) {
97 | // Only one result, so unwrap the array
98 | result = result[0];
99 | }
100 |
101 | if (options.log) {
102 | updateLog(result);
103 | }
104 |
105 | return cy.verifyUpcomingAssertions(result, options, {
106 | onFail: (err) => onFail(err, subject, attribute, attr),
107 | // Retry untill the upcoming assertion passes
108 | onRetry: resolveAttribute,
109 | });
110 | }
111 |
112 | return resolveAttribute().then((attribute) => {
113 | // The upcoming assertion passed, finish up the log
114 | if (options.log) {
115 | options._log.snapshot().end();
116 | }
117 | return attribute;
118 | });
119 | }
120 | );
121 |
122 | /**
123 | * Overwrite the error message of implicit assertions
124 | * @param {AssertionError} err
125 | * @param {jQuery} subject
126 | * @param {string} attribute
127 | * @param {string | string[]} result
128 | */
129 | function onFail(err, subject, attribute, result) {
130 | const negate = err.message.includes(' not ');
131 | result = _.isArray(result) ? result : [result];
132 |
133 | if (err.type === 'existence' && subject.length == 1) {
134 | const errorMessage = errMsg.existence.single;
135 |
136 | if (negate) {
137 | err.displayMessage = errorMessage.negated(attribute);
138 | } else {
139 | err.displayMessage = errorMessage.normal(attribute);
140 | }
141 | } else if (err.type === 'existence' && subject.length > 1) {
142 | const errorMessage = errMsg.existence.multiple;
143 |
144 | if (negate) {
145 | err.displayMessage = errorMessage.negated(attribute, subject.length, result.length);
146 | } else {
147 | err.displayMessage = errorMessage.normal(attribute, subject.length, result.length);
148 | }
149 |
150 | err.displayMessage += '\n\n' + errMsg.disable_strict;
151 | }
152 |
153 | err.message = err.displayMessage;
154 | }
155 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './attribute';
2 | import './then';
3 | import './text';
4 | import './to';
5 | import './request';
6 |
--------------------------------------------------------------------------------
/src/request.js:
--------------------------------------------------------------------------------
1 | import path from 'path-browserify';
2 | const _ = Cypress._;
3 |
4 | const methods = [
5 | 'GET',
6 | 'POST',
7 | 'PUT',
8 | 'DELETE',
9 | 'PATCH',
10 | 'HEAD',
11 | 'OPTIONS',
12 | 'TRACE',
13 | 'COPY',
14 | 'LOCK',
15 | 'MKCOL',
16 | 'MOVE',
17 | 'PURGE',
18 | 'PROPFIND',
19 | 'PROPPATCH',
20 | 'UNLOCK',
21 | 'REPORT',
22 | 'MKACTIVITY',
23 | 'CHECKOUT',
24 | 'MERGE',
25 | 'M-SEARCH',
26 | 'NOTIFY',
27 | 'SUBSCRIBE',
28 | 'UNSUBSCRIBE',
29 | 'SEARCH',
30 | 'CONNECT',
31 | ];
32 |
33 | /**
34 | * @yields {any}
35 | * @since 0.2.0
36 | */
37 | Cypress.Commands.overwrite('request', (originalCommand, ...args) => {
38 | const options = {};
39 |
40 | if (_.isObject(args[0])) {
41 | _.extend(options, args[0]);
42 | } else if (args.length === 1) {
43 | options.url = args[0];
44 | } else if (args.length === 2) {
45 | if (methods.includes(args[0].toUpperCase())) {
46 | options.method = args[0];
47 | options.url = args[1];
48 | } else {
49 | options.url = args[0];
50 | options.body = args[1];
51 | }
52 | } else if (args.length === 3) {
53 | options.method = args[0];
54 | options.url = args[1];
55 | options.body = args[2];
56 | }
57 |
58 | options.url = parseUrl(options.url);
59 |
60 | return originalCommand(options);
61 | });
62 |
63 | /**
64 | * @param {string} url
65 | * @return {string}
66 | */
67 | function parseUrl(url) {
68 | if (
69 | typeof url === 'string' &&
70 | !url.includes('://') &&
71 | !url.startsWith('localhost') &&
72 | !url.startsWith('www.')
73 | ) {
74 | // It's a relative url
75 | const config = Cypress.config();
76 | const requestBaseUrl = config.requestBaseUrl;
77 |
78 | if (_.isString(requestBaseUrl) && requestBaseUrl.length > 0) {
79 | const split = requestBaseUrl.split('://');
80 | const protocol = split[0] + '://';
81 | const baseUrl = split[1];
82 |
83 | url = protocol + path.join(baseUrl, url);
84 | }
85 | }
86 | return url;
87 | }
88 |
--------------------------------------------------------------------------------
/src/text.js:
--------------------------------------------------------------------------------
1 | const _ = Cypress._;
2 | const $ = Cypress.$;
3 |
4 | import whitespace from './utils/whitespace';
5 | import OptionValidator from './utils/optionValidator';
6 | const validator = new OptionValidator('text');
7 |
8 | /**
9 | * Get the text contents of the subject
10 | *
11 | * @example
12 | * cy.get('footer').text();
13 | *
14 | * @param {Object} [options]
15 | * @param {boolean} [options.log=true]
16 | * Log the command to the Cypress command log
17 | * @param {'simplify'|'keep-newline'|'keep'} [options.whitespace='simplify']
18 | * Replace complex whitespace (` `, `\t`, `\n`, multiple spaces and more
19 | * obscure whitespace characters) with a single regular space.
20 | * @param {number} [options.depth=0]
21 | * Include the text contents of child elements up to a depth of `n`
22 | *
23 | * @yields {string|string[]}
24 | * @since 0.1.0
25 | */
26 | Cypress.Commands.add('text', { prevSubject: 'element' }, (element, options = {}) => {
27 | validator.check('log', options.log, [true, false]);
28 | validator.check('whitespace', options.whitespace, ['simplify', 'keep', 'keep-newline']);
29 | validator.check('depth', options.depth, '>= 0');
30 |
31 | _.defaults(options, {
32 | log: true,
33 | whitespace: 'simplify',
34 | depth: 0,
35 | });
36 |
37 | options._whitespace = whitespace(options.whitespace);
38 |
39 | const consoleProps = {
40 | 'Applied to': $(element),
41 | 'Whitespace': options.whitespace,
42 | 'Depth': options.depth,
43 | };
44 | if (options.log) {
45 | options._log = Cypress.log({
46 | $el: $(element),
47 | name: 'text',
48 | message: '',
49 | consoleProps: () => consoleProps,
50 | });
51 | }
52 |
53 | /**
54 | * @param {Array.|string} result
55 | */
56 | function updateLog(result) {
57 | consoleProps.Yielded = result;
58 | if (_.isArray(result)) {
59 | options._log.set('message', JSON.stringify(result));
60 | } else {
61 | options._log.set('message', result);
62 | }
63 | }
64 |
65 | /**
66 | * Get the text and do the upcoming assertion
67 | * @return {Promise}
68 | */
69 | function resolveText() {
70 | let text = [];
71 | element.each((_, elem) => {
72 | text.push(getTextOfElement($(elem), options.depth).trim());
73 | });
74 |
75 | text = text.map(options._whitespace);
76 |
77 | if (text.length == 1) {
78 | text = text[0];
79 | }
80 |
81 | if (options.log) updateLog(text);
82 |
83 | return cy.verifyUpcomingAssertions(text, options, {
84 | // Retry untill the upcoming assertion passes
85 | onRetry: resolveText,
86 | });
87 | }
88 |
89 | return resolveText().then((text) => {
90 | // The upcoming assertion passed, finish up the log
91 | if (options.log) {
92 | options._log.snapshot().end();
93 | }
94 | return text;
95 | });
96 | });
97 |
98 | /**
99 | * @param {JQuery} element
100 | * @param {number} depth
101 | * @return {string}
102 | */
103 | function getTextOfElement(element, depth) {
104 | const TAG_REPLACEMENT = {
105 | WBR: '\u200B',
106 | BR: ' ',
107 | };
108 |
109 | let text = '';
110 | element.contents().each((i, content) => {
111 | if (content.nodeType === Node.TEXT_NODE) {
112 | return (text += content.data);
113 | }
114 | if (content.nodeType === Node.ELEMENT_NODE) {
115 | if (_.has(TAG_REPLACEMENT, content.nodeName)) {
116 | return (text += TAG_REPLACEMENT[content.nodeName]);
117 | }
118 |
119 | if (depth > 0) {
120 | return (text += getTextOfElement($(content), depth - 1));
121 | }
122 | }
123 | });
124 |
125 | return text;
126 | }
127 |
--------------------------------------------------------------------------------
/src/then.js:
--------------------------------------------------------------------------------
1 | const _ = Cypress._;
2 | const $ = Cypress.$;
3 |
4 | import isJquery from './utils/isJquery';
5 | import OptionValidator from './utils/optionValidator';
6 | const validator = new OptionValidator('then');
7 |
8 | /**
9 | * Enables you to work with the subject yielded from the previous command.
10 | *
11 | * @example
12 | * cy.then((subject) => {
13 | * // ...
14 | * });
15 | *
16 | * @param {function} fn
17 | * @param {Object} options
18 | * @param {boolean} [options.log=false]
19 | * Log to Cypress bar
20 | * @param {boolean} [options.retry=false]
21 | * Retry when an upcomming assertion fails
22 | *
23 | * @yields {any}
24 | * @since 0.0.0
25 | */
26 | Cypress.Commands.overwrite('then', (originalCommand, subject, fn, options = {}) => {
27 | if (_.isFunction(options)) {
28 | // Flip the values of `fn` and `options`
29 | [fn, options] = [options, fn];
30 | }
31 |
32 | validator.check('log', options.log, [true, false]);
33 | validator.check('retry', options.retry, [true, false]);
34 |
35 | if (options.retry && typeof options.log === 'undefined') {
36 | options.log = true;
37 | }
38 |
39 | _.defaults(options, {
40 | log: false,
41 | retry: false,
42 | });
43 |
44 | // Setup logging
45 | const consoleProps = {};
46 | if (options.log) {
47 | options._log = Cypress.log({
48 | name: 'then',
49 | message: '',
50 | consoleProps: () => consoleProps,
51 | });
52 |
53 | if (isJquery(subject)) {
54 | // Link the DOM element to the logger
55 | options._log.set('$el', $(subject));
56 | consoleProps['Applied to'] = $(subject);
57 | } else {
58 | consoleProps['Applied to'] = String(subject);
59 | }
60 |
61 | if (options.retry) {
62 | options._log.set('message', 'retry');
63 | }
64 | }
65 |
66 | /**
67 | * This function is recursively called untill timeout or the upcomming
68 | * assertion passes. Keep this function as fast as possible.
69 | *
70 | * @return {Promise}
71 | */
72 | async function executeFnAndRetry() {
73 | const result = await executeFn();
74 |
75 | return cy.verifyUpcomingAssertions(result, options, {
76 | // Try again by calling itself
77 | onRetry: executeFnAndRetry,
78 | });
79 | }
80 |
81 | /**
82 | * Execute the provided callback function
83 | *
84 | * @return {*}
85 | */
86 | async function executeFn() {
87 | // Execute using the original `then` to not reinvent the wheel
88 | return await originalCommand(subject, options, fn).then((value) => {
89 | if (options.log) {
90 | consoleProps.Yielded = value;
91 | }
92 | return value;
93 | });
94 | }
95 |
96 | if (options.retry) {
97 | return executeFnAndRetry();
98 | }
99 | return executeFn();
100 | });
101 |
--------------------------------------------------------------------------------
/src/to.js:
--------------------------------------------------------------------------------
1 | const _ = Cypress._;
2 |
3 | import { command } from './utils/errorMessages';
4 | const errMsg = command.to;
5 |
6 | import OptionValidator from './utils/optionValidator';
7 | const validator = new OptionValidator('to');
8 |
9 | const types = {
10 | array: castArray,
11 | string: castString,
12 | number: castNumber,
13 | };
14 |
15 | /**
16 | * @param {string} type Target type
17 | * @param {Object} [options]
18 | * @param {boolean} [options.log=true]
19 | * Log the command to the Cypress command log
20 | *
21 | * @yields {any}
22 | * @since 0.3.0
23 | */
24 | Cypress.Commands.add('to', { prevSubject: true }, (subject, type, options = {}) => {
25 | validator.check('log', options.log, [true, false]);
26 |
27 | _.defaults(options, {
28 | log: true,
29 | });
30 |
31 | if (_.isUndefined(subject)) {
32 | throw new Error(errMsg.cantCastType('undefined'));
33 | }
34 | if (_.isNull(subject)) {
35 | throw new Error(errMsg.cantCastType('null'));
36 | }
37 | if (_.isNaN(subject)) {
38 | throw new Error(errMsg.cantCastType('NaN'));
39 | }
40 |
41 | if (!_.keys(types).includes(type)) {
42 | // We don't know the given type, so we can't cast to it
43 | throw new Error(`${errMsg.cantCast('subject', type)} ${errMsg.expected(_.keys(types))}`);
44 | }
45 |
46 | const consoleProps = {
47 | 'Applied to': subject,
48 | };
49 | if (options.log) {
50 | options._log = Cypress.log({
51 | name: 'to',
52 | message: type,
53 | consoleProps: () => consoleProps,
54 | });
55 | }
56 |
57 | /**
58 | * Cast the subject and do the upcoming assertion
59 | * @return {Promise}
60 | */
61 | function castSubject() {
62 | try {
63 | return cy.verifyUpcomingAssertions(types[type](subject), options, {
64 | // Retry untill the upcoming assertion passes
65 | onRetry: castSubject,
66 | });
67 | } catch (err) {
68 | // The casting function threw an error, let's try again
69 | options.error = err;
70 | return cy.retry(castSubject, options, options._log);
71 | }
72 | }
73 |
74 | return castSubject().then((result) => {
75 | // Everything passed, finish up the log
76 | consoleProps.Yielded = result;
77 | return result;
78 | });
79 | });
80 |
81 | /**
82 | * @param {any|any[]} subject
83 | * @return {any[]}
84 | */
85 | function castArray(subject) {
86 | if (_.isArrayLikeObject(subject)) {
87 | return subject;
88 | }
89 | return [subject];
90 | }
91 |
92 | /**
93 | * @param {any|any[]} subject
94 | * @return {string}
95 | */
96 | function castString(subject) {
97 | if (_.isArrayLikeObject(subject)) {
98 | return subject.map(castString);
99 | }
100 | if (_.isObject(subject)) {
101 | return JSON.stringify(subject);
102 | }
103 | return `${subject}`;
104 | }
105 |
106 | /**
107 | * @param {any|any[]} subject
108 | * @return {number|number[]}
109 | */
110 | function castNumber(subject) {
111 | if (_.isArrayLikeObject(subject)) {
112 | return subject.map(castNumber);
113 | } else if (_.isObject(subject)) {
114 | throw new Error(errMsg.cantCastType('object', 'number'));
115 | }
116 |
117 | const casted = Number(subject);
118 | if (isNaN(casted)) {
119 | throw new Error(errMsg.cantCastVal(subject, 'number'));
120 | }
121 | return casted;
122 | }
123 |
--------------------------------------------------------------------------------
/src/utils/commandError.js:
--------------------------------------------------------------------------------
1 | const _ = Cypress._;
2 |
3 | /**
4 | * Error namespace for command related issues
5 | */
6 | export default class CommandError extends Error {
7 | /**
8 | * @param {string} message The error message
9 | */
10 | constructor(message) {
11 | if (_.isArray(message)) {
12 | message = message.join('');
13 | }
14 |
15 | super(message);
16 |
17 | this.name = 'CommandError';
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/utils/commandQueue.js:
--------------------------------------------------------------------------------
1 | import { notInProduction } from './errorMessages';
2 |
3 | /**
4 | * By marking the current command we can retrieve it later in any
5 | * context, including retried context.
6 | * @param {string} commandName
7 | */
8 | export function markCurrentCommand(commandName) {
9 | const queue = Cypress.cy.queue;
10 | const currentCommand = queue
11 | .get()
12 | .filter((command) => {
13 | return command.get('name') === commandName && !command.get('invoked');
14 | })
15 | .shift();
16 |
17 | // The mark
18 | currentCommand.attributes.invoked = true;
19 | }
20 |
21 | /**
22 | * Find out of the last marked command in the command queue has upcoming
23 | * assertions that negate existence.
24 | * @return {boolean}
25 | */
26 | export function upcomingAssertionNegatesExistence() {
27 | const currentCommand = getLastMarkedCommand();
28 | if (!currentCommand) {
29 | return false;
30 | }
31 |
32 | const upcomingAssertions = getUpcomingAssertions(currentCommand);
33 |
34 | return upcomingAssertions.some((c) => {
35 | let args = c.get('args');
36 | if (typeof args[0] === 'string') {
37 | args = args[0].split('.');
38 | return args.includes('exist') && args.includes('not');
39 | }
40 | return false;
41 | });
42 | }
43 |
44 | /**
45 | * @return {Command|false}
46 | */
47 | function getLastMarkedCommand() {
48 | const queue = Cypress.cy.queue;
49 | const cmd = queue
50 | .get()
51 | .filter((command) => command.get('invoked'))
52 | .pop();
53 |
54 | if (cmd === undefined) {
55 | console.error(
56 | 'Could not find any marked commands in the queue. ' +
57 | 'Did you forget to mark the command during its invokation?' +
58 | `\n\n${notInProduction}`
59 | );
60 |
61 | return false;
62 | }
63 |
64 | return cmd;
65 | }
66 |
67 | /**
68 | * Recursively find all direct upcoming assertions
69 | * @param {Command} command
70 | * @return {Command[]}
71 | */
72 | function getUpcomingAssertions(command) {
73 | const next = command.get('next');
74 | let ret = [];
75 | if (next && next.get('type') === 'assertion') {
76 | ret = [next, ...getUpcomingAssertions(next)];
77 | }
78 | return ret;
79 | }
80 |
--------------------------------------------------------------------------------
/src/utils/errorMessages.js:
--------------------------------------------------------------------------------
1 | import { repository } from '../../package.json';
2 |
3 | export const notInProduction =
4 | 'This message should never show ' +
5 | "if you're a user of cypress-commands. If it does, please open " +
6 | `an issue at ${repository.url}.`;
7 |
8 | export const command = {
9 | attribute: {
10 | disable_strict:
11 | 'This behaviour can be disabled by calling ' +
12 | "'.attribute()' with the option 'strict: false'.",
13 | existence: {
14 | single: {
15 | negated: (attribute) => {
16 | return (
17 | 'Expected element to not have attribute ' +
18 | `'${attribute}', but it was continuously found.`
19 | );
20 | },
21 | normal: (attribute) => {
22 | return (
23 | 'Expected element to have attribute ' +
24 | `'${attribute}', but never found it.`
25 | );
26 | },
27 | },
28 | multiple: {
29 | negated: (attribute, inputCount, outputCount) => {
30 | return (
31 | `Expected all ${inputCount} elements to not have ` +
32 | `attribute '${attribute}', but it was continuously found on ` +
33 | `${outputCount} elements.`
34 | );
35 | },
36 | normal: (attribute, inputCount, outputCount) => {
37 | return (
38 | `Expected all ${inputCount} elements to have ` +
39 | `attribute '${attribute}', but never found it on ` +
40 | `${inputCount - outputCount} elements.`
41 | );
42 | },
43 | },
44 | },
45 | },
46 | to: {
47 | cantCast: (description, target) => {
48 | let message = `Can't cast ${description}`;
49 | if (target) {
50 | message += ` to type ${target}`;
51 | }
52 | return `${message}.`;
53 | },
54 | cantCastType: (type, target) => {
55 | return command.to.cantCast(`subject of type ${type}`, target);
56 | },
57 | cantCastVal: (val, target) => {
58 | return command.to.cantCast(`'${val}'`, target);
59 | },
60 | expected: (expected) => {
61 | return `Expected one of [${expected.join(', ')}]`;
62 | },
63 | },
64 | };
65 |
--------------------------------------------------------------------------------
/src/utils/isJquery.js:
--------------------------------------------------------------------------------
1 | const _ = Cypress._;
2 |
3 | /**
4 | * Find out if a given value is a jQuery object
5 | * @param {*} value
6 | * @return {boolean}
7 | */
8 | export default function isJquery(value) {
9 | if (_.isUndefined(value) || _.isNull(value)) {
10 | return false;
11 | }
12 | return !!value.jquery;
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/optionValidator.js:
--------------------------------------------------------------------------------
1 | const _ = Cypress._;
2 |
3 | import CommandError from './commandError';
4 | import { repository } from '../../package.json';
5 |
6 | /**
7 | * Validate user set options
8 | */
9 | export default class OptionValidator {
10 | /**
11 | * @param {string} commandName
12 | */
13 | constructor(commandName) {
14 | /**
15 | * @type {string}
16 | */
17 | this.command = commandName;
18 |
19 | /**
20 | * Url to the full documentation of the command
21 | * @type {string}
22 | */
23 | this.docUrl = `${repository.url}/blob/master/docs/${commandName}.md`;
24 | }
25 |
26 | /**
27 | * Validate a user set option
28 | * @param {string} option
29 | * @param {*} actual
30 | * @param {string[]|string} expected
31 | * @throws {CommandError}
32 | */
33 | check(option, actual, expected) {
34 | if (_.isUndefined(actual)) {
35 | // The option is not set. This is fine.
36 | return;
37 | }
38 |
39 | const errMessage = {
40 | start: `Bad value for the option "${option}" of the command "${this.command}".\n\n`,
41 | received: `Command received the value "${actual}" but `,
42 | end: `\n\nFor details refer to the documentation at ${this.docUrl}`,
43 | };
44 |
45 | if (_.isArray(expected)) {
46 | if (!expected.includes(actual)) {
47 | throw new CommandError([
48 | errMessage.start,
49 | errMessage.received,
50 | `expected one of ${JSON.stringify(expected)}`,
51 | errMessage.end,
52 | ]);
53 | }
54 | } else if (_.isString(expected)) {
55 | if (!eval(`'${actual}' ${expected}`)) {
56 | throw new CommandError([
57 | errMessage.start,
58 | errMessage.received,
59 | `expected a value ${expected}`,
60 | errMessage.end,
61 | ]);
62 | }
63 | } else {
64 | throw new CommandError(
65 | 'Not sure how to validate ' +
66 | `the option "${option}" of the command "${this.command}".\n\n` +
67 | 'If you see this message in the wild, please create an issue ' +
68 | `so this error can be resolved.\n${repoUrl}`
69 | );
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/utils/whitespace.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {'simplify'|'keep-newline'|'keep'} mode
3 | * @return {function}
4 | */
5 | export default function whitespace(mode) {
6 | const zeroWidthWhitespace = /[\u200B-\u200D\uFEFF]/g;
7 |
8 | if (mode === 'simplify') {
9 | return (input) => {
10 | return input.replace(zeroWidthWhitespace, '').replace(/\s+/g, ' ').trim();
11 | };
12 | }
13 |
14 | if (mode === 'keep-newline') {
15 | return (input) => {
16 | return input
17 | .replace(zeroWidthWhitespace, '')
18 | .replace(/[^\S\n]+/g, ' ')
19 | .replace(/^[^\S\n]/g, '')
20 | .replace(/[^\S\n]$/g, '')
21 | .replace(/[^\S\n]*\n[^\S\n]*/g, '\n');
22 | };
23 | }
24 |
25 | return (input) => input;
26 | }
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["es2015", "dom"],
4 | "allowJs": true,
5 | "types": ["./types", "cypress"],
6 | "outDir": "./dist"
7 | },
8 | "include": ["cypress/**/*.js", "src/**/*.js"]
9 | }
10 |
--------------------------------------------------------------------------------
/types/attribute.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare namespace Cypress {
4 | interface Chainable {
5 | /**
6 | * Get the value of an attribute of a DOM element.
7 | *
8 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/attribute.md
9 | */
10 | attribute(
11 | attribute: string,
12 | options?: Partial
13 | ): Chainable;
14 |
15 | /**
16 | * Get the value of an attribute of a DOM element.
17 | *
18 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/attribute.md
19 | */
20 | attribute(
21 | options: Partial,
22 | attribute: string
23 | ): Chainable;
24 | }
25 |
26 | interface AttributeOptions extends Loggable, WhitespaceOptions {
27 | /**
28 | * If true, implicitly assert that all subjects have the requested attribute.
29 | *
30 | * @default true
31 | */
32 | strict: boolean;
33 |
34 | /**
35 | * @default 'keep'
36 | */
37 | whitespace: WhitespaceOptions['whitespace'];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/types/generic.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare namespace Cypress {
4 | interface WhitespaceOptions {
5 | /**
6 | * How to handle whitespace in the string.
7 | *
8 | * - 'simplify': Replace all whitespace with a single space.
9 | * - 'keep-newline': Replace all whitespace except for newline characters (`\n`) with a
10 | * single space.
11 | * - 'keep': Don't replace any whitespace.
12 | */
13 | whitespace: 'simplify' | 'keep-newline' | 'keep';
14 | }
15 |
16 | /**
17 | * Options that controls if the command can will be retried when it fails.
18 | *
19 | * A command should only be retryable when the command does not retry by default.
20 | */
21 | interface Retryable {
22 | /**
23 | * Retry the command when upcoming assertions fail.
24 | *
25 | * @default false
26 | */
27 | retry: boolean;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import './generic';
2 | import './attribute';
3 | import './then';
4 | import './text';
5 | import './to';
6 |
7 | // TODO: Update `request()` docs
8 | // TODO: Update `then()` docs
9 |
--------------------------------------------------------------------------------
/types/text.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare namespace Cypress {
4 | interface Chainable {
5 | /**
6 | * Get the text contents of a DOM element.
7 | *
8 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/text.md
9 | */
10 | text(options?: Partial): Chainable;
11 | }
12 |
13 | interface TextOptions extends Loggable, WhitespaceOptions {
14 | /**
15 | * Include the text contents of child elements upto `n` levels.
16 | *
17 | * @default 0
18 | */
19 | depth: number;
20 |
21 | /**
22 | * @default 'simplify'
23 | */
24 | whitespace: WhitespaceOptions['whitespace'];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/types/then.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare namespace Cypress {
4 | interface Chainable {
5 | /**
6 | * Enables you to work with the subject yielded from the previous command.
7 | *
8 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/then.md
9 | */
10 | then(
11 | options: Partial,
12 | fn: (this: ObjectLike, currentSubject: Subject) => Chainable
13 | ): Chainable;
14 | /**
15 | * Enables you to work with the subject yielded from the previous command / promise.
16 | *
17 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/then.md
18 | */
19 | then(
20 | options: Partial,
21 | fn: (this: ObjectLike, currentSubject: Subject) => PromiseLike
22 | ): Chainable;
23 | /**
24 | * Enables you to work with the subject yielded from the previous command / promise.
25 | *
26 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/then.md
27 | */
28 | then(
29 | options: Partial,
30 | fn: (this: ObjectLike, currentSubject: Subject) => S
31 | ): Chainable;
32 | /**
33 | * Enables you to work with the subject yielded from the previous command.
34 | *
35 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/then.md
36 | * @example
37 | * cy.get('.nav').then(($nav) => {}) // Yields .nav as first arg
38 | * cy.location().then((loc) => {}) // Yields location object as first arg
39 | */
40 | then(
41 | options: Partial,
42 | fn: (this: ObjectLike, currentSubject: Subject) => void
43 | ): Chainable;
44 |
45 | /**
46 | * Enables you to work with the subject yielded from the previous command.
47 | *
48 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/then.md
49 | */
50 | then(
51 | fn: (this: ObjectLike, currentSubject: Subject) => Chainable,
52 | options: Partial
53 | ): Chainable;
54 | /**
55 | * Enables you to work with the subject yielded from the previous command / promise.
56 | *
57 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/then.md
58 | */
59 | then(
60 | fn: (this: ObjectLike, currentSubject: Subject) => PromiseLike,
61 | options: Partial
62 | ): Chainable;
63 | /**
64 | * Enables you to work with the subject yielded from the previous command / promise.
65 | *
66 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/then.md
67 | */
68 | then(
69 | fn: (this: ObjectLike, currentSubject: Subject) => S,
70 | options: Partial
71 | ): Chainable;
72 | /**
73 | * Enables you to work with the subject yielded from the previous command.
74 | *
75 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/then.md
76 | * @example
77 | * cy.get('.nav').then(($nav) => {}) // Yields .nav as first arg
78 | * cy.location().then((loc) => {}) // Yields location object as first arg
79 | */
80 | then(
81 | fn: (this: ObjectLike, currentSubject: Subject) => void,
82 | options: Partial
83 | ): Chainable;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/types/to.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare namespace Cypress {
4 | interface Chainable {
5 | /**
6 | * Cast the subject to a given type
7 | *
8 | * - When the subject is an array it will cast all items in the array instead
9 | * - When the subject is already the given type it will do nothing
10 | *
11 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/to.md
12 | */
13 | to(type: 'string' | 'number' | 'array', options?: Partial): Chainable;
14 |
15 | /**
16 | * Cast the subject to a given type
17 | *
18 | * - When the subject is an array it will cast all items in the array instead
19 | * - When the subject is already the given type it will do nothing
20 | *
21 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/to.md
22 | */
23 | to(
24 | type: 'string',
25 | options?: Partial
26 | ): Chainable;
27 |
28 | /**
29 | * Cast the subject to a given type
30 | *
31 | * - When the subject is an array it will cast all items in the array instead
32 | * - When the subject is already the given type it will do nothing
33 | *
34 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/to.md
35 | */
36 | to(
37 | type: 'number',
38 | options?: Partial
39 | ): Chainable;
40 |
41 | /**
42 | * Cast the subject to a given type
43 | *
44 | * - When the subject is an array it will cast all items in the array instead
45 | * - When the subject is already the given type it will do nothing
46 | *
47 | * @see https://github.com/Lakitna/cypress-commands/blob/master/docs/to.md
48 | */
49 | to(
50 | type: 'array',
51 | options?: Partial
52 | ): Chainable;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------