Use karma-html
to test your html
files in the browser with karma
karma-html
loads the chosen.html
files into the iframe elements in thecontext.html
karma browser runner.karma-html
allows you to open, refresh, close and set the dimensions of the iframes through your test suits whenever you want.karma-html
gives you the access to theDocument
object of each loaded.html
file through your test suits. Testing.html
files via itsDocument
objects seems much more genuine and reliable than when HTML content is appended into thediv
containers.
See also
karma-jasmine-dom
[npm] (Set of 19 jasmine custom matchers configured for karma to test html DOM)
Got confused? See the step-by-step example below to get acquainted with what the
karma-html
can be used for.
- Description
- Q&A
- Config
- karmaHTML and Tag objects
- iframe Document Object access
- Methods
- Properties
- Common problems
- Examples
- Step-by-step configuration
- How should I configure the karma
config file
, to run karmaHTML in my browser? [see below] - How can I indicate
.html
files, that I want to test? [see below] - How can I open my
.html
files in my karma browser runner? [see below] - How can I get the access to the Document Object of each
.html
file loaded in my tests? [see below] - Ok, I've already tested my
.html
files, I don't need it anymore, I want to close them. [see below] - I've got million
.html
files to test! I'd prefer to open, test and close each.html
file separately, rather than open them all at once! [see below] - How do I know, if the
.html
file has been loaded successfully and whether it is ready to be tested? [see below] - The
.html
files are loading for ages! Perhaps something's not right... I want to set timeout! [see below] - I want to test the responsiveness of the layout. [see below]
- use
npm install karma-html --save-dev
- Add
"karmaHTML"
as areporter
in the karmaconfig file
- Add
karmaHTML
object in theclient
object in the karmaconfig file
module.exports = function(config) {
config.set({
//other karma settings
reporters: ["karmaHTML"],
client: {
karmaHTML: {
//all karma-html settings
}
}
});
};
All config properties (excluding client.karmaHTML.source
) are optional. For the omitted properties, the properties of the incorrect type or value, the default values will be used instead.
Type: Array
Default: If the source
property is of the incorrect syntax or does not indicate any (or existing) .html
file, the suitable hint errors will occure with the clear explanation what to fix. The karma
will run without karma-html
module if the client.karmaHTML.source
property is incorrect anyway.
Description: Indicates the .html
file(s) that should be loaded into the iframes. The array should include object-type items with src
property, that indicates the .html
file path and the tag
property, that can further be used in the unit tests to get the control of the particular .html
file. The tag names should respect JavaScript identifiers constructing rules: [A-Za-z0-9_$], began with letter, _ or $
You do not have to load
.html
files in the karmaconfig file
files
array, if you've already loaded them in theclient.karmaHTML.source
array.
Remember, to load all resources files of you.html
files in the karmaconfig file
files
array.
module.exports = function(config) {
config.set({
files: [
//no need to load your index.html and template.html here
//all resources of your .html files should be loaded here
{pattern: './public/*.js', watched:true, served:true, included:true},
{pattern: './public/*.png', watched:false, served:true, included:false},
{pattern: './public/fonts/*', watched:false, served:true, included:false}
],
client: {
karmaHTML:{
source: [
{src:'./public/index.html', tag:'index'},
{src:'./public/template.html', tag:'tmpl'}
]
}
}
});
};
Type: Boolean
Default: false
Description: If true
, the .html
files are loaded into the iframes automatically (immediately after the context.html
karma browser runner is loaded). If false
, the .html
files can be loaded manually by invoke open()
method [see below].
Type: Number
Default: 0
(no timeout)
Description: The number of milliseconds the iframe server request can take before automatically being terminated. When a timeout happens, the onstatechange
method [see below] fires and returns false.
Type: Number
Default: 0
(no abort)
Description: The number of milliseconds the loading resources can take before automatically being terminated. When an abort happens, the onstatechange
method [see below] fires and returns false.
You may also check out:
Also check out karma
browserNoActivityTimeout
[link]
Also check out jasmineDEFAULT_TIMEOUT_INTERVAL
[link]
Type: String
Default: "90%"
Description: The CSS width of the iframe, that the .html
file is about to be loaded into. The value (eg. 600px) is appended to the iframe element as the attribute style="width:600px"
. Each valid CSS values and units are allowed. Incorrect values will be ignored and the default value will be used instead.
Type: String
Default: "80vh"
Description: The CSS height of the iframe, that the .html
file is about to be loaded into. The value (eg. 600px) is appended to the iframe element as the attribute style="height:600px"
. Each valid CSS values and units are allowed. Incorrect values will be ignored and the default value will be used instead.
module.exports = function(config) {
config.set({
//all the karma config here
client: {
//all the client config here
karmaHTML:{
source: [
{src:'./public/index.html', tag:'index'},
{src:'./public/template.html', tag:'tmpl'}
],
auto: true,
timeout: 10000,
abort: 60000,
width: "730px",
height: "30vw"
}
}
});
};
- When you configured your karma
config file
, you have got the access to thekarmaHTML
object with all features (methods and properties) to control your.html
files. ThekarmaHTML
object is created and appended to thewindow
object when thecontext.html
karma browser runner is opened via cmdkarma start
command.
KarmaHTML {
0: Tag
1: Tag
index: Tag
tmpl: Tag
length: 2
onstatechange: function() | undefined
__proto__: {
open: function()
close: function()
reload: function()
height: (get|set) function()
width: (get|set) function()
elementList: {}
tagList: []
config: {}
}
}
karmaHTML
object lets you controll all.html
files, whileTag
objects let you control each.html
file separately.Tag
objects are generated automatically.- The number of accessible
Tag
objects is equal to the number of.html
files defined in the karmaconfig file
. - The
Tag
objects are accessible viakarmaHTML
object. - Assuming, that you want to test
index.html
file of theindex
tag and thetemplate.html
file of thetmpl
tag [as above], you would get the twoTag
objects with:karmaHTML.index
orkarmaHTML[0]
andkarmaHTML.tmpl
orkarmaHTML[1]
Tag {
tag: String
itemNum: Number
document: HTMLDocument | null
iframe: HTMLIFrameElement | null
src: String
hasIframe: Boolean
ready: Boolean
onstatechange: function() | undefined
__proto__: {
_root: karmaHTML
open: function()
close: function()
reload: function()
height: (get|set) function()
width: (get|set) function()
}
}
How to get the browser access to the Document
Object of each .html
file loaded into the iframes?
Assuming, that you want to test index.html
file of the index
tag and the template.html
file of the tmpl
tag [as above]:
To get the access to any iframe Document object of loaded .html
file, use karmaHTML['tag_name'].document
- To get the access to the Document object of
index.html
file, usekarmaHTML.index.document
- To get the access to the Document object of
template.html
file, usekarmaHTML.tmpl.document
It gives you the access to the Document object if its iframe is appended to the DOM. Use the
open()
method or configure the karmaconfig file
setting appropriately:client.karmaHTML.auto:true
[[as above]]
Each iframe Document object's reference is added to the window
object as document_0
, document_1
, document_2
, etc. property.
The document_X
s are numbered according to the order of client.karmaHTML.source
items (started from 0
)
- To get the access to the Document object of
index.html
file, usewindow.document_0
(or justdocument_0
) - To get the access to the Document object of
template.html
file, usewindow.document_1
(or justdocument_1
) - etc.
It gives you the access to the Document object if its iframe is appended to the DOM. Use the
open()
method or configure the karmaconfig file
setting appropriately:client.karmaHTML.auto:true
The methods can be used:
- As global methods; eg.
karmaHTML.open()
Then it affects all iframes of all.html
files (all files are opened at once) - As tag methods; eg.
karmaHTML.index.open()
Then it affects only the iframe of the.html
file ofindex
tag (theindex.html
is opened only)
Syntax: karmaHTML.open()
| karmaHTML.tagName.open()
Description: Appends the iframe element into the DOM and loads the .html
file.
- The
karmaHTML.open()
opens all.html
files defined in the karmaconfig file
. If some of the.html
files are already opened, theopen()
method opens only those of the.html
files, that are not yet appended to the DOM. - The
karmaHTML.index.open()
does the same only with the.html
file of theindex
tag. If the particular.html
file is already opened, theopen()
method is ignored.
Parameters: karmaHTML.open(dimensions)
parameter | Syntax | Description |
---|---|---|
dimensions |
{width:String,height:String} |
It sets the CSS width and height properties of the iframe(s). The width and height property are the equivalents of the karmaHTML.width and karmaHTML.height properties. If the iframe is already opened, only its dimensions will be changed. |
karmaHTML.open({width:'700px',height:'10%'}); //appends the iframes for all html files with specified dimensions
karmaHTML.index.open({height:'auto'}); //appends only html file of the tag index with specified height dimension
karmaHTML.tmpl.open({width:null,height:null}); //appends html file of tmpl tag with default width and height
Syntax: karmaHTML.close()
| karmaHTML.tagName.close()
Description: Removes iframe from the document.
- The
karmaHTML.close()
closes (if opened) all iframes of all.html
files defined in the karmaconfig file
. - The
karmaHTML.tmpl.close()
does the same only with the.html
file of thetmpl
tag. If the particular.html
file is already closed, theclose()
method is ignored.
Syntax: karmaHTML.reload()
| karmaHTML.tagName.reload()
Description: Refreshes the Document
of .html
file(s) in the iframes.
- The
karmaHTML.reload()
refreshes all opened iframeDocument
s. - The
karmaHTML.index.reload()
refreshes theDocument
of the.html
file of theindex
tag. If the iframe is not opened, it opens it and loads the.html
file instead.
(getter) Syntax: karmaHTML.width
Return value type: Object
(setter) Syntax: karmaHTML.width = String
Description: Sets or returns the width
dimension of the iframes of all .html
files defined in the karma config file
.
- Returns the object of
tag:value
pairs of all iframes (regardless they are appended to the DOM), eg.{index:'80%',tmpl:'600px'}
- Sets the CSS
width
of all iframes, eg.karmaHTML.width = '50%'
- You can set the
width
regardless all iframes are appended to the DOM - When used before
karmaHTML.open()
, the iframes will be opened with the newwidth
value - The value (eg.
'600px'
) is appended to the iframe elements as the attributestyle="width:600px"
- All valid CSS values and units are allowed
- Incorrect values will be ignored and the
width
will not be changed - Use
karmaHTML.width = null
to reset all iframeswidth
CSS style to its default values (to the value defined in the karmaconfig file
asclient.karmaHTML.width
[see above], or - if not defined - to the module default value90%
)
Also check out
karmaHTML.index.width
property
Also check outkarmaHTML.open()
dimensions parameter
(getter) Syntax: karmaHTML.height
Return value type: Object
(setter) Syntax: karmaHTML.height = String
Description: Sets or returns the height
dimension of the iframes of all .html
files defined in the karma config file
.
It works accordingly to the karmaHTML.width
property [see above].
Also check out
karmaHTML.index.height
property
Syntax: karmaHTML.elementList
(readonly)
Return value type: Array
Description: Returns the list of HTML elements that are used to display iframes in the browser. Getting the access to any of HTML elements gives you the access to all of its DOM properties and methods. It returns the array of object-type items. The number of items returned (and the item index) is equal to the number (and item index) of .html
files specified in the karma config file
. Each item of returned array contains the properties:
container
- reference to the <div> element that contains all below elementsiframe
- reference to the <iframe> element that the.html
files are loaded intoprompt
- reference to the iframe header that contains all below elementsstate
- reference to the <span> element that displays the current state ofrequest
|response
status of loading.html
filetag
- reference to the <span> element that displays the tag name of the current.html
file loaded to the iframe
Syntax: karmaHTML.tagList
(readonly)
Return value type: Array
Description: Returns the list of Tag
objects, generated for all .html
files specified in the karma config file
. The number of items returned (and the item index) is equal to the number (and item index) of .html
files in karma config file
. Getting the access to any of the Tag
objects gives you the access to all of its methods and properties.
Also check out
karmaHTML
andTag
objects
Syntax: karmaHTML.config
(readonly)
Return value type: Object
Description: Returns the list of config properties specified in the karma config file
. If some of config properties where not specified in the karma config file
it returns the module default value instead. It returns:
source
- returns theclient.karmaHTML.source
array [see above]auto
- returns theclient.karmaHTML.auto
[see above] (if not specified - returns defaultfalse
)width
- returns theclient.karmaHTML.width
[see above] (if not specified - returns default90%
)height
- returns theclient.karmaHTML.height
[see above] (if not specified - returns default80vh
)abort
- returns theclient.karmaHTML.abort
[see above] (if not specified - returns default0
)timeout
- returns theclient.karmaHTML.timeout
[see above] (if not specified - returns default0
)
Syntax: karmaHTML.length
(readonly)
Return value type: Number
Description: Returns the number of Tag Objects. The length
value is equal to the number of .html
files defined in the karma config file
.
var w = ['50%','700px'];
var h = ['400px','50vh'];
//loop through all Tag objects
for(var i=0; i<karmaHTML.length; i++){
karmaHTML[i].open({width:w[i],height:h[i]});
}
Syntax: karmaHTML.onstatechange = function(ready,tag,status,statusText){...}
Return value type: Function
| undefined
Description: Take the action when the state of any .html
file defined in the karma config file
changes.
If you want to observe the particular
.html
file of chosen tag name, rather than all.html
files defined in the karmaconfig file
, check outkarmaHTML.index.onstatechange
property
Define onstatechange
as a function()
. This function()
will be automatically fired in the following situations:
- the html resources are loaded (the
response
parameter is passed as true) - the iframe html page is reloaded (the
response
parameter is passed as true) - the iframe html page is closed (the
response
parameter is passed as false) - the
abort
is terminated (theresponse
parameter is passed as false) - the
timeout
is terminated (theresponse
parameter is passed as false) - any error occurs during html resources loading (the
response
parameter is passed as false)
karmaHTML.onstatechange = function(ready,tag,status,statusText){
//some fireworks and magic here
}
ready
(Boolean) parameter passestrue
if.html
file has been loaded correctly, otherwise it passesfalse
tag
(String) parameter indicates which.html
file's state has been changedresponse
(Number) parameter passes HTTP numerical response message, eg.200
,404
responseText
(String) parameter passes HTTP textual response message, eg.OK
,Not Found
To stop firing onstatechange
defined function()
, re-define onstatechange
property with any other - than function - value type.
karmaHTML.onstatechange = null;
karmaHTML.onstatechange = false;
karmaHTML.onstatechange = "";
karmaHTML.onstatechange = function(ready,tag,status,statusText){
if(ready) {
if(tag==='index') run_index_tests();
if(tag==='tmpl') run_tmpl_tests();
}
}
karmaHTML.open();
describe("The index.html iframe document",function(){
beforeAll(function(done){
karmaHTML.index.onstatechange = function(ready){
if(ready) done(); //if the #Document is ready, fire tests!
};
karmaHTML.index.open();
});
it("should be a real Document object",function(){
var iframeDocument = karmaHTML.index.document;
expect(iframeDocument.constructor.name).toEqual('HTMLDocument');
});
});
In all below contents assume, that your karma
config file
has got theindex.html
file ofindex
tag specified.
Also check out how to define tag names
Syntax: karmaHTML.index._root
(readonly)
Return value type: Object
Description: Returns the karmaHTML
Object. It lets you get the access to the methods and properties of karmaHTML
Object through Tag
Object.
(getter) Syntax: karmaHTML.index.width
Return value type: String
(setter) Syntax: karmaHTML.index.width = String
Description: Sets or returns the width
dimension of the iframe of the .html
file of index
tag defined in the karma config file
.
- Returns the
String
value of CSS width property of the iframe of the.html
file ofindex
tag (regardless it is appended to the DOM), eg.'600px'
- Sets the CSS
width
of the iframe of the.html
file ofindex
tag, eg.karmaHTML.index.width = '50%'
- You can set the
width
regardless this iframe is appended to the DOM - When used before
karmaHTML.open()
, this iframe will be opened with the newwidth
value - The value (eg.
'600px'
) is appended to the iframe element as the attributestyle="width:600px"
- All valid CSS values and units are allowed
- Incorrect values will be ignored and the
width
will not be changed - Use
karmaHTML.index.width = null
to reset this iframewidth
CSS style to its default value (to the value defined in the karmaconfig file
asclient.karmaHTML.width
[see above], or - if not defined - to the module default value90%
)
Also check out
karmaHTML.width
parameter
Also check outkarmaHTML.index.open()
dimensions parameter
(getter) Syntax: karmaHTML.index.height
Return value type: String
(setter) Syntax: karmaHTML.index.height = String
Description: Sets or returns the height
dimension of the iframe of the .html
file of index
tag defined in the karma config file
. It works accordingly to the karmaHTML.index.width
property [see above].
Also check out
karmaHTML.height
parameter
Also check outkarmaHTML.index.open()
dimensions parameter
Syntax: karmaHTML.index.tag
(readonly)
Return value type: String
Description: Returns the String
value of the tag name of the current Tag object (regardless its iframe is appended to the DOM); eg. karmaHTML.index.tag
returns 'index'
Syntax: karmaHTML.index.itemNum
(readonly)
Return value type: Number
Description: Returns the Number
value of the array index position of the .html
file of index
tag defined in the karma config file
client.karmaHTML.sources
array (regardless this iframe is appended to the DOM).
Assuming, that the karma config.file
client.karmaHTML.source
array contains index.html
file of index
tag as the first item, and template.html
file of tmpl
tag as the second item, the karmaHTML.index.itemNum
will return 0
and karmaHTML.tmpl.itemNum
will return 1
.
Syntax: karmaHTML.index.document
(readonly)
Return value type: HTMLDocument
| null
Description: Returns the HTML Document
object of the .html
file of index
tag defined in the karma config file
. If the iframe of this .html
file is not appended to the DOM, there's an error occurance, or when the timeout or abort has terminated, it returns null
.
Also check out iframe Document object access
Syntax: karmaHTML.index.iframe
(readonly)
Return value type: HTMLIFrameElement
Description: Returns the HTMLIFrameElement
object of the .html
file of index
tag defined in the karma config file
(regardless this iframe is appended to the DOM).
Also check out
karmaHTML.elementList
property
Syntax: karmaHTML.index.src
(readonly)
Return value type: String
Description: Returns the String
url path of the .html
file of index
tag defined in the karma config file
(regardless this iframe is appended to the DOM).
Also check out
client.karmaHTML.source
property
Syntax: karmaHTML.index.hasIframe
(readonly)
Return value type: Boolean
Description: Returns true
, if the iframe of the .html
file of index
tag defined in the karma config file
is appended to the DOM, otherwise it returns false
.
Also check out
karmaHTML.open()
method
Also check outkarmaHTML.close()
method
Syntax: karmaHTML.index.ready
(readonly)
Return value type: Boolean
Description: Returns true
, if the iframe of the .html
file of index
tag defined in the karma config file
is both appended to the DOM and loaded correctly (with no errors, timeout or abort terminated), otherwise it returns false
.
If you set your karma config file
client.karmaHTML.auto
property to true
, your .html
files may be loaded before you define onstatechange
property to catch the moment of loading finish. Then you can use the following code, to check out, whether your .html
file is already loaded:
if(karmaHTML.index.ready) run_index_tests();
Syntax: karmaHTML.index.onstatechange = function(ready,tag,status,statusText){...}
Return value type: Function
| undefined
Description: Take the action when the state of the .html
file of index
tag defined in the karma config file
changes.
If you want to observe all
.html
files defined in the karmaconfig file
, rather than the.html
file of chosen tag name, seekarmaHTML.onstatechange
property
Define onstatechange
as a function()
. This function()
will be automatically fired in the following situations:
- the html resources are loaded (the
response
parameter is passed as true) - the iframe html page is reloaded (the
response
parameter is passed as true) - the iframe html page is closed (the
response
parameter is passed as false) - the
abort
is terminated (theresponse
parameter is passed as false) - the
timeout
is terminated (theresponse
parameter is passed as false) - any error occurs during html resources loading (the
response
parameter is passed as false)
karmaHTML.index.onstatechange = function(ready,tag,status,statusText){
//some fireworks and magic here
}
ready
(Boolean) parameter passestrue
if.html
file has been loaded correctly, otherwise it passesfalse
tag
(String) parameter indicates which.html
file's state has been changed; (withkarmaHTML.index.onstatechange
it always will beindex
)response
(Number) parameter passes HTTP numerical response message, eg.200
,404
responseText
(String) parameter passes HTTP textual response message, eg.OK
,Not Found
To stop firing onstatechange
defined function()
, re-define onstatechange
property with any other - than function - value type.
karmaHTML.index.onstatechange = null;
karmaHTML.index.onstatechange = false;
karmaHTML.index.onstatechange = "";
karmaHTML.index.onstatechange = function(ready){
if(ready) run_index_tests();
}
karmaHTML.tmpl.onstatechange = function(ready){
if(ready) run_tmpl_tests();
}
karmaHTML.index.open();
karmaHTML.tmpl.open();
Also check out jasmine example
If you use Webpack, it is recommended to add node: {fs: "empty"}
to your webpack config file
, in order to avoid fs
module problems when loading karma-html
karmaHTML.index.onstatechange = function(state){
if(state){
run_index_tests();
this.close(); //or karmaHTML.index.close();
this._root.tmpl.open(); //or karmaHTML.tmpl.open();
}
};
karmaHTML.tmpl.onstatechange = function(state){
if(state){
run_tmpl_tests();
this.close(); //or karmaHTML.tmpl.close();
}
};
karmaHTML.index.open();
We will use Jasmine
framework for testing, Mocha
reporter to report our tests results in the terminal, karmaHTML
package to test our html files, karma-jasmine-dom
package to test DOM objects with DOM custom matchers and Chrome
browser to run our html templates for testing.
- Create new folder
New project
anywhere and create the following folders and files (empty) structure inside of it:
New project
├ tests
│ └ tests.js
├ karma.conf.js
└ index.html
- Open the
New project
folder and open your terminal there (with the location set toNew project
folder) - Install neccessary packages with npm by executing the following command:
npm install karma karma-jasmine jasmine-core karma-chrome-launcher karma-mocha-reporter karma-html karma-jasmine-dom
- In the
karma.conf.js
file add the following content:
module.exports = function(config) {
config.set({
files: [
{pattern: 'tests/*.js',watched:true,served:true,included:true}
],
//load karma-mocha-reporter and karma-html
reporters: ['mocha','karmaHTML'],
//load karma-jasmine-dom and karma-jasmine
frameworks: ['jasmine-dom','jasmine'],
//load karma-chrome-launcher
browsers: ['Chrome'],
client: {
//If false, Karma will not remove iframes upon the completion of running the tests
clearContext:false,
//karma-html configuration
karmaHTML: {
source: [
//indicate 'index.html' file that will be loaded in the browser
//the 'index' tag will be used to get the access to the Document object of 'index.html'
{src:'./index.html', tag:'index'}
],
auto: true
}
}
});
};
- It should work just fine right now, run
karma start
in the terminal and it should:- prompt the positive tests results in the terminal
- open the Chrome browser and display the iframe with our (empty)
index.html
template
- Add the following HTML content example to our
index.html
file:
<!DOCTYPE html>
<html>
<head>
<title>New project</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h3 id="header">Hello world!</h3>
<p id="paragraph">This is our html template.</p>
</body>
</html>
- Add the following jasmine test suits into our
tests/tests.js
file:
//describe, beforeAll, it, expext - are the Jasmine default methods
//karmaHTML is the karma-html package object with the access to all its features
describe("The index.html iframe document",function(){
beforeAll(function(done){
//load DOM custom matchers from karma-jasmine-dom package
jasmine.addMatchers(DOMCustomMatchers);
//lets open our 'index.html' file in the browser by 'index' tag as you specified in 'karma.conf.js'
karmaHTML.index.open();
//karmaHTML.index.onstatechange fires when the Document is loaded
//now the tests can be executed on the DOM
karmaHTML.index.onstatechange = function(ready){
//if the #Document is ready, fire tests
//the done() callback is the jasmine native async-support function
if(ready) done();
};
});
it("should be a real Document object",function(){
var _document = karmaHTML.index.document;
expect(_document.constructor.name).toEqual('HTMLDocument');
});
it("should contain paragraph and header that are the siblings",function(){
//karmaHTML.index.document gives the access to the Document object of 'index.html' file
var _document = karmaHTML.index.document;
//use all document javascript native methods on it
var header = _document.getElementById('header');
var paragraph = _document.getElementById('paragraph');
//these are the karma-jasmine-dom custom matchers
expect(paragraph).toBeNextSiblingOf(header);
expect(header).toBePreviousSiblingOf(paragraph);
});
});
- Run
karma start
again in the terminal and it should:- prompt the positive two tests results in the terminal
- open the Chrome browser and display the iframe with our
index.html
template loaded to run the tests on its DOM
- Read the documentation above to use all the features of
karma-html
!