0.0.7 • Published 3 months ago

cypress-selector-shorthand v0.0.7

Weekly downloads
-
License
GPL-3.0
Repository
github
Last release
3 months ago

cypress-selector-shorthand

Tired of the boilerplate needed for writing unambiguous selectors for Cypress UI tests? Use cypress-selector-shorthand to create navigation shorthands!

With this plugin, the following examples have full IntelliSense, and you can chain arbitrary Cypress commands off of your app selectors:

myApp.appInfo.createNewUserSection.firstNameInput
    .type('First');
myApp.appInfo.createNewUserSection.lastNameInput
    .type('Last');
myApp.appInfo.createNewUserSection.submitButton
    .click();
myApp.appInfo.createNewUserSection
    .within(({ firstNameInput, lastNameInput, submitButton }) => {
        firstNameInput
            .type('First');
        lastNameInput
            .type('Last');
        submitButton
            .click();
    });

The above snippets are equivalent to the following raw Cypress:

cy.get('[data-test=app_info] [data-test=create_new_user_section] [data-test=first_name_input]')
    .type('First');
cy.get('[data-test=app_info] [data-test=create_new_user_section] [data-test=last_name_input]')
    .type('Last');
cy.get('[data-test=app_info] [data-test=create_new_user_section] [data-test=submit_button]')
    .click();
cy.get('[data-test=app_info] [data-test=create_new_user_section]')
    .within(() => {
        cy.get('[data-test=first_name_input]')
            .type('First');
        cy.get('[data-test=last_name_input]')
            .type('Last');
        cy.get('[data-test=submit_button]')
            .click();
    });

There's also new cy.tget command. It works the same way as the built-in cy.get, but automatically wraps non-css/jQuery identifiers with [data-test].

For example:

cy.tget('app_info:visible create_new_user_section submit_button');

will be converted automatically into

cy.get('[data-test=app_info]:visible [data-test=create_new_user_section] [data-test=submit_button]');

Installation

pnpm add -D cypress-selector-shorthand
# or
npm i -D cypress-selector-shorthand
# or
yarn add -D cypress-selector-shorthand

Add the appropriate import to your project's cypress/support/commands.js (or commands.ts):

require('cypress-selector-shorthand/install');
import 'cypress-selector-shorthand/install';

Configuration

To make use of the navigation shorthands, there's three extra steps you need to do to generate the objects and interfaces. (Note: you can use cy.tget() without this.)

First, you need to create a schema JSON file, e.g. the following might be used for the example at the top of this file:

./myAppSchema.json

{
    "app_info": {
        "create_new_user_section": {
            "name_input": null,
            "address_input": null,
            "submit_button": null
        },
        "existing_users_table": {
            "user_rows": {
                "user_row": {
                    "firstName": null,
                    "lastName": null
                }
            }
        }
    }
}

Once you have your schema, you need to generate interfaces for IntelliSense:

