Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
530f6ed
chore(deps): update typescript-eslint monorepo to v4.9.0
renovate-bot Dec 5, 2020
78fc3c0
Merge pull request #199 from javierbrea/renovate/typescript-eslint-mo…
renovate[bot] Dec 5, 2020
2080397
Merge branch 'release' of github.com:javierbrea/cypress-localstorage-…
javierbrea Dec 5, 2020
84238f6
feat(#191): Add disableLocalStorage command
javierbrea Dec 6, 2020
7c642bd
chore(deps): update dependency eslint-plugin-prettier to v3.2.0
renovate-bot Dec 6, 2020
a18bc1a
Merge pull request #203 from javierbrea/renovate/eslint-plugin-pretti…
renovate[bot] Dec 7, 2020
13836cb
docs(#191): Add disableLocalStorage command docs
javierbrea Dec 7, 2020
eb7ce9c
test(#191): Add tests from documentation examples. Stub also localSto…
javierbrea Dec 7, 2020
5a1bb0b
style(lint): Lint also files in root folder. Fix jest.config style
javierbrea Dec 7, 2020
3aa30db
docs(changelog): Add changes to changelog
javierbrea Dec 7, 2020
21c3247
refactor(#167): Stub methods dinamically based on localStorage method…
javierbrea Dec 7, 2020
649f788
feat(#191): Add withError option to disableLocalStorage command
javierbrea Dec 7, 2020
d3242ff
docs(#191): Modify disableLocalStorage example as it is in E2E tests
javierbrea Dec 7, 2020
9595b53
test(e2e): Remove console.log
javierbrea Dec 7, 2020
aa3579e
chore(lint): Add missed pattern to lint-staged
javierbrea Dec 7, 2020
8b01094
docs(changelog): Add change
javierbrea Dec 7, 2020
d29818c
Merge branch 'release' into feat-191-disable-localstorage
javierbrea Dec 7, 2020
df1873a
Merge pull request #204 from javierbrea/feat-191-disable-localstorage
javierbrea Dec 7, 2020
19145c2
chore(release): Upgrade version
javierbrea Dec 7, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Removed
### BREAKING CHANGES

## [1.3.0] - 2020-12-07

### Added
- feat(#191): Add disableLocalStorage command (Thanks to @Uninen for his contribution).

### Changed
- chore(deps): Update some devDependencies

### Fixed
- style(lint): Lint also files in root folder. Fix jest.config style

## [1.2.5] - 2020-12-05

### Changed
Expand Down
132 changes: 102 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@

# Cypress localStorage commands

Extends Cypress' cy commands with localStorage methods. Allows preserving localStorage between tests.
Extends Cypress' cy commands with localStorage methods. Allows preserving localStorage between tests and disabling localStorage.

## The problem
## The problems

You want to preserve localStorage between Cypress tests.
* You want to preserve localStorage between Cypress tests.
* You want to disable localStorage to check error handling.

## This solution

This solution allows you to use all browser localStorage methods through Cypress commands, and preserve it between tests.
This solution allows you to use all browser localStorage methods through Cypress commands, and preserve it between tests. It also allows to simulate that localStorage is disabled in the browser.

## Installation

Expand All @@ -38,45 +39,47 @@ You can now use all next commands:

### Commands

Save current localStorage values into an internal "snapshot":
##### `cy.saveLocalStorage()`

```js
cy.saveLocalStorage();
```
Saves current localStorage values into an internal "snapshot".

Restore localStorage to previously "snapshot" saved values:
##### `cy.restoreLocalStorage()`

```js
cy.restoreLocalStorage();
```
Restores localStorage to previously "snapshot" saved values.

Clear localStorage "snapshot" values:
##### `cy.clearLocalStorageSnapshot()`

```js
cy.clearLocalStorageSnapshot();
```
Clears localStorage "snapshot" values, so previously saved values are cleaned.

Get localStorage item. Equivalent to `localStorage.getItem` in browser:
##### `cy.getLocalStorage(item)`

```js
cy.getLocalStorage("item");
```
Gets localStorage item. Equivalent to `localStorage.getItem` in browser.

Set localStorage item. Equivalent to `localStorage.setItem` in browser:
* `item` _(String)_: Item to get from `localStorage`.

```js
cy.setLocalStorage("item", "value");
```
##### `cy.setLocalStorage(item, value)`

Remove localStorage item. Equivalent to `localStorage.removeItem` in browser:
Sets localStorage item. Equivalent to `localStorage.setItem` in browser.

```js
cy.removeLocalStorage("item");
```
* `item` _(String)_: Item to set value.
* `value` _(String)_: Value to be set.

##### `cy.removeLocalStorage(item)`

Removes localStorage item. Equivalent to `localStorage.removeItem` in browser.

* `item` _(String)_: Item to be removed.

##### `cy.disableLocalStorage(options)`

Disables localStorage. It produces localStorage methods to throw errors.

* `options` _(Object)_: Options to use when disabling `localStorage`.
* `withError` _(Error)_: If provided, invocations to `localStorage` methods will throw this error.

### Preserving local storage between tests

Use `saveLocalStorage` to save a snapshot of current `localStorage` at the end of one test, and use the `restoreLocalStorage` command to restore it at the beginning of another one. _The usage of `beforeEach` and `afterEach` is recommended for this purpose._
Use `cy.saveLocalStorage()` to save a snapshot of current `localStorage` at the end of one test, and use the `cy.restoreLocalStorage()` command to restore it at the beginning of another one. _The usage of `beforeEach` and `afterEach` is recommended for this purpose._

### Examples

Expand Down Expand Up @@ -116,7 +119,7 @@ describe("Accept cookies button", () => {
});
```

> Note the usage of `beforeEach` and `afterEach` for preserving `localStorage` between all tests. Also `clearLocalStorageSnapshot` is used in the `before` statement to avoid possible conflicts with other test files preserving localStorage.
> Note the usage of `beforeEach` and `afterEach` for preserving `localStorage` between all tests. Also `cy.clearLocalStorageSnapshot` is used in the `before` statement to avoid possible conflicts with other test files preserving localStorage.

#### localStorage assertions

Expand Down Expand Up @@ -150,6 +153,75 @@ describe("localStorage cookies-accepted item", () => {
});
```

### Disabling localStorage

Use `cy.disableLocalStorage()` to simulate that `localStorage` is disabled, producing that any invocation to `localStorage.setItem`, `localStorage.getItem`, `localStorage.removeItem` or `localStorage.clear` will throw an error. [As MDN docs recommend](https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem), _"developers should make sure to always catch possible exceptions from setItem()"_. This command allows to test that possible exceptions are handled correctly.

Note that:

* Only pages loaded after calling this command will have `localStorage` disabled, so always use `cy.reload` or `cy.visit` after executing it.
* The `localStorage` only remains disabled for all pages loaded during the current test. If you want to disable it for multiple tests, execute it in all of them, or in a `beforeEach` statement.
* If any of the other plugin commands (except `clearLocalStorageSnapshot`) is executed while `localStorage` is disabled, it will do nothing but producing a Cypress log as: _"localStorage.setItem is disabled"_

### Examples

#### Disabling localStorage in a single test

Based on previous "Accept cookies button" example, next tests could be added:

```js
//...
const LOCALSTORAGE_DISABLED_WARNING = "#localstorage-disabled-warning";
const LOCALSTORAGE_ERROR = "#localstorage-error";

//... should not be visible after clicked

it("should still be visible when reloading if localStorage is disabled", () => {
cy.disableLocalStorage();
cy.reload();
cy.get(COOKIES_BUTTON).should("be.visible");
});

it("should display warning if localStorage is disabled", () => {
cy.disableLocalStorage();
cy.reload();
cy.get(LOCALSTORAGE_DISABLED_WARNING).should("be.visible");
});

it("should display localStorage error message", () => {
cy.disableLocalStorage();
cy.reload();
cy.get(LOCALSTORAGE_ERROR).should("have.text", "Error");
});

// ...should not be visible after reloading
```

#### Disabling localStorage in multiple tests

```js
describe("when localStorage is disabled", () => {
beforeEach(() => {
cy.disableLocalStorage({
withError: new Error("Disabled by cypress-localstorage-commands"),
});
cy.visit("/");
});

it("should display localStorage warning", () => {
cy.get("#localstorage-disabled-warning").should("be.visible");
});

it("should display localStorage error message", () => {
cy.get("#localstorage-error").should("have.text", "Disabled by cypress-localstorage-commands");
});

it("should display accept-cookies button disabled", () => {
cy.get("#accept-cookies").should("be.disabled");
});
});
```

## Usage with TypeScript

For those writing [TypesScript tests in Cypress][cypress-typescript], this package includes TypeScript declarations.
Expand Down
9 changes: 9 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,14 @@ declare namespace Cypress {
* @example cy.removeLocalStorage("cookies-accepted")
*/
removeLocalStorage(itemKeyName: string): Chainable<undefined>

/**
* Command to disable localStorage
* @param {object} options - Options for disabling localStorage
* @example cy.disableLocalStorage()
*/
disableLocalStorage(options?: {
withError: Error
}): Chainable<undefined>
}
}
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* global Cypress, localStorage */
/* global Cypress, cy, localStorage */

const { register } = require("./src/register");

register(Cypress, localStorage);
register(Cypress, cy, localStorage);
6 changes: 3 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ module.exports = {
branches: 100,
functions: 100,
lines: 100,
statements: 100
}
statements: 100,
},
},

// The test environment that will be used for testing
testEnvironment: "node",

// The glob patterns Jest uses to detect test files
testMatch: ["**/test/**/*.spec.js"]
testMatch: ["**/test/**/*.spec.js"],
};
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cypress-localstorage-commands",
"version": "1.2.5",
"version": "1.3.0",
"description": "Extends Cypress' cy commands with localStorage methods. Allows preserving localStorage between tests",
"keywords": [
"cypress",
Expand All @@ -23,7 +23,7 @@
"main": "index.js",
"scripts": {
"lint:typescript": "cd test-e2e/typescript && npm run copy:library && npm run lint",
"lint:javascript": "eslint src test test-e2e/react-app/src test-e2e/react-app/cypress",
"lint:javascript": "eslint src test test-e2e/react-app/src test-e2e/react-app/cypress test-e2e/react-app/*.js *.js",
"lint:all": "npm run lint:javascript && npm run lint:typescript",
"lint": "npm run test:e2e:typescript:install && npm run lint:all",
"lint:local": "npm run lint:all",
Expand All @@ -48,7 +48,7 @@
"babel-eslint": "10.1.0",
"eslint": "7.14.0",
"eslint-config-prettier": "6.15.0",
"eslint-plugin-prettier": "3.1.4",
"eslint-plugin-prettier": "3.2.0",
"eslint-plugin-react": "7.21.5",
"husky": "4.3.0",
"jest": "26.6.3",
Expand All @@ -57,8 +57,10 @@
"sinon": "9.2.1"
},
"lint-staged": {
"*.js": "eslint",
"src/**/*.js": "eslint",
"test/**/*.js": "eslint",
"test-e2e/react-app/*.js": "eslint",
"test-e2e/react-app/src/**/*.js": "eslint",
"test-e2e/react-app/cypress/**/*.js": "eslint",
"test-e2e/typescript/**/*.js": "npm run lint:typescript",
Expand Down
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
sonar.organization=javierbrea
sonar.projectKey=cypress-localstorage-commands
sonar.projectVersion=1.2.5
sonar.projectVersion=1.3.0

sonar.javascript.file.suffixes=.js
sonar.sourceEncoding=UTF-8
Expand Down
46 changes: 41 additions & 5 deletions src/LocalStorage.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
const LOCAL_STORAGE_METHODS = ["setItem", "getItem", "removeItem", "clear"];

function logDisabled(method) {
return function () {
this._cy.log(`localStorage.${method} is disabled`);
};
}

function logDisabledMethodName(localStorageMethod) {
return `_log${localStorageMethod}Disabled`;
}

class LocalStorage {
static get cypressCommands() {
return [
Expand All @@ -7,11 +19,16 @@ class LocalStorage {
"setLocalStorage",
"getLocalStorage",
"removeLocalStorage",
"disableLocalStorage",
];
}

constructor(localStorage) {
constructor(localStorage, cy) {
this._cy = cy;
this._localStorage = localStorage;
LOCAL_STORAGE_METHODS.forEach((localStorageMethod) => {
this[logDisabledMethodName(localStorageMethod)] = logDisabled(localStorageMethod).bind(this);
});
this.clearLocalStorageSnapshot();
}

Expand All @@ -20,10 +37,12 @@ class LocalStorage {
}

saveLocalStorage() {
this.clearLocalStorageSnapshot();
Object.keys(this._localStorage).forEach((key) => {
this._snapshot[key] = this._localStorage.getItem(key);
});
if (!this._localStorage.getItem.wrappedMethod) {
this.clearLocalStorageSnapshot();
Object.keys(this._localStorage).forEach((key) => {
this._snapshot[key] = this._localStorage.getItem(key);
});
}
}

restoreLocalStorage() {
Expand All @@ -44,6 +63,23 @@ class LocalStorage {
removeLocalStorage(key) {
return this._localStorage.removeItem(key);
}

disableLocalStorage(options = {}) {
this._cy.on("window:before:load", (win) => {
if (
win.localStorage &&
!win.localStorage[LOCAL_STORAGE_METHODS[0]].wrappedMethod &&
!this._localStorage[LOCAL_STORAGE_METHODS[0]].wrappedMethod
) {
LOCAL_STORAGE_METHODS.forEach((localStorageMethod) => {
this._cy
.stub(this._localStorage, localStorageMethod)
.callsFake(this[logDisabledMethodName(localStorageMethod)]);
this._cy.stub(win.localStorage, localStorageMethod).throws(options.withError);
});
}
});
}
}

module.exports = LocalStorage;
6 changes: 4 additions & 2 deletions src/register.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const LocalStorage = require("./LocalStorage");

const register = (Cypress, localStorage) => {
const localStorageCommands = new LocalStorage(localStorage);
const register = (Cypress, cy, localStorage) => {
const localStorageCommands = new LocalStorage(localStorage, cy);

// Register commands
LocalStorage.cypressCommands.forEach((commandName) => {
Cypress.Commands.add(
commandName,
Expand Down
Loading