UNPKG

siesta-lite

Version:

Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers

686 lines (452 loc) 26.1 kB
Getting started with Siesta in browser environment =============== Siesta is a stress-free JavaScript unit and UI-testing tool. It is known that change of activity is a form of rest, so stop writing code, write some tests and have some rest! Your application will win from both. Siesta is very easy to learn and as your test suite grows and your requirements becomes more complex it still scales very well. Siesta is also cross-platform - the same tests can run in browsers and NodeJS, on Linux, MacOs and Windows (assuming of course they are written in a platform-independent manner). Headless browser modes are supported. {@img images/demo.png autogen} In this guide, we assume a setup, pointed toward running tests in browsers. For the setup, targeting NodeJS environment, please refer to this guide <a href="#!/guide/getting_started_nodejs">Siesta getting started with NodeJS.</a> Installation ------------ Siesta Lite is [published in npm](https://www.npmjs.com/package/siesta-lite), so it can be installed with: > npm install siesta-lite Siesta Standard is distributed as plain archive file, you just unpack it in the preferred location. Siesta browser project -------------- Since plain browsers can't access the file system, we need to start with creation of the Siesta project - a single place for storing the meta-information about your test suite. const project = new Siesta.Project.Browser(); project.configure({ title : 'Basic browser test suite', preload : [ 'https://code.jquery.com/jquery-1.6.4.min.js' ] }); project.plan( '010_sanity.t.js', '020_basic.t.js' ); project.start(); Lets save this file as `siesta.js` in the `tests` folder of your app. We'll need a wrapping html file for this project too, to be able to access the web interface, lets name it `siesta.html`: <!DOCTYPE html> <html> <head> <!-- Recommended set of pragmas, required for IE11 compatibility--> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <!-- Web interface --> <link rel="stylesheet" type="text/css" href="__SIESTA_FOLDER__/resources/css/siesta-all.css"> <script type="text/javascript" src="__SIESTA_FOLDER__/siesta-all.js"></script> <!-- Project file --> <script type="text/javascript" src="siesta.js"></script> </head> <body> </body> </html> As you've probably already guessed, we've created a project with 2 test files: `010_sanity.t.js` and `020_basic.t.js`. **The paths in project file, are relative to the `siesta.html` wrapper file** Lets check how the test file look itself: StartTest(t => { t.it("Sanity", t => { t.ok($, 'jQuery is here'); t.is(1, "1", "Relaxed equality") t.exact(1, 1, "Exact equality") t.is(1, t.anyNumberApprox(0.95, 0.1), "Fuzzy equality") t.like(window.location.href, /010_sanity\.t\.js/, "Location of the test file is as expected") t.throwsOk( () => { vixie }, /vixie/, "Correct exception thrown" ) }); }) The test code is wrapped with `StartTest(t => { ... })` construct. The function passed to it will receive an instance of the {@link Siesta.Test.Browser} class, as the 1st argument. Alternative wrapper form is supported: ```javascript describe('Test case name', t => { t.it("Sanity", t => { ... }); }) ``` The instance of the test class has various *assertion* methods. An "assertion" is any statement about your code, which can be either truthy (we say "assertion pass") or false (assertion fail). It may have arbitrary meaning, ranging from very simple like "this variable is equal to that variable" to complex and domain specific: "this instance of EventEmitter will fire this event exactly N times during the following X milliseconds". Siesta has many general-purpose assertions built-in and it also allows you to <a href="#!/guide/extending_test_class">create your own assertions.</a> In the example above we use the simplest possible assertion: `t.ok(value, description)`. It passes when the provided `value` is "truthy", like : `true`, `1`, `{}` etc and fails otherwise. Each assertion also has an optional description which should contain an explanation of what's being asserted. We also used `throwsOk` assertion, which, accepts a function, run it, and verify that it does throw exception, which serializes to the string, matching a given `RegExp`. You've probably get the idea. To see the list of all **generic** assertions, supported by Siesta, please refer to: {@link Siesta.Test Siesta.Test} class. For the list of **browser-specific** assertions, please refer to: {@link Siesta.Test.Browser Siesta.Test.Browser} class. Ok, we now have a project file, a web-interface wrapper for it, and test file. Lets open the `siesta.html` file in a browser. <p class="side-note"> While it is possible to use file-system only setup, using `file://` urls, it is not recommended, as support for it varies per browser and some features may be different from normal `http://` setup. <br><br> The recommended way is to use a local web server on your machine. For a simple, zero-configuration one, just to serve the static files, you can check this Node package: <a href="https://www.npmjs.com/package/local-web-server">https://www.npmjs.com/package/local-web-server</a> <br><br> So, the browser url should look like: http://localhost/yourproject/tests/siesta.html </p> You should see something like this: {@img images/synopsys.png autogen} Click the "Run all" button in the toolbar. Notice, how the `010_sanity.t.js` test gains a green checkmark, indicating it has completed successfully, without failing assertions. The `020_basic.t.js` has a red cross indicating the file is missing - feel free to create it after reading a couple of the following sections :) Testing specific web page ------------------------- The sample test file above assumes a unit testing approach - you specify what files to preload and a new web page is constructed and test launched on it. Sometimes though, you might want to test a code on the already created web page. This is common for the functional (or acceptance) testing. To do this, you can use the {@link Siesta.Project.Browser#pageUrl pageUrl} config. See below about using the configuration options. Sandboxing. Test page vs project web-interface page ----------- By default, {@link Siesta.Project.Browser#sandbox sandboxing} is enabled. Every test is launched in isolated browser JavaScript context. Isolation is done using iframes or {@link Siesta.Project.Browser#runInPopup popups}. In general, there is no need to cleanup anything after the test. This behavior is configurable with the {@link Siesta.Project.Browser#sandbox sandbox} option, but we recommend to keep it enabled until you start feeling comfortable with Siesta. One need to understand the difference between the test page - the iframe in which the test will be launched, and the project page - the web-interface page. These are not related, and run different code. Typical mistake is to include the files from your application to the web-interface page: <!-- Web interface --> <link rel="stylesheet" type="text/css" href="__SIESTA_FOLDER__/resources/css/siesta-all.css"> <script type="text/javascript" src="__SIESTA_FOLDER__/siesta-all.js"></script> <!-- WRONG, web-interface does not need files from your projects --> <link rel="stylesheet" type="text/css" href="my/project/styles.css"> <script type="text/javascript" src="my/project/bundle.js"></script> <!-- Project file --> <script type="text/javascript" src="siesta.js"></script> In the same manner don't include the Siesta files in the {@link Siesta.Project.Browser#preload preload} option: project.configure({ title : 'Awesome Test Suite', preload : [ '../../siesta-all.js', // WRONG, no need to include siesta files on the test page '../my-app-all.js' ] }); Configuring the project --------- A project can be configured with the {@link Siesta.Project.Browser#configure configure} method, which can be called several times. See the {@link Siesta.Project.Browser} for a detailed description of all available options. The most important option is {@link Siesta.Project.Browser#preload preload}, which defines the files that should be pre-loaded for each test. All urls in {@link Siesta.Project.Browser#preload preload} config are relative to the project html wrapper file `siesta.html`. You can add test files to the project using the {@link Siesta.Project.Browser#plan plan} method, which can be called several times. Note, that the project file contains regular javascript code, so you can configure it differently, depending on various conditions. Siesta includes [Bowser](https://github.com/ded/bowser) library for browser detection, so you can for example mute some tests for certain browsers: var isDev = window.location.href.match(/localhost/); var project = new Siesta.Project.Browser() project.configure({ title : 'Awesome Test Suite', preload : [ isDev ? '../ext-4.0.6/ext-all-debug.js' : '../ext-4.0.6/ext-all.js', isDev ? '../yourproject-all-debug.js' : '../yourproject-all.js' ] }); project.plan('some_test_for_all_browsers.t.js') if (!bowser.msie) project.plan('some_test_for_all_browsers_except_IE.t.js'); project.start() Once the project is fully configured, you can launch it with the {@link Siesta.Project.Browser#start start} method. As your test suite grows, you may need to start grouping your tests in a logical hierarchy. You can do this by passing special "test file descriptor" instead of the string to the {@link Siesta.Project.Browser#plan plan} method. The descriptor should contain a group name and an array of child descriptors: project.plan( '011_single.t.js', { group : 'Rendering tests', items : [ 'rendering/grid.t.js', 'rendering/tree.t.js' ] } ); In turn, child descriptors can be groups as well. This feature is especially useful, when you need to override the {@link Siesta.Project.Browser project} options for some group of tests (see the following section). See also {@link Siesta.Project.Browser#plan} method for additional information. Configuring the individual test --------- When configuring a "test file descriptor", one can also provide some of the project configuration options and they will override the corresponding options that were provided to the project. Such options are explicitly marked in the {@link Siesta.Project.Browser project} documentation. For example, one can have a test with its own `preload` and `autoCheckGlobals` configs: project.configure({ autoCheckGlobals : false ... }) project.plan( { url : '011_simple.t.js', autoCheckGlobals : true, preload : [ ... ] }, '012_complex.t.js' ) When specifying config options for a group descriptor, these options will override the configuration for all child descriptors of that group: project.plan( { group : 'On demand loading', // will override the `preload` option for all tests in this group preload : [ ... ], items : [ ... ] } ) You can also provide test file descriptor in the test file itself, by adding it to the StartTest call: StartTest({ autoCheckGlobals : false }, t => { ... }) Values from this object takes the highest priority and will override any other configuration. See also {@link Siesta.Project.Browser#plan} method for additional information. Using Ecma modules -------------- Virtually all modern browsers already supports Ecma modules, which is very convenient way of structuring the dependencies. To use an Ecma module as the test file, use the {@link Siesta.Project.Browser#isEcmaModule isEcmaModule} config: project.plan( 'regular_script.t.js', { url: 'ecma-module.t.js', isEcmaModule: true } ) Then, inside the test file, you can use `import/export` normally: import {value} from './lib/ecmamodule.js' StartTest(t => { t.is(value, 1, 'Imported correct'); }); Simulating user interaction --------- Siesta can simulate user interactions such as {@link Siesta.Test.Browser#click click}, {@link Siesta.Test.Browser#doubleClick double click}, {@link Siesta.Test.Browser#type type}, {@link Siesta.Test.Browser#dragBy drag-drop} etc. Mouse simulating user actions are listed here: {@link Siesta.Test.UserAgent.Mouse} Keyboard simulating user actions are listed here: {@link Siesta.Test.UserAgent.Keyboard} Touch simulating user actions are listed here: {@link Siesta.Test.UserAgent.Touch} By default Siesta uses synthetic events simulation, which is very fast. To speed it up even further, check the {@link Siesta.Project.Browser#mouseMovePrecision mouseMovePrecision} option. In some cases, however, you may need to test some intrinsic browser behavior which synthetic events can not reproduce. Most notable example will be testing the `:hover` CSS styles of some DOM elements. In such case you can use {@link Siesta.Project.Browser#simulation native events simulation} Keep in mind, for tests involving mouse interaction - you should not move the mouse during the execution of such test, to not interfere with simulation. User actions recorder --------- You can also record user actions as Siesta test. Please refer to this guide: <a href="#!/guide/event_recorder">Using the event recorder.</a> Structuring the test suite ------------ Siesta supports BDD syntax for structuring your tests. Please refer to this guide for more information: <a href="#!/guide/structuring_test_suite">Structuring the test suite</a> Extending the test class --------- Quite often you may find yourself repeating various initialization code in your tests. Or you may need your own assertions, specific to your data. To avoid repetition, you can extend the default Siesta test class by adding own methods to it. Please refer to <a href="#!/guide/extending_test_class">Extending test class</a> guide to know more. Launchers and reports --------- You can launch your browser project from the command line, in various browsers / browser automation environments and generate reports with test suite results. Please refer to <a href="#!/guide/siesta_launchers">Siesta launchers & reports</a> guide for more information. Code coverage --------- Please refer to <a href="#!/guide/code_coverage">Code coverage</a> guide. Assertions --------- Some assertions are generic and cross platform - they belong to the {@link Siesta.Test} class. Others are browser specific - and those are listed in the {@link Siesta.Test.Browser} class. When an assertion passes - it shows a green checkmark with the assertion description (if description is not provided, Siesta will try to generate a sensible default text). When it fails, it also tries to provide you with as much information about the failure as possible, including the arguments passed to the assertion method. This is why you are encouraged to use various special assertions for each specific case. For example, lets say we would like to check if one value is greater than other. We could do that with a simple: t.ok(value1 > value2, 'Value1 is greater than value2') But in case of a failure, the only additional information you will see will be something like: Failed assertion [ok] at line xxx, Got : false, Need : "truthy" value" Compare with the output from the more specific "isGreater" assertion: t.isGreater(value1, value2, 'Value1 is greater than value2') It will output the message along with the provided arguments, instantly making it clear what happened: Failed assertion [isGreater] at line xxx, Got : value1, Need, greater than : value2 So make sure you've scanned the documentation {@link Siesta.Test} or {@link Siesta.Test.Browser} - doing so will save you a lot of time. Testing asynchronous code ----------------- Siesta is fine-tuned to allow easy testing of asynchronous code. There are several ways to do it. One way, is to return a `Promise` from the test function. Siesta will wait until that promise is resolved/rejected, before finalizing the test. Using the `async/await` syntax (which just de-sugars to the `Promise` returned from the function) it may look like this: let someAsyncOperation = () => new Promise((resolve, reject) => { setTimeout(() => resolve(42), 1000) }) t.it('Doing async stuff', async t => { let res = await someAsyncOperation() t.is(res, 42, "Async stuff finished correctly") }) // same thing t.it('Doing async stuff', t => { return someAsyncOperation().then(() => { t.is(res, 42, "Async stuff finished correctly") }) }) Sometimes, you need to explicitly explain to Siesta, that test need to wait the finalization of some asynchronous code block. For that, indicate the beginning of the asynchronous code with {@link Siesta.Test#beginAsync beginAsync} method. This method returns an "asynchronous frame" handler. Then, once the async code is complete, use {@link Siesta.Test#endAsync endAsync} method with the frame handler from the `beginAsync`. For example: let async = t.beginAsync(); Ajax.request({ url : 'ajax_demo/sample.json', success : function (response, opts) { t.is(response, 'foobar', 'Response is correct'); t.endAsync(async); }, failure : function (response, opts) { t.fail("request failed"); t.endAsync(async); } }); You can start as many asynchronous code "frames" as you need. By default, all frames will be forced to finalize after the {@link Siesta.Project#defaultTimeout} seconds, so the whole test will not get stuck in case of unexpected failures. You can configure this interval in the {@link Siesta.Test#beginAsync beginAsync}. ### Chaining Many methods of the test class are asynchronous, accept a callback and returns a `Promise`. A typical scenario is to execute one such method after another, emulating user actions. When using old-good callbacks, the nesting level can grow very quickly and affect the readability: t.type(userNameField, 'username', function () { t.type(passwordField, 'secret', function () { t.click(loginButton, function () { }) }) }) Promises makes the things a bit better, but code still looks cluttered: t.type(userNameField, 'username').then(() => { return t.type(passwordField, 'secret') }).then(() => { return t.type(passwordField, 'secret') }).then(() => { return t.click(loginButton) }) If you see such code in your tests, make sure you've checked the {@link Siesta.Test#chain chain} method. It allows you to keep the nesting at minimum, as in Promises variant: t.chain( next => { t.type(userNameField, 'username', next) }, next => { t.type(passwordField, 'secret', next) }, next => { t.click(loginButton, next) } }) and, supports compact notation, resulting in most readable code: t.chain( { type : 'username', target : userNameField }, { type : 'secret', target : passwordField }, { click : loginButton } ) ### Waiting Since the nature of web applications is very asynchronous (Ajax calls, animations, etc.), be prepared to wait a lot in your tests. To wait for a condition, do: this.waitFor( // The condition to check for, this line will mean waiting // until the presence of #foo element in the documented () => document.getElementById('foo'), // The callback, after the condition has been fulfilled el => { /* DO COOL STUFF WITH el */ } ); You will find lots and lots of waitForXXX methods in the API to assist you in various situations, example: t.waitForSelector('.some_class', () => { // Found it }); setTimeout(() => { // this fulfills the condition, // and the waitFor's callback function is called document.body.className = 'some_class' }, 1000); You can also wait in a chain, by adding a "waitFor" step with a value equal to XXX of any waitForXXX command as the value: t.chain({ { waitFor : 'selector', args : ['.some_class'] }, // calls waitForSelector which waits until // the some_class CSS class exists in the DOM { waitFor : 500 }, // waits for 500 ms { waitFor : 'elementVisible', args : [ someEl ] }, // calls waitForElementVisible and waits // for an element to become visible }); Detecting global variable leaks --------- Siesta has a special built-in assertion, called {@link Siesta.Test#verifyGlobals t.verifyGlobals()}. It will scan the global properties of the test context (`window` object in browsers), and compare them with the properties of a clean and fresh context. In case it finds any "unexpected" globals it will report them as a test failure. You can specify your list of "expected" globals, using the {@link Siesta.Project.Browser#expectedGlobals expectedGlobals} configuration option, or by using the {@link Siesta.Test#expectGlobals t.expectGlobals} method of your test. You can enable this assertion to be executed automatically at the end of each test, by setting {@link Siesta.Project.Browser#autoCheckGlobals autoCheckGlobals} to true on the project configuration. For example, in project: project.configure({ autoCheckGlobals : true, expectedGlobals : [ 'Ext', 'MyProject' ], ... }); And then in tests: // will suppress the complaints about these globals t.expectGlobals('Additional', 'Globals'); "TODO" assertions --------- Sometimes, you might want to mark some assertions in a test as "TODO". For example, you might start to write a test covering an edge case, but only fully implement it later. It's still desirable to run such assertions (sometimes they can accidentally start passing actually), but if they fail - that doesn't mean a failure of the whole test. In such cases you can wrap your assertions using the {@link Siesta.Test#todo t.todo()} method: t.todo('Scheduled for 4.1.x release', t => { let treePanel = new Ext.tree.Panel() t.is(treePanel.getView().store, treePanel.store, "NodeStore and TreeStore have been merged and there's only 1 store now"); }) The `todo` method starts a special sub-test (see <a href="#!/guide/structuring_test_suite">Structuring the test suite</a> guide), in which the failing assertions are considered not harmful for the overall suite. See also {@link Siesta.Test#snooze snooze} method, with which you can "snooze" some sub-test, or assertion group till certain date: // allow an assertion to fail until a certain date t.snooze(new Date(2016, 0, 1), t => { // TODO fix this soon t.expect(1).toBe(2); }); Limitations ----------- Please note that the test page is limited by the same-origin policy as any other web page. You can include scripts/CSS from another domain, but XHR requests on older browsers are restricted to the same domain. The best approach is to run the test suite on the same domain as your application, by placing your tests in one of the deploy directories during the staging deploys. You can also configure a server side proxy. Buy this product --------- Visit our store: <https://bryntum.com/store/siesta> Support --------- Ask question in our community forum: <https://www.bryntum.com/forum/viewforum.php?f=20> Subscribers can post expedited questions in Premium Forum: <https://www.bryntum.com/forum/viewforum.php?f=21> Please report any bugs through the web interface at <https://www.assembla.com/spaces/bryntum/support/tickets> See also --------- Siesta web-page: <https://bryntum.com/products/siesta> Other Bryntum products: <https://bryntum.com/products> Attribution --------- This software contains icons from the following icon packs (licensed under Creative Common 2.5/3.0 Attribution licenses) - <http://www.famfamfam.com/lab/icons/silk/> - <http://led24.de/iconset/> - <http://p.yusukekamiyamane.com/> - <http://rrze-icon-set.berlios.de/index.html> - <http://www.smashingmagazine.com/2009/05/20/flavour-extended-the-ultimate-icon-set-for-web-designers/> - <http://www.doublejdesign.co.uk/products-page/icons/super-mono-icons/> - <http://pixel-mixer.com/> Thanks a lot to the authors of the respective icons packs. COPYRIGHT AND LICENSE --------- Copyright (c) 2009-2022, Bryntum & Nickolay Platonov All rights reserved.