npx cypress-selector-shorthand interfaces --appName 'MyApp' --schema './myAppSchema.json' --outfile './src/myAppInterface.ts'`

This will create a number of interfaces from your schema file (using the quicktype library). For example, the below is the result of the sample JSON above.

./src/myAppInterface.ts

export type MyApp = {
    appInfo: Cypress.ChainableLike<JQuery<HTMLElement>, AppInfo>;
} & { [key: string]: Cypress.ChainableLike<JQuery<HTMLElement>, AppInfo> };

export type AppInfo = {
    createNewUserSection: Cypress.ChainableLike<JQuery<HTMLElement>, CreateNewUserSection>;
    existingUsersTable:   Cypress.ChainableLike<JQuery<HTMLElement>, ExistingUsersTable>;
} & { [key: string]: Cypress.ChainableLike<JQuery<HTMLElement>, CreateNewUserSection | ExistingUsersTable> };

export type CreateNewUserSection = {
    nameInput:    Cypress.Chainable<JQuery<HTMLElement>>;
    addressInput: Cypress.Chainable<JQuery<HTMLElement>>;
    submitButton: Cypress.Chainable<JQuery<HTMLElement>>;
};

export type ExistingUsersTable = {
    userRows: Cypress.ChainableLike<JQuery<HTMLElement>, UserRows>;
} & { [key: string]: Cypress.ChainableLike<JQuery<HTMLElement>, UserRows> };

export type UserRows = {
    userRow: Cypress.ChainableLike<JQuery<HTMLElement>, UserRow>;
} & {
    with:    UserWith;
} & { [key: string]: Cypress.ChainableLike<JQuery<HTMLElement>, UserRow> };

export type UserRow = {
    firstName: Cypress.Chainable<JQuery<HTMLElement>>;
    lastName:  Cypress.Chainable<JQuery<HTMLElement>>;
};

export interface UserWith {
    firstName: (text: string) => Cypress.ChainableLike<JQuery<HTMLElement>, UserRow>;
    lastName:  (text: string) => Cypress.ChainableLike<JQuery<HTMLElement>, UserRow>;
}

You might notice this generated a with field that didn't exist in the schema. More on that under the Usage section.

Finally, you need to generate your navigation object:

myApp.ts

import { generateNavigationObject } from 'cypress-selector-shorthand';

import type MyApp from './myAppInterface.ts';

const myApp = generateNavigationObject<MyApp>(schema);

export { myApp };

Usage

cy.tget(selector, options) behaves the same way as cy.get, but it processes your selector string before passing it to cy.get.

For any tokens in selector that are not inside ()/{}/[] and do not start with @, #, ., >, :, [, (, or {, the token is wrapped as [data-test=${token}]. If token is followed by a :, >, or ~, those are left alone. So token:visible will become [data-test=token]:visible, etc.

If you are using the generated selector shorthands, you can chain Cypress commands off them. The shorthand chain is not evaluated until you chain a Cypress command off of it. So myApp.appInfo.existingUsersSection will not do anything, but myApp.appInfo.existingUsersSection.refresh.click() is equivalent to cy.tget('my_app app_info existing_users_section refresh').click().

Note that the selector shorthand generation looks for fields ending with rows/items/menu-items with a matching direct descendent that also has descendants (i.e. row/item/menu-item), and generates a corresponding with property. So in the example, you can use myApp.appInfo.existingUsersSection.rows.with.firstName('John') and that will return all rows for people with the first name John. This is equivalent to cy.tget('my_app app_info existing_users_section rows row:has([data-test="firstName"]:contains(John))'), except the tget won't actually fire until you chain further Cypress commands off of it.

The selector shorthands are also passed to .within. If you use .within on a regular Cypress command, it behaves exactly like normal, but if you chain .within on a generated navigation object, it yields the navigation object AND the previous subject:

import { myApp } from './myApp.ts';

cy.tget('something')
    // Normal Cypress callback parameters
    .within((prevSubject) => {});

myApp.appInfo
    // Parameter order is different! First argument matches whatever shorthand the `.within` was chained off of,
    // whereas the second argument is generally going to be the corresponding element itself.
    .within((appInfo, prevSubject) => {});

myApp.appInfo
    // You can destructure and continue using shorthands:
    .within(({ createNewUserSection, existingUsersTable }) => {
        createNewUserSection
            .should('be.visible');
        existingUsersTable
            .scrollIntoView();
    });
0.0.7-test10

3 months ago

0.0.7

3 months ago

0.0.7-test8

3 months ago

0.0.7-test9

3 months ago

0.0.7-test6

3 months ago

0.0.7-test7

3 months ago

0.0.7-test5

3 months ago

0.0.7-test2

3 months ago

0.0.7-test3

3 months ago

0.0.7-test4

3 months ago

0.0.7-test

3 months ago

0.0.6

8 months ago

0.0.5

8 months ago

0.0.4

10 months ago

0.0.4-finaltest1

10 months ago

0.0.4-testimport5

10 months ago

0.0.4-testimport4

10 months ago

0.0.4-testimport3

10 months ago

0.0.4-testimport2

10 months ago

0.0.4-testimport1

10 months ago

0.0.3

10 months ago

0.0.3-pre

10 months ago

0.0.2

10 months ago

0.0.2-pre

10 months ago

0.0.2-pre.2

10 months ago

0.0.2-pre.1

10 months ago

0.0.2-pre.0

10 months ago

0.0.1

10 months ago