codeceptjs
Advanced tools
Comparing version 1.2.0 to 1.2.1
@@ -0,1 +1,7 @@ | ||
## 1.2.1 | ||
* Fixed running `I.retry()` on multiple steps. | ||
* Fixed parallel execution wih chunks. | ||
* [Puppeteer] Fixed `grabNumberOfVisibleElements` to return `0` instead of throwing error if no elements are found. | ||
## 1.2.0 | ||
@@ -18,3 +24,3 @@ | ||
* [Parallel Execution](https://codecept.io/advanced/#chunks) by @sveneisenschmidt. Run tests in parallel specifying number of chunks: | ||
* [Parallel Execution](https://codecept.io/advanced/#parallel-execution) by @sveneisenschmidt. Run tests in parallel specifying number of chunks: | ||
@@ -21,0 +27,0 @@ ```js |
@@ -104,8 +104,67 @@ # Advanced Usage | ||
## Multiple Execution | ||
## Parallel Execution | ||
CodeceptJS can execute multiple suites in parallel. This is useful if you want to execute same tests but on different browsers and with different configurations or different tests on same browsers in parallel. Before using this feature you need to add `multiple` option to the config: | ||
CodeceptJS can be configured to run tests in parallel. | ||
When enabled, it collects all test files and executes them in parallel by the specified amount of chunks. Given we have five test scenarios (`a_test.js`,`b_test.js`,`c_test.js`,`d_test.js` and `e_test.js`), by setting `"chunks": 2` we tell the runner to run two suites in parallel. The first suite will run `a_test.js`,`b_test.js` and `c_test.js`, the second suite will run `d_test.js` and `e_test.js`. | ||
```js | ||
"multiple": { | ||
"parallel": { | ||
// Splits tests into 2 chunks | ||
"chunks": 2 | ||
} | ||
} | ||
``` | ||
To execute them use `run-multiple` command passing configured suite, which is `parallel` in this example: | ||
``` | ||
codeceptjs run-multiple parallel | ||
``` | ||
Grep and multiple browsers are supported. Passing more than one browser will multiply the amount of suites by the amount of browsers passed. The following example will lead to four parallel runs. | ||
```js | ||
"multiple": { | ||
// 2x chunks + 2x browsers = 4 | ||
"parallel": { | ||
// Splits tests into chunks | ||
"chunks": 2, | ||
// run all tests in chrome and firefox | ||
"browsers": ["chrome", "firefox"] | ||
}, | ||
} | ||
``` | ||
Passing a function will enable you to provide your own chunking algorithm. The first argument passed to you function is an array of all test files, if you enabled grep the test files passed are already filtered to match the grep pattern. | ||
```js | ||
"multiple": { | ||
"parallel": { | ||
// Splits tests into chunks by passing an anonymous function, | ||
// only execute first and last found test file | ||
"chunks": (files) => { | ||
return [ | ||
[ files[0] ], // chunk 1 | ||
[ files[files.length-1] ], // chunk 2 | ||
] | ||
}, | ||
// run all tests in chrome and firefox | ||
"browsers": ["chrome", "firefox"] | ||
}, | ||
} | ||
``` | ||
Note: Chunking will be most effective if you have many individual test files that contain only a small amount of scenarios. Otherwise the combined execution time of many scenarios or big scenarios in one single test file potentially lead to an uneven execution time. | ||
## Multiple Browsers Execution | ||
This is useful if you want to execute same tests but on different browsers and with different configurations or different tests on same browsers in parallel. | ||
```js | ||
"multiple": { | ||
"basic": { | ||
@@ -201,41 +260,2 @@ // run all tests in chrome and firefox | ||
### Chunks | ||
Chunking is a way to execute multiple tests in parallel. The default chunking collects all test files and executes them in parallel by the specified amount of chunks. Given we have five test scenarios (`a_test.js`,`b_test.js`,`c_test.js`,`d_test.js` and `e_test.js`), by setting `"chunks": 2` we tell the runner to run two suites in parallel. The first suite will run `a_test.js`,`b_test.js` and `c_test.js`, the second suite will run `d_test.js` and `e_test.js`. | ||
Grep and multiple browsers are supported. Passing more than one browser will multiply the amount of suites by the amount of browsers passed. The following example will lead to four parallel runs. | ||
```js | ||
"multiple": { | ||
// 2x chunks + 2x browsers = 4 | ||
"parallel": { | ||
// Splits tests into chunks | ||
"chunks": 2, | ||
// run all tests in chrome and firefox | ||
"browsers": ["chrome", "firefox"] | ||
}, | ||
} | ||
``` | ||
Passing a function will enable you to provide your own chunking algorithm. The first argument passed to you function is an array of all test files, if you enabled grep the test files passed are already filtered to match the grep pattern. | ||
```js | ||
"multiple": { | ||
"parallel": { | ||
// Splits tests into chunks by passing an anonymous function, | ||
// only execute first and last found test file | ||
"chunks": (files) => { | ||
return [ | ||
[ files[0] ], // chunk 1 | ||
[ files[files.length-1] ], // chunk 2 | ||
] | ||
}, | ||
// run all tests in chrome and firefox | ||
"browsers": ["chrome", "firefox"] | ||
}, | ||
} | ||
``` | ||
Note: Chunking will be most effective if you have many individual test files that contain only a small amount of scenarios. Otherwise the combined execution time of many scenarios or big scenarios in one single test file potentially lead to an uneven execution time. | ||
## Dynamic Configuration | ||
@@ -242,0 +262,0 @@ |
@@ -5,7 +5,38 @@ # Locators | ||
* CSS and XPath locators | ||
* semantic locators: by link text, by button text, by field names, etc. | ||
* locator builder | ||
* ID locators: by CSS id or by accessibility id | ||
* [CSS and XPath locators](#css-and-xpath) | ||
* [Semantic locators](#semantic-locators): by link text, by button text, by field names, etc. | ||
* [Locator Builder](#locator-builder) | ||
* [ID locators](#id-locators): by CSS id or by accessibility id | ||
Most methods in CodeceptJS use locators which can be either a string or an object. | ||
If the locator is an object, it should have a single element, with the key signifying the locator type (`id`, `name`, `css`, `xpath`, `link`, or `class`) and the value being the locator itself. This is called a "strict" locator. | ||
Examples: | ||
* {id: 'foo'} matches `<div id="foo">` | ||
* {name: 'foo'} matches `<div name="foo">` | ||
* {css: 'input[type=input][value=foo]'} matches `<input type="input" value="foo">` | ||
* {xpath: "//input[@type='submit'][contains(@value, 'foo')]"} matches `<input type="submit" value="foobar">` | ||
* {class: 'foo'} matches `<div class="foo">` | ||
Writing good locators can be tricky. | ||
The Mozilla team has written an excellent guide titled [Writing reliable locators for Selenium and WebDriver tests](https://blog.mozilla.org/webqa/2013/09/26/writing-reliable-locators-for-selenium-and-webdriver-tests/). | ||
If you prefer, you may also pass a string for the locator. This is called a "fuzzy" locator. | ||
In this case, CodeceptJS uses a variety of heuristics (depending on the exact method called) to determine what element you're referring to. If you are locating a clickable element or an input element, CodeceptJS will use [semantic locators](#semantic-locators). | ||
For example, here's the heuristic used for the `fillField` method: | ||
1. Does the locator look like an ID selector (e.g. "#foo")? If so, try to find an input element matching that ID. | ||
2. If nothing found, check if locator looks like a CSS selector. If so, run it. | ||
3. If nothing found, check if locator looks like an XPath expression. If so, run it. | ||
4. If nothing found, check if there is an input element with a corresponding name. | ||
5. If nothing found, check if there is a label with specified text for input element. | ||
6. If nothing found, throw an `ElementNotFound` exception. | ||
Be warned that fuzzy locators can be significantly slower than strict locators. | ||
If speed is a concern, it's recommended you stick with explicitly specifying the locator type via object syntax. | ||
## CSS and XPath | ||
@@ -30,3 +61,3 @@ | ||
// it's not clear that 'descendant::table/tr' is actual CSS locator | ||
// it's not clear that 'descendant::table/tr' is actual XPath locator | ||
I.seeElement({ xpath: 'descendant::table/tr' }); | ||
@@ -52,3 +83,3 @@ ``` | ||
To locate `a` element inside `label` with test: 'Hello' use: | ||
To locate `a` element inside `label` with text: 'Hello' use: | ||
@@ -178,2 +209,4 @@ ```js | ||
* `#user` or `{ id: 'user' }` finds element with id="user" | ||
* `~user` finds element with accessibility id "user" (in Mobile testing) or with `aria-label=user`. | ||
* `~user` finds element with accessibility id "user" (in Mobile testing) or with `aria-label=user`. | ||
### done() |
@@ -27,3 +27,3 @@ # Robust Chrome Testing with Puppeteer | ||
```bash | ||
npm install -g codeceptjs-puppeteer | ||
npm install -g codeceptjs puppeteer | ||
``` | ||
@@ -30,0 +30,0 @@ |
@@ -97,3 +97,6 @@ const fsPath = require('path'); | ||
glob.sync(pattern, options).forEach((file) => { | ||
this.testFiles.push(fsPath.resolve(fsPath.join(global.codecept_dir, file))); | ||
if (!fsPath.isAbsolute(file)) { | ||
file = fsPath.join(global.codecept_dir, file); | ||
} | ||
this.testFiles.push(fsPath.resolve(file)); | ||
}); | ||
@@ -100,0 +103,0 @@ } |
@@ -57,2 +57,7 @@ const getConfig = require('./utils').getConfig; | ||
if (options.config) { // update paths to config path | ||
config.tests = path.resolve(testRoot, config.tests); | ||
} | ||
const childProcessesPromise = new Promise((resolve, reject) => { | ||
@@ -69,2 +74,6 @@ processesDone = resolve; | ||
if (!runsToExecute.length) { | ||
fail('Nothing scheduled for execution'); | ||
} | ||
// Execute all forks | ||
@@ -71,0 +80,0 @@ totalSubprocessCount = runsToExecute.length; |
@@ -101,2 +101,11 @@ const { createChunks } = require('./chunk'); | ||
if (!Array.isArray(runConfig.browsers)) { | ||
runConfig.browsers = guessBrowser(config) || []; | ||
} | ||
// no browser property in helper? | ||
if (!runConfig.browsers.length) { | ||
runConfig.browsers.push('default'); | ||
} | ||
runConfig.browsers.forEach((browser) => { | ||
@@ -157,4 +166,13 @@ const browserConfig = browser.browser ? browser : { browser }; | ||
function guessBrowser(config) { | ||
const firstHelper = Object.keys(config.helpers)[0]; | ||
if (!firstHelper) throw new Error('No helpers declared!'); | ||
if (!config.helpers[firstHelper].browser) { | ||
return []; | ||
} | ||
return [config.helpers[firstHelper].browser]; | ||
} | ||
module.exports = { | ||
createRuns, | ||
}; |
@@ -5,3 +5,2 @@ const getConfig = require('./utils').getConfig; | ||
const fileExists = require('../utils').fileExists; | ||
const detectAbsolutePath = require('../utils').detectAbsolutePath; | ||
const path = require('path'); | ||
@@ -27,3 +26,3 @@ const mkdirp = require('mkdirp'); | ||
if (detectAbsolutePath(config.output)) outputDir = config.output; | ||
if (path.isAbsolute(config.output)) outputDir = config.output; | ||
else outputDir = path.join(testRoot, config.output); | ||
@@ -30,0 +29,0 @@ |
@@ -123,3 +123,3 @@ const requireg = require('requireg'); | ||
} catch (e) { | ||
return ['puppeteer@^1.0.0']; | ||
return ['puppeteer@^1.3.0']; | ||
} | ||
@@ -903,11 +903,6 @@ } | ||
/** | ||
* Grab number of visible elements by locator | ||
* | ||
* ```js | ||
* I.grabNumberOfVisibleElements('p'); | ||
* ``` | ||
* * {{> ../webapi/grabNumberOfVisibleElements }} | ||
*/ | ||
async grabNumberOfVisibleElements(locator) { | ||
let els = await this._locate(locator); | ||
assertElementExists(els, locator); | ||
els = await Promise.all(els.map(el => el.boundingBox())); | ||
@@ -914,0 +909,0 @@ return els.filter(v => v).length; |
@@ -21,3 +21,2 @@ const promiseRetry = require('promise-retry'); | ||
/** | ||
@@ -28,2 +27,5 @@ * Singleton object to record all test steps as promises and run them in chain. | ||
/** | ||
* @var retries Array[object] | ||
*/ | ||
retries: [], | ||
@@ -128,2 +130,3 @@ | ||
return promise = Promise.resolve(promise).then((res) => { | ||
@@ -138,5 +141,7 @@ const retryOpts = this.retries.slice(-1).pop(); | ||
if (number > 1) log(`${currentQueue()}Retrying... Attempt #${number}`); | ||
const retryRules = this.retries.reverse(); | ||
return Promise.resolve(res).then(fn).catch((err) => { | ||
// does retry handled or should be thrown | ||
for (const retryObj in this.retries.reverse()) { | ||
for (const retryObj of retryRules) { | ||
if (!retryObj.when) return retry(err); | ||
@@ -160,3 +165,3 @@ if (retryObj.when && retryObj.when(err)) return retry(err); | ||
} | ||
return promise.then(() => this.retries.push(opts)); | ||
return this.add(() => this.retries.push(opts)); | ||
}, | ||
@@ -163,0 +168,0 @@ |
@@ -55,8 +55,2 @@ const fs = require('fs'); | ||
module.exports.detectAbsolutePath = function (filePath) { | ||
if (filePath.startsWith('/') || filePath.startsWith('\\')) return true; | ||
if (filePath.match(/^\w\:\\/)) return true; // Windows style | ||
return false; | ||
}; | ||
module.exports.isFile = function (filePath) { | ||
@@ -63,0 +57,0 @@ let filestat; |
{ | ||
"name": "codeceptjs", | ||
"version": "1.2.0", | ||
"version": "1.2.1", | ||
"description": "Modern Era Acceptance Testing Framework for NodeJS", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1100675
198
22946