{"action":"created","issue":{"url":"https://api.github.com/repos/DevExpress/testcafe/issues/960","repository_url":"https://api.github.com/repos/DevExpress/testcafe","labels_url":"https://api.github.com/repos/DevExpress/testcafe/issues/960/labels{/name}","comments_url":"https://api.github.com/repos/DevExpress/testcafe/issues/960/comments","events_url":"https://api.github.com/repos/DevExpress/testcafe/issues/960/events","html_url":"https://github.com/DevExpress/testcafe/pull/960","id":188224167,"number":960,"title":"Redesign selector system (closes #798)","user":{"login":"inikulin","id":453071,"avatar_url":"https://avatars.githubusercontent.com/u/453071?v=3","gravatar_id":"","url":"https://api.github.com/users/inikulin","html_url":"https://github.com/inikulin","followers_url":"https://api.github.com/users/inikulin/followers","following_url":"https://api.github.com/users/inikulin/following{/other_user}","gists_url":"https://api.github.com/users/inikulin/gists{/gist_id}","starred_url":"https://api.github.com/users/inikulin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/inikulin/subscriptions","organizations_url":"https://api.github.com/users/inikulin/orgs","repos_url":"https://api.github.com/users/inikulin/repos","events_url":"https://api.github.com/users/inikulin/events{/privacy}","received_events_url":"https://api.github.com/users/inikulin/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":9,"created_at":"2016-11-09T11:24:56Z","updated_at":"2016-11-14T12:46:02Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/DevExpress/testcafe/pulls/960","html_url":"https://github.com/DevExpress/testcafe/pull/960","diff_url":"https://github.com/DevExpress/testcafe/pull/960.diff","patch_url":"https://github.com/DevExpress/testcafe/pull/960.patch"},"body":"# Feature summary\r\n\r\n## New selector methods\r\n\r\nAll methods are exposed both on selector and on selector promise, so you can apply it on selector with arguments, e.g.:\r\n```js\r\nconst withIdOrClass = Selector(val => document.getElementById(val) || document.getElemenstByClassName(val));\r\n\r\nwithIdOrClass('some').withText('Hey!');\r\n```\r\n\r\n- `.nth(index)` - creates selector for `nth` element in the matching set. Replacement for `index` option.\r\n\r\n- `.withText(text)`, `.withText(re)` - creates selector that filters matching set by given text. Replacement for `text` option.\r\n\r\n- `.find(cssSelector)`, `.find(filterFn)` - finds all descendants of all nodes in the matching set and filters them either by CSS selector or using `filterFn`. `filterFn` is a client function predicate that receives node.\r\n\r\n- `.parent()`, `.parent(idx)`, `.parent(cssSelector)`, `.parent(filterFn)` - finds all parents of all nodes in the matching set.  If filter specified, filters parents either by CSS selector, `index` (0 is closest) or using `filterFn`. `filterFn` is a client function predicate that receives node. .\r\n\r\n- `.child()`, `.child(idx)`, `.child(cssSelector)`, `.child(filterFn)` - finds all child **elements** (not nodes!) of all nodes in the matching set. If filter specified, filters children either by CSS selector, `index` or using `filterFn`. `filterFn` is a client function predicate that receives node. \r\n\r\n- `.sibling()`, `.sibling(idx)`, `.sibling(cssSelector)`, `.sibling(filterFn)` - finds all sibling **elements** (not nodes!) of all nodes in the matching set. If filter specified, filters siblings either by CSS selector, `index` or using `filterFn`. `filterFn` is a client function predicate that receives node. \r\n\r\nMethods works like filters and can be combined together, making flexible functional-style selector system.\r\n\r\n**Examples:**\r\n```js\r\nSelector('ul').find('label').parent('div.someClass')\r\n```\r\nFinds all `ul` elements on page. Then in each found `ul` element finds `label` elements. Then for each `label` element finds parent that matches  `div.someClass` selector. \r\n\r\n------\r\n\r\nLike in jQuery, if you request a property of the matching set or try evaluate a snapshot it returns values for the first element in the set:\r\n```js\r\n// Returns id of the first element in the set\r\nvar id = await Selector('ul').find('label').parent('div.someClass').id;\r\n\r\n// Returns snapshot for the first element in the set\r\nvar snapshot = await Selector('ul').find('label').parent('div.someClass')();\r\n```\r\n\r\n------\r\n\r\nHowever, you can obtain data for any element in the set by using `nth` filter:\r\n```js\r\n// Returns id of the third element in the set\r\nvar id = await Selector('ul').find('label').parent('div.someClass').nth(2).id;\r\n\r\n// Returns snapshot for the fourth element in the set\r\nvar snapshot = await Selector('ul').find('label').parent('div.someClass').nth(4)();\r\n```\r\n\r\n------\r\n\r\nNote that you can add text and index filters in the selector chain:\r\n```js\r\nSelector('.container').parent(1).nth(0).find('.someClass').withText('yo!').child('span');\r\n```\r\nFinds second parents (parent of parent) of `.container` elements. Peeks first element in the matching set. Then in that element finds elements that matches '.someClass' selector. Filter them by text `yo!`. And in each filtered element searches for a child with tag name `span`.\r\n\r\n------ \r\n\r\n## New selector properties \r\n\r\nLike for methods, properties are exposed on both selector and selector promise.\r\n\r\n- `.count` - returns the number of matching elements.\r\n- `.exists` - returns `true` if at least one matching element exists.\r\n\r\n**Examples:**\r\n```js\r\nexpect(await Selector('div').withText('42').count).eql(2);\r\nexpect(await Selector('.someClass').child(2).exists).to.be.true;\r\n```\r\n\r\n## Unawaited selector calls now allowed out of test context (breaking)\r\n\r\nPreviously selector call outside of text context thrown an error:\r\n```js\r\nconst selector = Selector(id => document.getElementById(id));\r\n\r\nselector('smthg'); // <-- throws\r\n\r\ntest ('Some test', async t=> {\r\n...\r\n});\r\n```\r\n\r\nNow it's not a case if selector is not awaited. It's useful when you need to build a page model outside the text context:\r\n```js\r\nconst selector = Selector(id => document.getElementById(id));\r\nconst selector2 = selector('smthg').find('smthelse'); // <-- doesn't throw anymore\r\n\r\ntest ('Some test', async t=> {\r\n...\r\n});\r\n```\r\n\r\nHowever, once you'll try to await snapshot or snapshot property values it will throw:\r\n```js\r\nconst selector = Selector(id => document.getElementById(id));\r\n\r\nasynct someFunc() {\r\n  return await selector('yo'); // Throws\r\n}\r\n\r\nsomeFunc();\r\n\r\ntest ('Some test', async t=> {\r\n...\r\n});\r\n```\r\n\r\n## Index now is not ignored if selector returns single node (breaking)\r\nPreviously if selector returned single node `index` was ignored:\r\n```js\r\nSelector(() => document.getElementById('hey'), { index: 2 } ); // returns element by id\r\n``` \r\n\r\nhowever it's not a case now:\r\n```js\r\nSelector(() => document.getElementById('hey') ).nth(2); // returns null\r\n``` \r\n\r\nThis leads to more consistent system then it used in chain:\r\n```js\r\nconst withId = Selector(() => document.getElementById('hey') ;\r\n\r\nfunction getSecondElementWithText(selector) {\r\n  return selector.nth(2).withText('hey');\r\n}\r\n\r\ngetSecondElementWithText(withId);\r\n```\r\n\r\n## Deprecated API (breaking)\r\n\r\nAll deprecated API will last at least for the next release. However, currently they will give deprecation warning when used.\r\n\r\n- `t.select` - Doesn't fit into new selector system, since it doesn't provide combination of selectors. Use selectors and property shorthands to obtain values:\r\n```js\r\nvar id = await t.select('.yo').id;\r\n\r\ncan be replaced with\r\n\r\nvar id = await Selector('.yo').id;\r\n``` \r\n- `selectorOptions.index` - Use `.nth()` instead.\r\n- `selectorOptions.text` - Use `.withText` instead.\r\n- `selectorOptions.dependencies` - The mechanism was vague from the start. It's not required anymore as a public API, since now we have a flexible selector mechanism, so there is no need to pass selectors around.   \r\n\r\n\\cc @VasilyStrelyaev @AlexanderMoskovkin @helen-dikareva "},"comment":{"url":"https://api.github.com/repos/DevExpress/testcafe/issues/comments/260326059","html_url":"https://github.com/DevExpress/testcafe/pull/960#issuecomment-260326059","issue_url":"https://api.github.com/repos/DevExpress/testcafe/issues/960","id":260326059,"user":{"login":"testcafe-build-bot","id":13555664,"avatar_url":"https://avatars.githubusercontent.com/u/13555664?v=3","gravatar_id":"","url":"https://api.github.com/users/testcafe-build-bot","html_url":"https://github.com/testcafe-build-bot","followers_url":"https://api.github.com/users/testcafe-build-bot/followers","following_url":"https://api.github.com/users/testcafe-build-bot/following{/other_user}","gists_url":"https://api.github.com/users/testcafe-build-bot/gists{/gist_id}","starred_url":"https://api.github.com/users/testcafe-build-bot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/testcafe-build-bot/subscriptions","organizations_url":"https://api.github.com/users/testcafe-build-bot/orgs","repos_url":"https://api.github.com/users/testcafe-build-bot/repos","events_url":"https://api.github.com/users/testcafe-build-bot/events{/privacy}","received_events_url":"https://api.github.com/users/testcafe-build-bot/received_events","type":"User","site_admin":false},"created_at":"2016-11-14T12:46:02Z","updated_at":"2016-11-14T12:46:02Z","body":":x:&nbsp;Tests for the commit c9d2dcce3a34f0c94e3231e8623d3f5e2b277a9b have failed. See details:\n\n* [:white_check_mark:&nbsp;continuous-integration/appveyor/branch](https://ci.appveyor.com/project/testcafe-build-bot/testcafe/build/%23327)\n\n* [:x:&nbsp;continuous-integration/travis-ci/push](https://travis-ci.org/testcafe-build-bot/testcafe/builds/175685001)."}}
{"actor":{"display_login":"testcafe-build-bot"}}