UNPKG

cybernaut

Version:

Reliable, zero configuration end-to-end testing in BDD-style.

1,423 lines (950 loc) 35.8 kB
# Cybernaut [![npm][npm-badge]][npm] [![build][travis-ci-badge]][travis-ci] [![coverage][coveralls-badge]][coveralls] [![semantic-release][semantic-release-badge]][semantic-release] [![Greenkeeper][greenkeeper-badge]][greenkeeper] [![TypeScript][typescript-badge]][typescript] Reliable, zero configuration end-to-end testing in BDD-style. [![Example][example-png]][example] ```js const {browser, defineElement, it, test} = require('cybernaut'); test('Star the "clebert/cybernaut" repository on GitHub', async t => { await t.perform(browser.loadPage('https://github.com/clebert/cybernaut')); await t.assert(browser.pageTitle, it.should.contain('clebert/cybernaut')); await t.perform(browser.takeScreenshot()); const switchToDesktopButton = defineElement('button.switch-to-desktop'); // When on the mobile version, then switch to the desktop version if (await t.verify(switchToDesktopButton.visibility, it.should.equal(true))) { await t.perform(switchToDesktopButton.click()); } const starButton = defineElement( 'ul.pagehead-actions > li:nth-child(2) > a:nth-child(1)' ); // The star button leads to a login form, so the project is not really starred await t.perform(starButton.click()); }); ``` The above [example][example] can be executed in a [Docker][docker] container, on Chrome: ```sh git clone https://github.com/clebert/cybernaut.git && cd cybernaut && \ ./example/docker-build.sh chrome && ./example/docker-run.sh chrome ``` on Firefox: ```sh git clone https://github.com/clebert/cybernaut.git && cd cybernaut && \ ./example/docker-build.sh firefox && ./example/docker-run.sh firefox ``` on [iPhone 6 Plus][emulating-mobile-devices-in-chrome]: ```sh git clone https://github.com/clebert/cybernaut.git && cd cybernaut && \ ./example/docker-build.sh iphone && ./example/docker-run.sh iphone ``` The captured screenshots can be found in the `./example/screenshots/` directory. ## Contents * [Installation](#installation) * [Usage](#usage) * [Starting Cybernaut](#starting-cybernaut) * [Configuring Cybernaut](#configuring-cybernaut) * [Testing with Docker](#testing-with-docker) * [Emulating mobile devices in Chrome](#emulating-mobile-devices-in-chrome) * [Writing end-to-end tests](#writing-end-to-end-tests) * [API](#api) * [Related links](#related-links) * [Development](#development) ## [Installation](#contents) ```sh npm install --save-dev cybernaut ``` If the default configuration is used, Chrome and a matching version of [`chromedriver`][node-chromedriver] must also be installed: ```sh npm install --save-dev chromedriver ``` *Note: It is recommended to [run your end-to-end tests with Docker](#testing-with-docker).* ## [Usage](#contents) ### [Starting Cybernaut](#usage) Cybernaut must be started from the command line: ```sh $(npm bin)/cybernaut ``` Directories are recursed, with all `**/*.e2e.js` files being treated as test files. Cybernaut produces output in [TAP][tap] format, [`tap-mocha-reporter`][tap-mocha-reporter] can be used to format it: ```sh npm install --save-dev tap-mocha-reporter ``` ```sh $(npm bin)/cybernaut | $(npm bin)/tap-mocha-reporter spec ``` *Note: You can set the `DEBUG=cybernaut:*` environment variable to enable debug output.* ### [Configuring Cybernaut](#usage) The following configuration is active by default: ```json { "capabilities": {"browserName": "chrome"}, "concurrency": 1, "dependencies": ["chromedriver"], "exclude": ["**/node_modules/**/*"], "include": "**/*.e2e.js", "retries": 4, "retryDelay": 500, "screenshotDirectory": "screenshots", "timeouts": {"element": 0, "page": 30000, "script": 30000} } ``` Configuration options: * `capabilities`: Specifies the desired [WebDriver capabilities][selenium-desired-capabilities]. * `concurrency`: Specifies the maximum number of end-to-end tests running at the same time. * `dependencies`: Specifies additional modules to be loaded. * `exclude`: Specifies the [glob patterns][node-glob], for which matching files will be removed from the set of test files. * `include`: Specifies the [glob pattern][node-glob], for which matching files will be added to the set of test files. * `retries`: Specifies the maximum number of retries of failed [test steps](#assert). * `retryDelay`: Specifies the time, in milliseconds, to wait between retries of failed [test steps](#assert). * `screenshotDirectory`: Specifies the relative or absolute path to the screenshot directory. * `timeouts.element`: Specifies the maximum time, in milliseconds, to wait when searching for an element, that is not immediately present, before returning an error. * `timeouts.page`: Specifies the maximum time, in milliseconds, to wait for a page load to complete before returning an error. * `timeouts.script`: Specifies the maximum time, in milliseconds, for an asynchronous script to finish execution before returning an error. A separate configuration can be passed as a command line argument: ```sh $(npm bin)/cybernaut config.json ``` Such a configuration can be validated with [this JSON schema][config-schema] and written as a JSON file: ```json { "capabilities": {"browserName": "firefox"}, "dependencies": ["geckodriver"] } ``` or JavaScript module: ```js module.exports = { capabilities: {browserName: 'firefox'}, dependencies: ['geckodriver'] }; ``` *Note: Cybernaut uses [`selenium-webdriver@3.3.0`][selenium], which is incompatible with [`geckodriver@1.5.0`][node-geckodriver]. Until these incompatibilities have been solved, [`geckodriver@1.4.0`][node-geckodriver] must be used.* ### [Testing with Docker](#usage) End-to-end tests written with Cybernaut can be run in a Docker container. This has the advantage of being able to run them independently of the environment and under reproducible conditions. *Note: The included [examples][example] can serve as a reference implementation.* Cybernaut brings two fully configured Docker containers, which can be found on [Docker Hub][docker-hub-clebert]. One allows testing on Chrome: ```dockerfile FROM clebert/cybernaut-chrome:2.4.5 ``` the other on Firefox: ```dockerfile FROM clebert/cybernaut-firefox:2.4.5 ``` You can find a list of available tags for `cybernaut-chrome` [here][docker-hub-chrome-tags] and for `cybernaut-firefox` [here][docker-hub-firefox-tags]. Each Docker tag corresponds to the same tag/version of [Cybernaut on npm][npm]. The test files must be copied into the `/opt/e2e-test/` directory inside the Docker container: ```dockerfile COPY example.e2e.js /opt/e2e-test/example.e2e.js ``` The default configuration can be overridden with the following Docker instruction: ```dockerfile COPY config.json /opt/config.json ``` Chrome default configuration: ```json { "capabilities": { "browserName": "chrome", "chromeOptions": { "args": [ "--disable-gpu", "--no-sandbox" ] } } } ``` Firefox default configuration: ```json { "capabilities": { "browserName": "firefox" }, "dependencies": [ "geckodriver" ] } ``` In addition, a default `CMD` instruction is configured to specify the virtual screen resolution and the reporter: ```dockerfile CMD ["1280x720", "spec"] ``` You can override it with your own `CMD` instruction or with CLI arguments for `docker run`: ```sh docker run -ti --rm -v /dev/shm:/dev/shm clebert/cybernaut-chrome-example 1920x1080 dot ``` In order to get access to the captured screenshots, a local screenshots directory can be [mounted][docker-mount] into the `/opt/e2e-test/` directory inside the Docker container: ```sh docker run -ti --rm -v $(cd example/screenshots; pwd):/opt/e2e-test/screenshots -v /dev/shm:/dev/shm clebert/cybernaut-chrome-example ``` *Note: When executing docker run for an image with chrome browser please add `-v /dev/shm:/dev/shm` [volume mount][docker-mount] to use the host's shared memory. Since a Docker container is not meant to preserve state and spawning a new one takes less than 3 seconds you will likely want to remove containers after each end-to-end test with `--rm` command.* ### [Emulating mobile devices in Chrome](#usage) [ChromeDriver][chromedriver] allows developers to emulate Chrome on a mobile device, by enabling the [Mobile Emulation][mobile-emulation] feature via the `mobileEmulation` capability. This feature speeds up web development, allows developers to quickly test how a website will render on a mobile device, without requiring a real device. There are two ways in [ChromeDriver][chromedriver] to enable [Mobile Emulation][mobile-emulation]: by specifying a known device, or by specifying individual device attributes. The format of the `mobileEmulation` dictionary depends on which method is desired. #### Specifying a known mobile device To enable [Mobile Emulation][mobile-emulation] with a specific device name, the `mobileEmulation` dictionary must contain a `deviceName`. Use a valid device name from the DevTools Emulation panel as the value for `deviceName`: ```json { "capabilities": { "browserName": "chrome", "chromeOptions": { "mobileEmulation": { "deviceName": "Apple iPhone 6 Plus" } } } } ``` #### Specifying individual device attributes It is also possible to enable [Mobile Emulation][mobile-emulation] by specifying individual attributes. To enable [Mobile Emulation][mobile-emulation] this way, the `mobileEmulation` dictionary can contain a `deviceMetrics` dictionary and a `userAgent` string. The following device metrics must be specified in the `deviceMetrics` dictionary: * `width` - the width in pixels of the device's screen * `height` - the height in pixels of the device's screen * `pixelRatio` - the device's pixel ratio * `touch` - whether to emulate touch events (defaults to true, usually does not need to be set) ```json { "capabilities": { "browserName": "chrome", "chromeOptions": { "mobileEmulation": { "deviceMetrics": { "width": 360, "height": 640, "pixelRatio": 3.0 }, "userAgent": "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19" } } } } ``` ### [Writing end-to-end tests](#usage) It is recommended to write end-to-end tests using [async functions][mdn-async], which are natively supported by [Node.js][nodejs] as of version 7. Alternatively, the test files must be transpiled using [TypeScript][typescript] or [Babel][babel]. If you write your end-to-end tests with [TypeScript][typescript], it is recommended to enable the [`no-floating-promises`][tslint-rule-no-floating-promises] [TSLint][tslint] rule. This can prevent the [`await`][mdn-await] operators from being forgotten. ## [API](#contents) * [Module `exports`](#module-exports) * [`test`](#test) * [`skip`](#skip) * [`browser`](#browser) * [`defineElement`](#defineelement) * [`it`](#it) * [Interface `Test`](#interface-test) * [`assert`](#assert) * [`perform`](#perform) * [`verify`](#verify) * [`fail`](#fail) * [`pass`](#pass) * [Interface `Browser`](#interface-browser) * [`pageTitle`](#pagetitle) * [`pageUrl`](#pageurl) * [`windowX`](#windowx) * [`windowY`](#windowy) * [`windowWidth`](#windowwidth) * [`windowHeight`](#windowheight) * [`scriptResult`](#scriptresult) * [`executeScript`](#executescript) * [`loadPage`](#loadpage) * [`maximizeWindow`](#maximizewindow) * [`navigateBack`](#navigateback) * [`navigateForward`](#navigateforward) * [`reloadPage`](#reloadpage) * [`setWindowPosition`](#setwindowposition) * [`setWindowSize`](#setwindowsize) * [`sleep`](#sleep) * [`takeScreenshot`](#takescreenshot) * [Interface `Element`](#interface-element) * [`tagName`](#tagname) * [`text`](#text) * [`visibility`](#visibility) * [`x`](#x) * [`y`](#y) * [`width`](#width) * [`height`](#height) * [`cssValue`](#cssvalue) * [`propertyValue`](#propertyvalue) * [`clearValue`](#clearvalue) * [`click`](#click) * [`sendKeys`](#sendkeys) * [`submitForm`](#submitform) * [Interface `PredicateBuilder`](#interface-predicatebuilder) * [`contain`](#contain) * [`not.contain`](#notcontain) * [`equal`](#equal) * [`not.equal`](#notequal) * [`match`](#match) * [`not.match`](#notmatch) * [`be.above`](#beabove) * [`be.at.least`](#beatleast) * [`be.below`](#bebelow) * [`be.at.most`](#beatmost) ### [Module `exports`](#api) #### [`test`](#api) Type definition: * **`test(name: string, implementation?: Implementation): void`** * `Implementation = (t: Test) => Promise<void>` * [`Test`](#interface-test) Example usage: ```js const {test} = require('cybernaut'); test('foo'); // This end-to-end test will be marked as TODO test('foo', async t => { // This end-to-end test will be executed // ... }); ``` #### [`skip`](#api) Type definition: * **`skip(name: string, implementation: Implementation): void`** * `Implementation = (t: Test) => Promise<void>` * [`Test`](#interface-test) Example usage: ```js const {skip} = require('cybernaut'); skip('foo', async t => { // This end-to-end test won't be executed (and marked as SKIP) // ... }); ``` #### [`browser`](#api) Type definition: * **`browser: Browser`** * [`Browser`](#interface-browser) Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.loadPage('http://bar.baz')); }); ``` #### [`defineElement`](#api) Type definition: * **`defineElement(selector: string): Element`** * [`Element`](#interface-element) Example usage: ```js const {defineElement, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.perform(bar.click()); }); ``` #### [`it`](#api) Type definition: * **`it: {should: PredicateBuilder}`** * [`PredicateBuilder`](#interface-predicatebuilder) Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageTitle, it.should.contain('bar')); }); ``` ### [Interface `Test`](#api) #### [`assert`](#api) Type definition: * **`assert<T>(accessor: Accessor<T>, predicate: Predicate<T>, retries?: number, retryDelay?: number): Promise<void>`** Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageTitle, it.should.contain('bar')); // Throws an error if the condition isn't met }); ``` *Note: An assertion is a single test step for which the globally configured `retries` and `retryDelay` options can be overwritten.* #### [`perform`](#api) Type definition: * **`perform(action: Action, retries?: number, retryDelay?: number): Promise<void>`** Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.loadPage('http://bar.baz')); // Throws an error if the action fails }); ``` *Note: A performance is a single test step for which the globally configured `retries` and `retryDelay` options can be overwritten.* #### [`verify`](#api) Type definition: * **`verify<T>(accessor: Accessor<T>, predicate: Predicate<T>, retries?: number, retryDelay?: number): Promise<boolean>`** Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { if (await t.verify(browser.pageTitle, it.should.contain('bar'))) { // Evaluates to false if the condition isn't met // ... } }); ``` *Note: A verification is a single test step for which the globally configured `retries` and `retryDelay` options can be overwritten.* #### [`fail`](#api) Type definition: * **`fail(message: string, cause: Error): void`** Example [TAP][tap] output: `not ok 1 - bar (cause: baz)` Example usage: ```js const {test} = require('cybernaut'); test('foo', async t => { t.fail('bar', new Error('baz')); // Throws a new error }); ``` #### [`pass`](#api) Type definition: * **`pass(message: string): void`** Example [TAP][tap] output: `ok 1 - bar` Example usage: ```js const {test} = require('cybernaut'); test('foo', async t => { t.pass('bar'); // Prints a successful-test line in TAP format on standard output }); ``` ### [Interface `Browser`](#api) #### [`pageTitle`](#api) Type definition: * **`pageTitle: Accessor<string>`** Example [TAP][tap] output: `ok 1 - page title should contain 'bar' (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageTitle, it.should.contain('bar')); }); ``` #### [`pageUrl`](#api) Type definition: * **`pageUrl: Accessor<string>`** Example [TAP][tap] output: `ok 1 - page url should contain 'http://bar.baz' (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageUrl, it.should.contain('http://bar.baz')); }); ``` #### [`windowX`](#api) Type definition: * **`windowX: Accessor<number>`** Example [TAP][tap] output: `ok 1 - window x-position should equal 123 (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.windowX, it.should.equal(123)); }); ``` #### [`windowY`](#api) Type definition: * **`windowY: Accessor<number>`** Example [TAP][tap] output: `ok 1 - window y-position should equal 123 (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.windowY, it.should.equal(123)); }); ``` #### [`windowWidth`](#api) Type definition: * **`windowWidth: Accessor<number>`** Example [TAP][tap] output: `ok 1 - window width should equal 123 (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.windowWidth, it.should.equal(123)); }); ``` #### [`windowHeight`](#api) Type definition: * **`windowHeight: Accessor<number>`** Example [TAP][tap] output: `ok 1 - window height should equal 123 (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.windowHeight, it.should.equal(123)); }); ``` #### [`scriptResult`](#api) Type definition: * **`scriptResult(scriptName: string, script: Script): Accessor<any>`** * `Script = (callback: (result?: any) => void) => void` Example [TAP][tap] output: `ok 1 - result of script 'bar' should equal 'baz' (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.scriptResult('bar', callback => { // This function will be executed in browser context, so any references to outer scope won't work // ... callback('baz'); }), it.should.equal('baz')); }); ``` #### [`executeScript`](#api) Type definition: * **`executeScript(scriptName: string, script: Script): Action`** * `Script = (callback: (result?: any) => void) => void` Example [TAP][tap] output: `ok 1 - execute script 'bar' (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.executeScript('bar', callback => { // This function will be executed in browser context, so any references to outer scope won't work // ... callback(); })); }); ``` #### [`loadPage`](#api) Type definition: * **`loadPage(url: string): Action`** Example [TAP][tap] output: `ok 1 - load page 'http://bar.baz' (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.loadPage('http://bar.baz')); }); ``` #### [`maximizeWindow`](#api) Type definition: * **`maximizeWindow(): Action`** Example [TAP][tap] output: `ok 1 - maximize window (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.maximizeWindow()); }); ``` #### [`navigateBack`](#api) Type definition: * **`navigateBack(): Action`** Example [TAP][tap] output: `ok 1 - navigate back (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.navigateBack()); }); ``` #### [`navigateForward`](#api) Type definition: * **`navigateForward(): Action`** Example [TAP][tap] output: `ok 1 - navigate forward (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.navigateForward()); }); ``` #### [`reloadPage`](#api) Type definition: * **`reloadPage(): Action`** Example [TAP][tap] output: `ok 1 - reload page (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.reloadPage()); }); ``` #### [`setWindowPosition`](#api) Type definition: * **`setWindowPosition(x: number, y: number): Action`** Example [TAP][tap] output: `ok 1 - set window position to 123,456 (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.setWindowPosition(123, 456)); }); ``` #### [`setWindowSize`](#api) Type definition: * **`setWindowSize(width: number, height: number): Action`** Example [TAP][tap] output: `ok 1 - set window size to 123x456 (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.setWindowSize(123, 456)); }); ``` #### [`sleep`](#api) Type definition: * **`sleep(duration: number): Action`** Example [TAP][tap] output: `ok 1 - sleep for 123 ms (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.sleep(123)); }); ``` #### [`takeScreenshot`](#api) Type definition: * **`takeScreenshot(): Action`** Example [TAP][tap] output: `ok 1 - take screenshot 'screenshots/07cc9369-ab10-4221-9bc9-18ad12b87c7c.png' (attempt 1 of 5)` Example usage: ```js const {browser, test} = require('cybernaut'); test('foo', async t => { await t.perform(browser.takeScreenshot()); }); ``` ### [Interface `Element`](#api) #### [`tagName`](#api) Type definition: * **`tagName: Accessor<string>`** Example [TAP][tap] output: `ok 1 - tag name of element '#bar' should equal 'div' (attempt 1 of 5)` Example usage: ```js const {defineElement, it, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.assert(bar.tagName, it.should.equal('div')); }); ``` #### [`text`](#api) Type definition: * **`text: Accessor<string>`** Example [TAP][tap] output: `ok 1 - text of element '#bar' should equal 'baz' (attempt 1 of 5)` Example usage: ```js const {defineElement, it, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.assert(bar.text, it.should.equal('baz')); }); ``` #### [`visibility`](#api) Type definition: * **`visibility: Accessor<boolean>`** Example [TAP][tap] output: `ok 1 - visibility of element '#bar' should equal true (attempt 1 of 5)` Example usage: ```js const {defineElement, it, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.assert(bar.visibility, it.should.equal(true)); }); ``` #### [`x`](#api) Type definition: * **`x: Accessor<number>`** Example [TAP][tap] output: `ok 1 - x-position of element '#bar' should equal 123 (attempt 1 of 5)` Example usage: ```js const {defineElement, it, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.assert(bar.x, it.should.equal(123)); }); ``` #### [`y`](#api) Type definition: * **`y: Accessor<number>`** Example [TAP][tap] output: `ok 1 - y-position of element '#bar' should equal 123 (attempt 1 of 5)` Example usage: ```js const {defineElement, it, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.assert(bar.y, it.should.equal(123)); }); ``` #### [`width`](#api) Type definition: * **`width: Accessor<number>`** Example [TAP][tap] output: `ok 1 - width of element '#bar' should equal 123 (attempt 1 of 5)` Example usage: ```js const {defineElement, it, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.assert(bar.width, it.should.equal(123)); }); ``` #### [`height`](#api) Type definition: * **`height: Accessor<number>`** Example [TAP][tap] output: `ok 1 - height of element '#bar' should equal 123 (attempt 1 of 5)` Example usage: ```js const {defineElement, it, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.assert(bar.height, it.should.equal(123)); }); ``` #### [`cssValue`](#api) Type definition: * **`cssValue(cssName: string): Accessor<string>`** Example [TAP][tap] output: `ok 1 - css value 'margin-left' of element '#bar' should equal '22px' (attempt 1 of 5)` Example usage: ```js const {defineElement, it, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.assert(bar.cssValue('margin-left'), it.should.equal('22px')); }); ``` #### [`propertyValue`](#api) Type definition: * **`propertyValue(propertyName: string): Accessor<string | null>`** Example [TAP][tap] output: `ok 1 - property value 'id' of element '#bar' should equal 'bar' (attempt 1 of 5)` Example usage: ```js const {defineElement, it, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.assert(bar.propertyValue('id'), it.should.equal('bar')); }); ``` #### [`clearValue`](#api) Type definition: * **`clearValue(): Action`** Example [TAP][tap] output: `ok 1 - clear value of element '#bar' (attempt 1 of 5)` Example usage: ```js const {defineElement, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.perform(bar.clearValue()); }); ``` #### [`click`](#api) Type definition: * **`click(): Action`** Example [TAP][tap] output: `ok 1 - click on element '#bar' (attempt 1 of 5)` Example usage: ```js const {defineElement, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.perform(bar.click()); }); ``` #### [`sendKeys`](#api) Type definition: * **`sendKeys(...keys: string[]): Action`** Example [TAP][tap] output: `ok 1 - send keys [ 'text was', 'Key.CONTROL', 'a', 'Key.NULL', 'now text is' ] to element '#bar' (attempt 1 of 5)` Example usage: ```js const {Key, defineElement, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.perform(bar.sendKeys('text was', Key.CONTROL, 'a', Key.NULL, 'now text is')); }); ``` > Modifier keys ([Key.SHIFT][selenium-webdriver-key], [Key.CONTROL][selenium-webdriver-key], [Key.ALT][selenium-webdriver-key], [Key.META][selenium-webdriver-key]) are stateful; once a modifier is processed in the keysequence, that key state is toggled until one of the following occurs: > > - The modifier key is encountered again in the sequence. At this point the state of the key is toggled (along with the appropriate keyup/down events). > > - The [Key.NULL][selenium-webdriver-key] key is encountered in the sequence. When this key is encountered, all modifier keys current in the down state are released (with accompanying keyup events). > > - The end of the keysequence is encountered. When there are no more keys to type, all depressed modifier keys are released (with accompanying keyup events). > > -- *[selenium-webdriver.WebElement][selenium-webdriver-webelement]* *Note: The `WebElement` of `selenium-webdriver` is used internally, but is not accessible from the outside.* #### [`submitForm`](#api) Type definition: * **`submitForm(): Action`** Example [TAP][tap] output: `ok 1 - submit form containing element '#bar' (attempt 1 of 5)` Example usage: ```js const {defineElement, test} = require('cybernaut'); test('foo', async t => { const bar = defineElement('#bar'); await t.perform(bar.submitForm()); }); ``` ### [Interface `PredicateBuilder`](#api) #### [`contain`](#api) Type definition: * **`contain(expectedValue: string): Predicate<string>`** Example [TAP][tap] output: `ok 1 - page title should contain 'bar' (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageTitle, it.should.contain('bar')); }); ``` #### [`not.contain`](#api) Type definition: * **`not.contain(expectedValue: string): Predicate<string>`** Example [TAP][tap] output: `ok 1 - page title should not contain 'bar' (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageTitle, it.should.not.contain('bar')); }); ``` #### [`equal`](#api) Type definition: * **`equal<T>(expectedValue: T): Predicate<T>`** Example [TAP][tap] output: `ok 1 - page title should equal 'bar' (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageTitle, it.should.equal('bar')); }); ``` *Note: The comparison is performed with [deep-strict-equal][deep-strict-equal].* #### [`not.equal`](#api) Type definition: * **`not.equal<T>(expectedValue: T): Predicate<T>`** Example [TAP][tap] output: `ok 1 - page title should not equal 'bar' (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageTitle, it.should.not.equal('bar')); }); ``` *Note: The comparison is performed with [deep-strict-equal][deep-strict-equal].* #### [`match`](#api) Type definition: * **`match(regex: RegExp): Predicate<string>`** Example [TAP][tap] output: `ok 1 - page title should match /bar/ (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageTitle, it.should.match(/bar/)); }); ``` #### [`not.match`](#api) Type definition: * **`not.match(regex: RegExp): Predicate<string>`** Example [TAP][tap] output: `ok 1 - page title should not match /bar/ (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.pageTitle, it.should.not.match(/bar/)); }); ``` #### [`be.above`](#api) Type definition: * **`be.above(expectedValue: number): Predicate<number>`** Example [TAP][tap] output: `ok 1 - window width should be above 123 (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.windowWidth, it.should.be.above(123)); // windowWidth > 123 }); ``` #### [`be.at.least`](#api) Type definition: * **`be.at.least(expectedValue: number): Predicate<number>`** Example [TAP][tap] output: `ok 1 - window width should be at least 123 (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.windowWidth, it.should.be.at.least(123)); // windowWidth >= 123 }); ``` #### [`be.below`](#api) Type definition: * **`be.below(expectedValue: number): Predicate<number>`** Example [TAP][tap] output: `ok 1 - window width should be below 123 (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.windowWidth, it.should.be.below(123)); // windowWidth < 123 }); ``` #### [`be.at.most`](#api) Type definition: * **`be.at.most(expectedValue: number): Predicate<number>`** Example [TAP][tap] output: `ok 1 - window width should be at most 123 (attempt 1 of 5)` Example usage: ```js const {browser, it, test} = require('cybernaut'); test('foo', async t => { await t.assert(browser.windowWidth, it.should.be.at.most(123)); // windowWidth <= 123 }); ``` ## [Related links](#contents) * [Google Testing Blog: Just Say No to More End-to-End Tests][link1] * [Testing Strategies in a Microservice Architecture][link2] ## [Development](#contents) ### Installing dev dependencies ```sh npm install ``` ### Watching sources and unit tests ```sh npm run watch ``` ### Checking for formatting and linting errors ```sh npm run check ``` ### Formatting sources ```sh npm run format ``` ### Committing a new change ```sh npm run cz ``` --- Built by (c) Clemens Akens. Released under the MIT license. [babel]: https://babeljs.io/ [chromedriver]: https://sites.google.com/a/chromium.org/chromedriver/home [config-schema]: https://github.com/clebert/cybernaut/blob/master/config-schema.json [coveralls]: https://coveralls.io/github/clebert/cybernaut?branch=master [coveralls-badge]: https://coveralls.io/repos/github/clebert/cybernaut/badge.svg?branch=master [deep-strict-equal]: https://github.com/sindresorhus/deep-strict-equal [docker]: https://www.docker.com/ [docker-hub-chrome-tags]: https://hub.docker.com/r/clebert/cybernaut-chrome/tags/ [docker-hub-clebert]: https://hub.docker.com/r/clebert/ [docker-hub-firefox-tags]: https://hub.docker.com/r/clebert/cybernaut-firefox/tags/ [docker-mount]: https://docs.docker.com/engine/tutorials/dockervolumes/#mount-a-host-directory-as-a-data-volume [emulating-mobile-devices-in-chrome]: https://github.com/clebert/cybernaut#emulating-mobile-devices-in-chrome [example]: https://github.com/clebert/cybernaut/tree/master/example [example-png]: https://raw.githubusercontent.com/clebert/cybernaut/master/example/example.png [greenkeeper]: https://greenkeeper.io/ [greenkeeper-badge]: https://badges.greenkeeper.io/clebert/cybernaut.svg [link1]: https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html [link2]: https://www.martinfowler.com/articles/microservice-testing/#testing-end-to-end-tips [mdn-async]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function [mdn-await]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await [mobile-emulation]: https://sites.google.com/a/chromium.org/chromedriver/mobile-emulation [node-chromedriver]: https://github.com/giggio/node-chromedriver [node-geckodriver]: https://github.com/vladikoff/node-geckodriver [node-glob]: https://github.com/isaacs/node-glob [nodejs]: https://nodejs.org/en/ [npm]: https://www.npmjs.com/package/cybernaut [npm-badge]: https://img.shields.io/npm/v/cybernaut.svg?maxAge=3600 [selenium]: https://github.com/SeleniumHQ/selenium [selenium-desired-capabilities]: https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities [selenium-webdriver-key]: https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Key.html [selenium-webdriver-webelement]: https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html [semantic-release]: https://github.com/semantic-release/semantic-release [semantic-release-badge]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg [tap]: https://testanything.org/ [tap-mocha-reporter]: https://github.com/tapjs/tap-mocha-reporter [travis-ci]: https://travis-ci.org/clebert/cybernaut [travis-ci-badge]: https://travis-ci.org/clebert/cybernaut.svg?branch=master [tslint]: https://palantir.github.io/tslint/ [tslint-rule-no-floating-promises]: https://palantir.github.io/tslint/rules/no-floating-promises/ [typescript]: http://www.typescriptlang.org/ [typescript-badge]: https://img.shields.io/badge/TypeScript-friendly-blue.svg