Skip to content

Commit

Permalink
Fix remarks
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreyBelym committed Apr 25, 2017
1 parent 48012e5 commit d72f248
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Expand Up @@ -24,7 +24,7 @@
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-proto": "error",
"no-return-assign": "error",
"no-return-assign": "off",
"no-script-url": "error",
"no-sequences": "error",
"no-shadow": "error",
Expand Down
33 changes: 30 additions & 3 deletions README.md
Expand Up @@ -37,13 +37,40 @@ testCafe
## Config
Below is complete list of all configuration options that you can put into `.testcafe-electron-rc`.

- `mainWindowUrl` __(mandatory)__ - specifies URL used for the main window page of the appplication.
- `mainWindowUrl` __(required)__ - specifies URL used for the main window page of the appplication.
If you use `file://` urls, you can also specify a relative (to the application directory) or an absolute path to the file of the page.
- `appPath` __(optional)__ - alters path to the application. By default, application path is the part after `electron:`
of the string used in TestCafe CLI or API. You can override it by specifying an absolute path, or append a relative path from 'appPath'.
- `appArgs` __(optional)__ - overrides application commandline arguments with the values specified in this option. It should be an array or an object with numeric keys.
- `disableNavigateEvents` __(optional)__ - if you use `did-navigate` ow `will-navigate` webContent events to prevent navigation, you should disable it by setting this option to `true`.
- `openDevTools` __(optional)__ - if `true`, DevTools will be opened just before tests start
.
- `openDevTools` __(optional)__ - if `true`, DevTools will be opened just before tests start.

