Skip to content

Commit

Permalink
Merge pull request #824 from gemini-testing/HERMIONE-1313.clear_sessi…
Browse files Browse the repository at this point in the history
…on_cmd

feat: add "clearSession" browser command
  • Loading branch information
DudaGod authored Jan 11, 2024
2 parents 496fae9 + 7114cd5 commit 53a7faa
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 2 deletions.
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,30 @@ Finally, run tests (be sure that you have already run `selenium-standalone start
node_modules/.bin/hermione
```

## Commands API

Since Hermione is based on [WebdriverIO v8](https://webdriver.io/docs/api/), all the commands provided by WebdriverIO are available in it. But Hermione also has her own commands.

### clearSession

Browser command that clears session state (deletes cookies, clears local and session storages). For example:

```js
it('', async ({ browser }) => {
await browser.url('https://github.com/gemini-testing/hermione');

(await browser.getCookies()).length; // 5
await browser.execute(() => localStorage.length); // 2
await browser.execute(() => sessionStorage.length); // 1

await browser.clearSession();

(await browser.getCookies()).length; // 0
await browser.execute(() => localStorage.length); // 0
await browser.execute(() => sessionStorage.length); // 0
});
```

## Tests API

### Arguments
Expand Down Expand Up @@ -1640,8 +1664,6 @@ Another command features:
// foo: 1
```



#### Test development in runtime

For quick test development without restarting the test or the browser, you can run the test in the terminal of IDE with enabled REPL mode:
Expand All @@ -1652,6 +1674,10 @@ npx hermione --repl-before-test --grep "foo" -b "chrome"

After that, you need to configure the hotkey in IDE to run the selected one or more lines of code in the terminal. As a result, each new written line can be sent to the terminal using a hotkey and due to this, you can write a test much faster.

Also, during the test development process, it may be necessary to execute commands in a clean environment (without side effects from already executed commands). You can achieve this with the following commands:
- [clearSession](#clearsession) - clears session state (deletes cookies, clears local and session storages). In some cases, the environment may contain side effects from already executed commands;
- [reloadSession](https://webdriver.io/docs/api/browser/reloadSession/) - creates a new session with a completely clean environment.

##### How to set up using VSCode

1. Open `Code` -> `Settings...` -> `Keyboard Shortcuts` and print `run selected text` to search input. After that, you can specify the desired key combination
Expand Down
27 changes: 27 additions & 0 deletions src/browser/commands/clearSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Browser } from "../types";
import logger from "../../utils/logger";

export = async (browser: Browser): Promise<void> => {
const { publicAPI: session } = browser;

const clearStorage = async (storageName: "localStorage" | "sessionStorage"): Promise<void> => {
try {
await session.execute(storageName => window[storageName].clear(), storageName);
} catch (e) {
const message = (e as Error).message || "";

if (message.startsWith(`Failed to read the '${storageName}' property from 'Window'`)) {
logger.warn(`Couldn't clear ${storageName}: ${message}`);
} else {
throw e;
}
}
};

session.addCommand("clearSession", async function (): Promise<void> {
await session.deleteAllCookies();

await clearStorage("localStorage");
await clearStorage("sessionStorage");
});
};
1 change: 1 addition & 0 deletions src/browser/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module.exports = [
"assert-view",
"clearSession",
"getConfig",
"getPuppeteer",
"setOrientation",
Expand Down
92 changes: 92 additions & 0 deletions test/src/browser/commands/clearSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as webdriverio from "webdriverio";
import sinon, { SinonStub } from "sinon";

import clientBridge from "src/browser/client-bridge";
import logger from "src/utils/logger";
import { mkExistingBrowser_ as mkBrowser_, mkSessionStub_ } from "../utils";

import type ExistingBrowser from "src/browser/existing-browser";

describe('"clearSession" command', () => {
const sandbox = sinon.sandbox.create();

const initBrowser_ = ({ browser = mkBrowser_(), session = mkSessionStub_() } = {}): Promise<ExistingBrowser> => {
(webdriverio.attach as SinonStub).resolves(session);

return browser.init({ sessionId: session.sessionId, sessionCaps: session.capabilities, sessionOpts: {} });
};

beforeEach(() => {
sandbox.stub(webdriverio, "attach");
sandbox.stub(clientBridge, "build").resolves();
sandbox.stub(logger, "warn");

global.window = {
localStorage: { clear: sinon.stub() } as unknown as Storage,
sessionStorage: { clear: sinon.stub() } as unknown as Storage,
} as unknown as Window & typeof globalThis;
});

afterEach(() => {
global.window = undefined as unknown as Window & typeof globalThis;
sandbox.restore();
});

it("should add command", async () => {
const session = mkSessionStub_();

await initBrowser_({ session });

assert.calledWith(session.addCommand, "clearSession", sinon.match.func);
});

it("should delete all cookies", async () => {
const session = mkSessionStub_();

await initBrowser_({ session });
await session.clearSession();

assert.calledOnce(session.deleteAllCookies);
});

(["localStorage", "sessionStorage"] as const).forEach(storageName => {
describe(storageName, () => {
it("should clear", async () => {
const session = mkSessionStub_();
session.execute.callsFake((cb: (storageName: string) => void, storageName: string) => cb(storageName));

await initBrowser_({ session });
await session.clearSession();

assert.calledOnce(global.window[storageName].clear as SinonStub);
});

it("should not throw is storage is not available on the page", async () => {
const err = new Error(
`Failed to read the '${storageName}' property from 'Window': Storage is disabled inside 'data:' URLs.`,
);
(global.window[storageName].clear as SinonStub).throws(err);

const session = mkSessionStub_();
session.execute.callsFake((cb: (storageName: string) => void, storageName: string) => cb(storageName));

await initBrowser_({ session });

await assert.isFulfilled(session.clearSession());
assert.calledOnceWith(logger.warn, `Couldn't clear ${storageName}: ${err.message}`);
});

it("should throw if clear storage fails with not handled error", async () => {
const err = new Error("o.O");
(global.window[storageName].clear as SinonStub).throws(err);

const session = mkSessionStub_();
session.execute.callsFake((cb: (storageName: string) => void, storageName: string) => cb(storageName));

await initBrowser_({ session });

await assert.isRejected(session.clearSession(), /o.O/);
});
});
});
});
2 changes: 2 additions & 0 deletions test/src/browser/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ exports.mkSessionStub_ = () => {
};

session.deleteSession = sinon.stub().named("end").resolves();
session.clearSession = sinon.stub().named("clearSession").resolves();
session.url = sinon.stub().named("url").resolves();
session.getUrl = sinon.stub().named("getUrl").resolves("");
session.execute = sinon.stub().named("execute").resolves();
Expand All @@ -112,6 +113,7 @@ exports.mkSessionStub_ = () => {
session.switchToFrame = sinon.stub().named("switchToFrame").resolves();
session.switchToParentFrame = sinon.stub().named("switchToParentFrame").resolves();
session.switchToRepl = sinon.stub().named("switchToRepl").resolves();
session.deleteAllCookies = sinon.stub().named("deleteAllCookies").resolves();

session.addCommand = sinon.stub().callsFake((name, command, isElement) => {
const target = isElement ? wdioElement : session;
Expand Down

0 comments on commit 53a7faa

Please sign in to comment.