├── .gitignore
├── .npmrc
├── README.md
├── circle.yml
├── cypress.json
├── cypress
├── fixtures
│ └── example.json
├── integration
│ └── spec.js
├── plugins
│ └── index.js
└── support
│ ├── commands.js
│ └── index.js
├── img
└── get-it.png
├── index.html
├── index.js
├── package-lock.json
├── package.json
└── renovate.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | cypress/videos
3 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=true
2 | save-exact=true
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cypress-get-it [](https://circleci.com/gh/bahmutov/cypress-get-it) [![renovate-app badge][renovate-badge]][renovate-app]
2 |
3 | > Get elements by data attribute by creating a Cy command on the fly
4 |
5 | [![NPM][npm-icon] ][npm-url]
6 |
7 | ## Why
8 |
9 | According to the Cypress best practices for [Selecting Elements](https://on.cypress.io/best-practices#Selecting-Elements) using dedicated test id data attributes is the most stable way of finding elements during end-to-end tests. Yet typing the full selector is ... annoying.
10 |
11 | ```html
12 |
foo
13 | ```
14 | ```js
15 | cy.get('[data-test-id="foo"]').should('have.text', 'foo')
16 | ```
17 |
18 | Who has time for this!
19 |
20 | So you could make a little utility function
21 |
22 | ```js
23 | const tid = s => `[data-test-id="${s}"]`
24 | cy.get(tid('foo')).should('have.text', 'foo')
25 | ```
26 |
27 | Hmm, sure, but what if your HTML markup has different data attributes? Like this
28 |
29 | ```html
30 | foo
31 | bar
32 | baz
33 | ```
34 |
35 | Do you need to write 3 different helper functions? No. With `cypress-get-it` you can create data attribute selector _methods_ with descriptive names. In fact, the actual selector will be created _from the method name_ on the fly. You want to get element with `data-test-id=foo`? Just use camel cased words and call `cy.getDataTestId('foo')`. The above elements become
36 |
37 | ```js
38 | cy.getDataTestId('foo').should('have.text', 'foo')
39 | cy.getDataTest('bar').should('have.text', 'bar')
40 | cy.getTestId('baz').should('have.text', 'baz')
41 | ```
42 |
43 | 
44 |
45 | So any element attribute like `foo-bar-baz="something"` could be fetched with descriptive `cy.getFooBarBaz("something")`.
46 |
47 | ## How it works
48 |
49 | Load this plugin from your support file and it will replace global `cy` instance with an ES6 Proxy which will intercept any methods calls to `get...` and will call [`cy.get`](https://on.cypress.io/get) with the right selector automatically.
50 |
51 | ## Install and use
52 |
53 | ```shell
54 | npm i -D cypress-get-it
55 | ```
56 |
57 | Require this module from your `cypress/support/index.js`
58 |
59 | ```js
60 | require('cypress-get-it')
61 | ```
62 |
63 | ### Small print
64 |
65 | Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2019
66 |
67 | * [@bahmutov](https://twitter.com/bahmutov)
68 | * [glebbahmutov.com](https://glebbahmutov.com)
69 | * [blog](https://glebbahmutov.com/blog)
70 |
71 | License: MIT - do anything with the code, but don't blame me if it does not work.
72 |
73 | Support: if you find any problems with this module, email / tweet /
74 | [open issue](https://github.com/bahmutov/cypress-get-it/issues) on Github
75 |
76 | ## MIT License
77 |
78 | Copyright (c) 2019 Gleb Bahmutov <gleb.bahmutov@gmail.com>
79 |
80 | Permission is hereby granted, free of charge, to any person
81 | obtaining a copy of this software and associated documentation
82 | files (the "Software"), to deal in the Software without
83 | restriction, including without limitation the rights to use,
84 | copy, modify, merge, publish, distribute, sublicense, and/or sell
85 | copies of the Software, and to permit persons to whom the
86 | Software is furnished to do so, subject to the following
87 | conditions:
88 |
89 | The above copyright notice and this permission notice shall be
90 | included in all copies or substantial portions of the Software.
91 |
92 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
93 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
94 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
95 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
96 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
97 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
98 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
99 | OTHER DEALINGS IN THE SOFTWARE.
100 |
101 | [npm-icon]: https://nodei.co/npm/cypress-skip-and-only-ui.svg?downloads=true
102 | [npm-url]: https://npmjs.org/package/cypress-skip-and-only-ui
103 | [renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg
104 | [renovate-app]: https://renovateapp.com/
105 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | orbs:
3 | cypress: cypress-io/cypress@1.19.1
4 |
5 | jobs:
6 | release:
7 | executor: cypress/base-12-14-0
8 | steps:
9 | - attach_workspace:
10 | at: ~/
11 | - run: npm run semantic-release 🚀
12 |
13 | workflows:
14 | build:
15 | jobs:
16 | - cypress/run:
17 | name: Cypress run 🛎📦🧪
18 | - release:
19 | filters:
20 | branches:
21 | only:
22 | - master
23 | requires:
24 | - Cypress run 🛎📦🧪
25 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/cypress/integration/spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | it('finds element by test id data attribute', () => {
4 | cy.visit('index.html')
5 | cy.get('[data-test-id="foo"]').should('have.text', 'foo')
6 | cy.getDataTestId('foo').should('have.text', 'foo')
7 | cy.getDataTest('bar').should('have.text', 'bar')
8 | cy.getTestId('baz').should('have.text', 'baz')
9 | })
10 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.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 commands.js using ES2015 syntax:
17 | import '../..'
18 | import './commands'
19 |
20 | // Alternatively you can use CommonJS syntax:
21 | // require('./commands')
22 |
--------------------------------------------------------------------------------
/img/get-it.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bahmutov/cypress-get-it/971db60d7c694fbf42e576425ce0051b072bf3f4/img/get-it.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 | foo
3 | bar
4 | baz
5 |
6 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | const getSomethingToWords = s => Cypress._.split(Cypress._.snakeCase(s), '_')
4 |
5 | const getAttribute = words => {
6 | // ['get', 'data', test', 'id']
7 | words.shift()
8 | return words.join('-')
9 | }
10 |
11 | global.cy = new Proxy(global.cy, {
12 | get (target, prop) {
13 | console.log('getting prop', prop)
14 | if (/^get\w+/.test(prop)) {
15 | console.log('get', prop)
16 | const words = getSomethingToWords(prop)
17 | console.log(words)
18 | const attribute = getAttribute(words)
19 | console.log(attribute)
20 |
21 | return selector => {
22 | cy.log(`${prop} "${selector}"`)
23 | return target.get(`[${attribute}="${selector}"]`, { log: false })
24 | }
25 | } else {
26 | return target[prop]
27 | }
28 | }
29 | })
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cypress-get-it",
3 | "version": "0.0.0-development",
4 | "description": "Get elements by data attribute by creating a Cy command on the fly",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "cypress run",
8 | "semantic-release": "semantic-release"
9 | },
10 | "keywords": [
11 | "cypress",
12 | "cypress-plugin"
13 | ],
14 | "author": "Gleb Bahmutov (https://glebbahmutov.com/)",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "cypress": "4.2.0",
18 | "semantic-release": "17.0.4"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/bahmutov/cypress-get-it.git"
23 | },
24 | "files": [
25 | "*.js"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "automerge": true,
6 | "commitMessage": "{{semanticPrefix}}Update {{depName}} to {{newVersion}} 🌟",
7 | "prTitle": "{{semanticPrefix}}{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{newVersion}} 🌟",
8 | "prConcurrentLimit": 3,
9 | "prHourlyLimit": 2,
10 | "rangeStrategy": "pin",
11 | "schedule": [
12 | "after 10pm and before 5am on every weekday",
13 | "every weekend"
14 | ],
15 | "updateNotScheduled": false,
16 | "timezone": "America/New_York",
17 | "lockFileMaintenance": {
18 | "enabled": true
19 | },
20 | "separatePatchReleases": true,
21 | "separateMultipleMajor": true,
22 | "masterIssue": true,
23 | "labels": [
24 | "type: dependencies",
25 | "renovate"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------