## Helpers
You can use some helper functions from provider in your test files. Use ES6 import statement to get them, like
```js
import { getMainMenu, clickOnMenuItem } from 'testcafe-browser-provider-electron';
```
- `async function getMenuItem (menuItemSelector)` - get a snapshot of the given menu item. `menuItemSelector` is a string that consists
of menu type and menu item labels, separated by the `>` sign, e.g. `Main Menu > File > Open` or `Context Menu > Undo`.
The `Main Menu` menu type can be skipped. If there are a number of the specified menu items with the same label on the same level,
you can specify a one-based index in square brackets, e.g. `Main Menu > Window > My Window [2]` selects the second menu item with
label `My Window` in the `Window` menu. Check properties available in the snapshot [here](https://github.com/electron/electron/blob/master/docs/api/menu-item.md).

- `async function getMainMenu ()` - get a snapshot of application main menu. You can check properties available in the snapshot
[here](https://github.com/electron/electron/blob/master/docs/api/menu.md).

- `async function getContextMenu ()` - get a snapshot of context menu. You can check properties available in the snapshot
[here](https://github.com/electron/electron/blob/master/docs/api/menu.md),

- `async function clickOnMenuItem (menuItem, modifiers)` - perform a click on the given `menuItem`. It can be a string,
in this case it will be passed to the `getMenuItem` function and the returned value will be used; or a value retrieved
with `getMenuItem`, `getMainMenu`, `getContextMenu` functions.
Also you can pass state of control keys (`Ctrl`, `Alt`, `Meta` etc.) in the `modifiers` argument, e.g. the default is
`{ shift: false, ctrl: false, alt: false, meta: false}`. Examples: `clickOnMenuItem('Main Menu > File > Open')`,
`clickOnMenuItem('File > Open')`, `clickOnMenuItem((await getMainMenu()).items[0].submenu.items[0])`,

- `async function setElectronDialogHandler (handler, dependencies)` - set a function `function handler (type, ...args)` that will handle native Electron dialogs. Specify global variables of the function in the `dependencies` argument.
Handler function must be synchronous and will be invoked with the dialog type `type`, and the arguments `args` from the original dialog function.

## Author
Developer Express Inc. (https://devexpress.com)
4 changes: 2 additions & 2 deletions package.json
@@ -1,7 +1,7 @@
{
"name": "testcafe-browser-provider-electron",
"version": "0.0.1",
"description": "electron TestCafe browser provider plugin.",
"description": "TestCafe browser provider plugin for testing applications built with Electron.",
"repository": "https://github.com/DevExpress/testcafe-browser-provider-electron",
"homepage": "https://github.com/DevExpress/testcafe-browser-provider-electron",
"author": {
Expand Down Expand Up @@ -51,7 +51,7 @@
"gulp-eslint": "^3.0.1",
"node-version": "^1.0.0",
"publish-please": "^2.1.4",
"testcafe": "alpha",
"testcafe": "*",
"tmp": "0.0.28"
}
}
71 changes: 62 additions & 9 deletions src/index.js
Expand Up @@ -14,6 +14,17 @@ import { ClientFunction } from 'testcafe';

const exec = promisify(nodeExec, Promise);

const simplifyMenuItemLabel = label => label.replace(/\s/g, '').toLowerCase();

const MENU_ITEM_INDEX_RE = /\[(\d+)\]$/;

const MODIFIERS_KEYS_MAP = {
'shift': 'shiftKey',
'ctrl': 'ctrlKey',
'alt': 'altKey',
'meta': 'metaKey'
};

/* eslint-disable no-undef */
const getMainMenu = ClientFunction(() => {
return require('electron').remote.Menu.getApplicationMenu();
Expand All @@ -23,11 +34,11 @@ const getContextMenu = ClientFunction(() => {
return require('electron').remote.getGlobal(contextMenuGlobal);
}, { dependencies: CONSTANTS });

const doMenuClick = ClientFunction((menuSnapshot, modifiers) => {
const doClickOnMenuItem = ClientFunction((menuType, menuItemIndex, modifiers) => {
var remote = require('electron').remote;
var menu = null;
var menu = null;

switch (menuSnapshot[typeProperty]) {
switch (menuType) {
case mainMenuType:
menu = remote.Menu.getApplicationMenu();
break;
Expand All @@ -40,7 +51,7 @@ const doMenuClick = ClientFunction((menuSnapshot, modifiers) => {
if (!menu)
return;

var menuItem = menuSnapshot[indexProperty]
var menuItem = menuItemIndex
.reduce((m, i) => m.items[i].submenu || m.items[i], menu);

menuItem.click(menuItem, require('electron').remote.getCurrentWindow(), modifiers);
Expand Down Expand Up @@ -80,10 +91,35 @@ function wrapMenu (type, menu, index = []) {
if (item.submenu)
wrapMenu(type, item.submenu, currentIndex);
}

return menu;
}

export default {
function findMenuItem (menu, menuItemPath) {
var menuItem = null;

for (let i = 0; menu && i < menuItemPath.length; i++) {
const indexMatch = menuItemPath[i].match(MENU_ITEM_INDEX_RE);
const index = indexMatch ? Number(indexMatch[1]) - 1 : 0;
const label = indexMatch ? menuItemPath[i].replace(MENU_ITEM_INDEX_RE, '') : menuItemPath[i];

menuItem = menu.items.filter(item => simplifyMenuItemLabel(item.label) === label)[index];

menu = menuItem && menuItem.submenu || null;
}

return menuItem || null;
}

function ensureModifiers (srcModifiers = {}) {
var result = {};

Object.keys(MODIFIERS_KEYS_MAP).forEach(mod => result[MODIFIERS_KEYS_MAP[mod]] = !!srcModifiers[mod]);

return result;
}

const ElectronBrowserProvider = {
isMultiBrowser: true,

async openBrowser (id, pageUrl, mainPath) {
Expand Down Expand Up @@ -120,22 +156,39 @@ export default {
},

//Helpers
async mainMenu () {
async getMainMenu () {
return wrapMenu(CONSTANTS.mainMenuType, await getMainMenu());
},

async contextMenu () {
async getContextMenu () {
return wrapMenu(CONSTANTS.contextMenuType, await getContextMenu());
},

async menuClick (menuSnapshot, modifiers = {}) {
await doMenuClick(menuSnapshot, modifiers);
async clickOnMenuItem (menuItem, modifiers = {}) {
var menuItemSnapshot = typeof menuItem === 'string' ? await ElectronBrowserProvider.getMenuItem(menuItem) : menuItem;

if (!menuItemSnapshot)
throw new Error('Invalid menu item argument');

await doClickOnMenuItem(menuItemSnapshot[CONSTANTS.typeProperty], menuItemSnapshot[CONSTANTS.indexProperty], ensureModifiers(modifiers));
},

async setElectronDialogHandler (fn, context) {
await doSetElectronDialogHandler({
fn: fn.toString(),
ctx: context
});
},

async getMenuItem (menuItemSelector) {
var menuItemPath = menuItemSelector.split(/\s*>\s*/).map(simplifyMenuItemLabel);
var menu = menuItemPath[0] === 'contextmenu' ? await ElectronBrowserProvider.getContextMenu() : await ElectronBrowserProvider.getMainMenu();

if (menuItemPath[0] === 'contextmenu' || menuItemPath[0] === 'mainmenu')
menuItemPath.shift();

return findMenuItem(menu, menuItemPath);
}
};

export { ElectronBrowserProvider as default };
6 changes: 2 additions & 4 deletions test/fixtures/dialog-test.js
@@ -1,6 +1,6 @@
import { testPage } from '../config';
import { ClientFunction } from 'testcafe';
import { mainMenu, setElectronDialogHandler, menuClick } from 'testcafe-browser-provider-electron';
import { setElectronDialogHandler, clickOnMenuItem } from 'testcafe-browser-provider-electron';


fixture `Dialog`
Expand All @@ -9,11 +9,9 @@ fixture `Dialog`
const checkDialogHandled = ClientFunction(() => window.dialogResult);

test('Should handle Open Dialog', async t => {
var menu = await mainMenu();

await setElectronDialogHandler(type => type + ' handled');

await menuClick(menu.items[0].submenu.items[1]);
await clickOnMenuItem('Test > Dialog');

await t.expect(checkDialogHandled()).eql('open-dialog handled');
});
10 changes: 3 additions & 7 deletions test/fixtures/menu-test.js
@@ -1,6 +1,6 @@
import { testPage } from '../config';
import { ClientFunction } from 'testcafe';
import { mainMenu, contextMenu, menuClick } from 'testcafe-browser-provider-electron';
import { clickOnMenuItem } from 'testcafe-browser-provider-electron';


fixture `Menu`
Expand All @@ -10,19 +10,15 @@ const checkMainMenuClicked = ClientFunction(() => window.mainMenuClicked);
const checkContextMenuClicked = ClientFunction(() => window.contextMenuClicked);

test('Should click on main menu', async t => {
var menu = await mainMenu();

await menuClick(menu.items[0].submenu.items[0]);
await clickOnMenuItem('Main menu > Test > Click');

await t.expect(checkMainMenuClicked()).ok();
});

test('Should click on context menu', async t => {
await t.rightClick('body');

var menu = await contextMenu();

await menuClick(menu.items[0]);
await clickOnMenuItem('Context Menu > Test');

await t.expect(checkContextMenuClicked()).ok();
});

0 comments on commit d72f248

Please sign in to comment.