We are going to run everything off the filesystem to avoid having our tests impacted by external influences.
Part 1: Basic test setup
- Create a directory to house Javascript unit test files; we will refer to this as \jsunit henceforth when giving paths.
- Download QUnit.js and QUnit.css into \jsunit
- Download run-qunit.js into \jsunit
- Create a file testme.js in \jsunit with the following content
/** * var-args; adds up all arguments and returns sum */ function add() { }
- Create a file testme.test.htm in \jsunit with the following content
- Note we are using local filesystem paths to load all content; we have no external dependencies
-
<!DOCTYPE html> <html> <head> <!-- we need QUnit as a test runner --> <link rel="stylesheet" href="qunit.css" type="text/css" media="screen" /> <script src="qunit.js"></script> <!-- we'd like to have the file we're going to test --> <script src="testme.js"></script> <!-- and finally lets write some tests --> <script> console.log("test time baby"); test("add is defined", function() { equals(typeof window.add, "function", "add isn't a function :("); }); </script> </head> <body> <h1 id="qunit-header">QUnit Tests</h1> <h2 id="qunit-banner"></h2> <div id="qunit-testrunner-toolbar"></div> <h2 id="qunit-userAgent"></h2> <ol id="qunit-tests"></ol> <div id="qunit-fixture"></div> </body> </html>
- Download PhantomJS (1.3.0 at time of writing)
- For example PhantomJS commands I will assume it is on PATH (eg phantomjs args); use the qualified path if not (eg C:\where\phantom\is\phantomjs args)
- Open testme.test.htm in a browser; it should look like this:
- Open a command prompt, navigate to \jsunit and run phantomjs run-qunit.js testme.test.htm
- Output should be similar to:
test time baby 'waitFor()' finished in 211ms. Tests completed in 57 milliseconds. 1 tests of 1 passed, 0 failed.
- Note we don't see any "test blah pass" or "test two fail" style output
Part 2: CLI build integration prep
So far so good, now we need to get setup to run in a CLI build. There a couple of things we'd like here, most of which are already implemented in run-qunit.js:
- Output each test pass/fail
- Output log messages from tests to the console
- This "just works" courtesy of run-qunit.js, yay!
- Exit with non-zero error code if tests fail
- This makes it easy for build to detect failure and do something in response; for example an Ant build could simply set failonerror
- This "just works" courtesy of run-qunit.js, yay!
We just have to setup output of test pass/fail information to the console. We'll add a test that fails to show what that looks like. Proceed as follows:
- Create a file test-support.js in \jsunit with the following content:
//create a scope so we don't pollute global (function() { var testName; //arg: { name } QUnit.testStart = function(t) { testName = t.name; }; //arg: { name, failed, passed, total } QUnit.testDone = function(t) { console.log('Test "' + t.name + '" completed: ' + (0 === t.failed ? 'pass' : 'FAIL')) }; //{ result, actual, expected, message } QUnit.log = function(t) { if (!t.result) { console.log('Test "' + testName + '" assertion failed. Expected <' + t.expected + '> Actual <' + t.actual + '>' + (t.message ? ': \'' + t.message + '\'' : '')); } }; }());
- Edit testme.test.htm to pull in test-support.js and add a test that will currently fail
-
<!DOCTYPE html> <html> <head> <!-- we need QUnit as a test runner --> <link rel="stylesheet" href="qunit.css" type="text/css" media="screen" /> <script src="qunit.js"></script> <!-- where would our tests be without support! --> <script src="test-support.js"></script> <!-- we'd like to have the file we're going to test --> <script src="testme.js"></script> <!-- and finally lets write some tests --> <script> test("add is defined", function() { equals(typeof window.add, "function", "add isn't a function :("); }); test("add 1+1", function() { equals(add(1, 1), 2); }); </script> </head> <body> <h1 id="qunit-header">QUnit Tests</h1> <h2 id="qunit-banner"></h2> <div id="qunit-testrunner-toolbar"></div> <h2 id="qunit-userAgent"></h2> <ol id="qunit-tests"></ol> <div id="qunit-fixture"></div> </body> </html>
- Open a command prompt, navigate to \jsunit and run phantomjs run-qunit.js testme.test.htm
- Output should be similar to:
Test "add is defined" completed: pass Test "add 1+1" assertion failed. Expected <2> Actual <undefined> Test "add 1+1" completed: FAIL 'waitFor()' finished in 209ms. Tests completed in 70 milliseconds. 1 tests of 2 passed, 1 failed.
- If you print the exit code (echo %ERRORLEVEL% in Windoze) you should get a 1, indicating we have fulfilled the 'exit with non-zero exit code on failure' requirement :)
At long last we are ready to integrate this mess into a build. For this example I will use Ant and will assume Ant is on PATH. At time of writing I am using Ant 1.8.2.
- Create a phantomjs.bat file in \jsunit with the following content
@echo off C:\where\you\put\phantom\phantomjs.exe %*
- Alternately create phantomjs.sh with equivalent functionality if on *nix
- Create a build.xml file in \jsunit with the following content
-
<?xml version="1.0" encoding="UTF-8"?> <project name="jsunittests" basedir="." default="main"> <property name="builddir" location="${basedir}/target"/> <condition property="phantom.filename" value="phantomjs.bat"><os family="windows"/></condition> <condition property="phantom.filename" value="phantomjs.sh"><os family="unix"/></condition> <target name="clean"> <delete dir="${builddir}"/> </target> <target name="prep"> <mkdir dir="${builddir}"/> </target> <target name="jstest"> <!--Run all tests w/phantom, fail if tests fail. Execute all files w/extension .test.htm. --> <apply executable="${phantom.filename}" failonerror="true" dir="${basedir}" relative="true"> <arg value="run-qunit.js"/> <fileset dir="${basedir}"> <include name="**/*.test.htm" /> </fileset> </apply> </target> <target name="main" depends="clean, prep, jstest"> </target> </project>
- Run 'ant'; you should get output similar to the following (yes, it's supposed to fail, remember we have a test that fails setup on purpose)
-
Buildfile: build.xml clean: [delete] Deleting directory C:\Code\jsunit-trial\target prep: [mkdir] Created dir: C:\Code\jsunit-trial\target jstest: [apply] Test "add is defined" completed: pass [apply] Test "add 1+1" assertion failed. Expected <2> Actual
[apply] Test "add 1+1" completed: FAIL [apply] 'waitFor()' finished in 218ms. [apply] Tests completed in 58 milliseconds. [apply] 1 tests of 2 passed, 1 failed. BUILD FAILED C:\Code\jsunit-trial\build.xml:18: apply returned: 1 Total time: 0 seconds - Edit testme.js just enough to fix the test
-
/** * var-args; adds up all arguments and returns sum */ function add() { var sum =0; for (var i=0; i<arguments.length; i++) sum += arguments[i]; return sum; }
- Run 'ant'; you should get output similar to the following
Buildfile: build.xml clean: [delete] Deleting directory C:\Code\jsunit-trial\target prep: [mkdir] Created dir: C:\Code\jsunit-trial\target jstest: [apply] Test "add is defined" completed: pass [apply] Test "add 1+1" completed: pass [apply] 'waitFor()' finished in 214ms. [apply] Tests completed in 59 milliseconds. [apply] 2 tests of 2 passed, 0 failed. main: BUILD SUCCESSFUL Total time: 0 seconds
Part 4: Code coverage
Finally we are ready to get some code coverage. We are going to get code coverage by instrumenting our js files using JSCoverage, running our QUnit tests such that the relative paths resolve to the instrumented copies, and then using the PhantomJS file system APIs to create a colorized copy of the original js file to visually display coverage. We'll do a quick and dirty percentage coverage output to the console as well.
- Download JSCoverage 0.5.1
- Create a jscoverage.bat file in \jsunit with the following content
@echo off C:\where\you\put\jscoverage\jscoverage.exe %*
- Create a template file for coverage information named coverageBase.htm in \jsunit
<!DOCTYPE html> <html> <head> <style> .code { white-space: pre; font-family: courier new; width: 100%; } .miss { background-color: #FF0000; } .hit { background-color: #94FF7C; } .undef { background-color: #AFFF9E; } </style> </head> <body> COLORIZED_LINE_HTML </body> </html>
- Update build.xml to perform a few new steps
- Create a \target\testjs\js directory and copy our js files into it
- Index our js files for code coverage, putting the indexed version into \target\testjs\jsinstrumented
- Copy *.test.htm into \target\testhtm
- Copy base resources to run tests (run-qunit.js, qunit.js, qunit.css) into \target\testhtm
- Copy the instrumented js files into \target\testhtm
- Note that because we used relative paths to our test js files the *.test.htm QUnit html files will now resolve js to the instrumented version when we run the files out of \target\testhtm
- Run PhantomJS on *.test.htm in \target\testhtm
- The updated build.xml looks like this:
- Modify our test-support.js to look for jscoverage data and output a rough count of lines hit, missed, and irrelevant (non-executable). Also expose a function a caller outside of page context can use to access coverage information. The new version should look like this:
-
//create a scope so we don't pollute global (function() { var testName; //arg: { name } QUnit.testStart = function(t) { testName = t.name; }; //arg: { name, failed, passed, total } QUnit.testDone = function(t) { console.log('Test "' + t.name + '" completed: ' + (0 === t.failed ? 'pass' : 'FAIL')) }; //{ result, actual, expected, message } QUnit.log = function(t) { if (!t.result) { console.log('Test "' + testName + '" assertion failed. Expected <' + t.expected + '> Actual <' + t.actual + '>' + (t.message ? ': \'' + t.message + '\'' : '')); } }; //we want this at global scope so outside callers can find it. In a more realistic implementation we //should probably put it in a namespace. window.getCoverageByLine = function() { var key = null; var lines = null; //look for code coverage data if (typeof _$jscoverage === 'object') { for (key in _$jscoverage) {} lines = _$jscoverage[key]; } if (!lines) { console.log('code coverage data is NOT available'); } return { 'key': key, 'lines': lines }; }; QUnit.done = function(t) { var cvgInfo = getCoverageByLine(); if (!!cvgInfo.lines) { var testableLines = 0; var testedLines = 0; var untestableLines = 0; for (lineIdx in cvgInfo.lines) { var cvg = cvgInfo.lines[lineIdx]; if (typeof cvg === 'number') { testableLines += 1; if (cvg > 0) { testedLines += 1; } } else { untestableLines += 1; } } var coverage = '' + Math.floor(100 * testedLines / testableLines) + '%'; var result = document.getElementById('qunit-testresult'); if (result != null) { result.innerHTML = result.innerHTML + ' ' + coverage + ' test coverage of ' + cvgInfo.key; } else { console.log('can\'t find test-result element to update'); } } }; }());
- Finally, modify run-qunit.js to load the original js file and produce a colorized version based on the coverage data we get by running the test against the version of the js file indexed for coverage. The new version should look like this:
-
/** * Wait until the test condition is true or a timeout occurs. Useful for waiting * on a server response or for a ui change (fadeIn, etc.) to occur. * * @param testFx javascript condition that evaluates to a boolean, * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or * as a callback function. * @param onReady what to do when testFx condition is fulfilled, * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or * as a callback function. * @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used. */ function waitFor(testFx, onReady, timeOutMillis) { var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timout is 3s start = new Date().getTime(), condition = false, interval = setInterval(function() { if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) { // If not time-out yet and condition not yet fulfilled condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code } else { if(!condition) { // If condition still not fulfilled (timeout but condition is 'false') console.log("'waitFor()' timeout"); phantom.exit(1); } else { // Condition fulfilled (timeout and/or condition is 'true') console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms."); typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled clearInterval(interval); //< Stop this interval } } }, 100); //< repeat check every 250ms }; if (phantom.args.length === 0 || phantom.args.length > 3) { console.log('Usage: run-qunit.js URL basedir'); phantom.exit(1); } var fs = require('fs'); var page = require('webpage').create(); // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") page.onConsoleMessage = function(msg) { console.log(msg); }; var openPath = phantom.args[0].replace(/^.*(\\|\/)/, ''); var basedir = phantom.args[1]; var coverageBase = fs.read(basedir + fs.separator + 'coverageBase.htm'); page.open(openPath, function(status){ if (status !== "success") { console.log("Unable to access network"); phantom.exit(1); } else { waitFor(function(){ return page.evaluate(function(){ var el = document.getElementById('qunit-testresult'); if (el && el.innerText.match('completed')) { return true; } return false; }); }, function(){ //BEGIN MODIFIED: output colorized code coverage //reach into page context and pull out coverage info. stringify to pass context boundaries. var coverageInfo = JSON.parse(page.evaluate(function() { return JSON.stringify(getCoverageByLine()); })); var lineCoverage = coverageInfo.lines; var originalFile = basedir + fs.separator + coverageInfo.key; var fileLines = readFileLines(originalFile); var colorized = ''; console.log('lines=' + JSON.stringify(lineCoverage)); for (var idx=0; idx < lineCoverage.length; idx++) { //+1: coverage lines count from 1. var cvg = lineCoverage[idx + 1]; var hitmiss = ''; if (typeof cvg === 'number') { hitmiss = ' ' + (cvg>0 ? 'hit' : 'miss'); } else { hitmiss = ' ' + 'undef'; } var htmlLine = fileLines[idx].replace('<', '<').replace('>', '>'); colorized += '<div class="code' + hitmiss + '">' + htmlLine + '</div>\n'; }; colorized = coverageBase.replace('COLORIZED_LINE_HTML', colorized); var coverageOutputFile = phantom.args[0].replace('.test.htm', '.coverage.htm'); fs.write(coverageOutputFile, colorized, 'w'); console.log('Coverage for ' + coverageInfo.key + ' in ' + coverageOutputFile); //END MODIFIED var failedNum = page.evaluate(function(){ var el = document.getElementById('qunit-testresult'); console.log(el.innerText); try { return el.getElementsByClassName('failed')[0].innerHTML; } catch (e) { } return 10000; }); phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0); }); } }); //MODIFIED: add new fn function readFileLines(filename) { var stream = fs.open(filename, 'r'); var lines = []; var line; while (!stream.atEnd()) { lines.push(stream.readLine()); } stream.close(); return lines; }
- Run 'ant'; you should see output similar to:
- Open \jsunit\target\testhtm\testme.test.htm in a browser; you should see something similar to this (note coverage % appears):
- Open \jsunit\target\testhtm\testme.coverage.htm in a browser; you should see something similar to this (red for untested, green for tested, light green for non-executable lines):
<?xml version="1.0" encoding="UTF-8"?> <project name="jsunittests" basedir="." default="main"> <property name="builddir" location="${basedir}/target"/> <property name="jstestdir" location="${builddir}/testjs"/> <property name="jsdir" location="${jstestdir}/js"/> <property name="jsinstrumenteddir" location="${jstestdir}/jsinstrumented"/> <property name="testhtmdir" location="${builddir}/testhtm"/> <condition property="phantom.filename" value="phantomjs.bat"><os family="windows"/></condition> <condition property="phantom.filename" value="phantomjs.sh"><os family="unix"/></condition> <property name="jscoverage.filename" value="jscoverage.bat" /> <target name="clean"> <delete dir="${builddir}"/> </target> <target name="prep"> <mkdir dir="${jsdir}"/> <mkdir dir="${jsinstrumenteddir}"/> <mkdir dir="${testhtmdir}"/> <!-- copy non-test js files to target so we can mess with 'em. how we select which files may vary; for this example just pick the one file we are testing.--> <copy todir="${jsdir}"> <fileset dir="${basedir}"> <include name="testme.js" /> </fileset> </copy> <!-- run jscoverage to produce a version of the file instrumented for code coverage --> <exec executable="${jscoverage.filename}" failonerror="true"> <arg value="${jsdir}"/> <arg value="${jsinstrumenteddir}"/> </exec> <!-- copy our test htm files and modify them to point to the coverage indexed version of the test file. --> <copy todir="${testhtmdir}"> <fileset dir="${basedir}"> <include name="**/*.test.htm" /> </fileset> </copy> <!-- copy core resources to testhtmdir so we can load them with same paths as when executing test htm files directly --> <copy todir="${testhtmdir}"> <fileset dir="${jsinstrumenteddir}"> <include name="**/*.js" /> <exclude name="jscoverage.js"/> </fileset> </copy> <copy todir="${testhtmdir}"> <fileset dir="${basedir}"> <include name="test-support.js" /> <include name="run-qunit.js" /> <include name="qunit.css" /> <include name="qunit.js" /> </fileset> </copy> </target> <target name="jstest"> <!--Run all tests w/phantom, fail if tests fail. Execute all files w/extension .test.htm. --> <apply executable="${basedir}/${phantom.filename}" failonerror="true" dir="${testhtmdir}" relative="false"> <arg value="run-qunit.js"/> <srcfile/> <arg value="${basedir}"/> <fileset dir="${testhtmdir}"> <include name="**/*.test.htm" /> </fileset> </apply> </target> <target name="main" depends="clean, prep, jstest"> </target> </project>
We have clearly displayed we can accomplish some important things:
- Write unit tests for Javascript
- Run unit tests for Javascript in a command line build
- Index Javascript files for code coverage
- Output coverage percentage to the test runner (QUnit html file)
- Render a colorized version of the Javascript under test clearly indicating which lines are/aren't being tested
I think this is awesome! Bear in mind in a real version we would of course make numerous refinements to this rather basic implementation; what we have is a proof of concept not by any stretch of the imagination an implementation ready for a team to consume.
94 comments:
Hi there, I’m not a CI user, but I thought I’d sign up and tell you that the problem is actually with Doctrine cli not correctly passing the configuration to the generatemodelsdb task.
JavaScript Countdown Timer
have you ever tried writing the add function like this:
function add() {
var sum = 0;
for(var i in arguments){
sum += arguments[i];
}
return sum;
}
The test fails when I run it from the CL. In the browser the test is OK.
Check this out, it adds sweet colors to the pass/fail output. Totally ganked it from https://gist.github.com/1588423, but it's still nice.
/*global QUnit */
//create a scope so we don't pollute global
(function(QUnit) {
var testName
, displayPass = '\033[1;92mPass\033[0m'
, displayFail = '\033[1;31mFail\033[0m';
//arg: { name }
QUnit.testStart = function(t) {
testName = t.name;
};
//arg: { name, failed, passed, total }
QUnit.testDone = function(t) {
console.log((0 === t.failed ? displayPass : displayFail) + ': ' + t.name);
};
//{ result, actual, expected, message }
QUnit.log = function(t) {
if (!t.result) {
console.log(' ' + testName + ' assertion failed. Expected <' + t.expected +
'> Actual <' + t.actual + '>' + (t.message ? ': \'' + t.message + '\'' : ''));
}
};
}(QUnit));
Actually, this too. You want it to handle .equal and .ok both, I think:
/*global QUnit */
//create a scope so we don't pollute global
(function(QUnit) {
var testName
, displayPass = '\033[1;92mPass\033[0m'
, displayFail = '\033[1;31mFail\033[0m';
//arg: { name }
QUnit.testStart = function(t) {
testName = t.name;
};
//arg: { name, failed, passed, total }
QUnit.testDone = function(t) {
console.log((0 === t.failed ? displayPass : displayFail) + ': ' + t.name);
};
//{ result, actual, expected, message }
QUnit.log = function(t) {
if (!t.result) {
if (t.expected) {//Only print this if it's there (works for 'equal' assertions)
console.log (' ' + 'Expected "' + t.expected + '" Actual "' + t.actual + '"');
}
console.log( ' ' + testName + ' assertion failed: ' + (t.message ? t.message : ''));
}
};
}(QUnit));
http://www.simpleascouldbe.com
Great instructions, thank you.
I ended up getting this to work in a way that is fairly elegant for our multi-platform maven system. I followed your instructions, with these enhancements to promote automation:
* I put installs of phantom-win and phantom-osx in the source tree so that run scripts could always depend on their location.
* I used maven-exec-plugin to run the .bat or .sh file, depending on the environment (developer needs to manually set the path to the batch file in ~/.m2/settings.xml, but that's a one time thing). This is attached to the integration-test phase.
This makes it so that after SCM update, the developer only needs to change the path to the executable in order for this to be automated.
http://www.simpleascouldbe.com
Great Blog Easy to understand lot of Things.
Really useful for Web Developers. Wordpress development company in chennai
ecommerce website development services
Best web design company in chennai
ecommerce website design services
Nice post. Thanks for sharing information about your services. This is really useful. Web Design & Development Company in Bangalore | Website Design Company in Bangalore | Web Designing Company in Bangalore | Web Design Companies in Bangalore
Thanks so much for sharing this awesome info! I am looking forward to see more posts by you! Check out our websites,
Antivirus || Clean my PC || Antivirus Software || Antivirus Cleaner || Internet Security || Antivirus Protection || Internet Security Software || Antivirus Download || Security Antivirus || Antivirus and Security Software || Install Antivirus || Antivirus & Security Software || Mobile Security and Antivirus ||
Nice post Really useful information Web Design Company in Bangalore, Web Development Company in Bangalore, Web Design Services in Bangalore, Web Design Company in Bangalore, Best Website Design Companies in Bangalore, Best Website Development Company in Bangalore, Website Design Services in Bangalore, Best Website Design Company in Bangalore, SEO Company in Bangalore, SEO Agency in Bangalore, SEO Services in Bangalore, Best SEO Companies in Bangalore, SEO Services Company in Bangalore
Nice Blog!! Thanks For Sharing Wonderful Post.
Digital Marketing | Outsource SEO Services | Digital Marketing Services | Digital Marketing in India | Facebook Ads | Lead Generation Services
Nice Article..Thanks for the information..found ir really interesting
Python Training in Chennai
Python Training Institute in Chennai
Python Training in Chennai Anna Nagar
Python Training in Chennai OMR
UiPath Training in Chennai
Informatica Training in Chennai
Informatica Training Center Chennai
Informatica Training Center in Chennai
Thanks for the information...
seo services bangalore
Thanks for sharing your info. I really appreciate your efforts and I will be waiting for your further write ups thanks once again...
Digital Marketing Company in Bangalore
Digital Marketing Agencies in Bangalore
Learning basics being a Developer is very necessary. Thanks for sharing this article, share more in future.
Hey, Your post is very informative and helpful for us...Postal Recruitment 2020 is a leading job website for all Government job recruitment notification. This page is an exclusive page for the Latest recruitment notification from India Post India Post 2020...
Good one. Thanks for sharing
Digital Marketing company in Bangalore l Web Design Company in Bangalore l Web Development Company in Bangalore l Advertising Agencies in Bangalore l SEO Company in Bangalore l SEO Company in Bangalore l Digital Marketing Companies in Bangalore l Ecommerce Website Development in Bangalore
Good content seo company in Bangalore
Amazing information shared.
SEO agency in Bangalore
To Know FreeJobAlert and to know more about upcoming jobs .
Good Technical Blog.Are you looking to revamp or optimize your blog?
We are a Seo Firm Bangalore.Our Senior SEO Consultant have a 10+ years experience, offer services like
SEO,PPC,SMM,online reputation management & web development both to small and medium companies.As a running offer,we are providing free performance audit for our customers
Thank you for sharing your information i like that your content and your writing way. keep sharing your content and also write a blog about the invest in startups. Keep posting!
Thanks for your post! Really interesting blogs. Here is the some more interesting and most related links.
Best digital marketing company in Dubai, United Arab Emirates. Brandstory is one of the top and best digital marketing companies in Dubai UAE. As a leading digital marketing agency in Dubai, We offer search engine optimization services, online marketing services, UI UX design services, search engine marketing services, email marketing services, Google / Facebook / Bing pay per click services, Internet marketing services, website design services and website development services, social media marketing services. Hire ROI based digital marketing services company in dubai to get digital leads for your business.
Digital marketing company in Dubai | Digital Marketing Agency in Dubai | SEO Company in Dubai | SEO Agency in Dubai | Best Digital Marketing Companies in Dubai | Top Digital Marketing Agencies in Dubai | Best SEO Companies in Dubai | SEO Agencies in Dubai | Online Marketing Company in Dubai | SEO Services Company in Dubai | PPC Company in Dubai | PPC Agency in Dubai | PPC Services in Dubai | Social Media Marketing Company in Dubai | Social Media Marketing Services in Dubai | Social Media Marketing Agencies in Dubai | Web Design Company in Dubai | Website Designers in Dubai | Website Development Services Company in Dubai | Web Design Companies in Dubai
It's Really A Great Post. Looking For Some More Stuff.
Digital marketing agency in hyderabad | Digital marketing
companies in hyderabad
he post was really very good.Thanks for sharing.
SEO company in bangalore | SEO services in
bangalore
SIAUAE is one of the top audit firms in UAE with highly experienced professionals. As a leading vat consultancy services in uae, we achieved the label of best VAT consultant in UAE.
Looking for the top accounting firms in UAE? SIAUAE is the best accounting companies in uae , we offer comprehensive professional services in UAE.
SIAUAE management consultancy is a professional Management Consultancy UAE , Helping clients achieve their business goals. For more details call us now
Today the companies are continually being challenged to reduce the expenses, most of working expenditures and other requirements. The main costs include the salary of employees like HR professional, accountant, auditors and administration. These problem can be overcome by best expert business outsourcing company. They provide quality and valuable services at low cost for the business enterprises. In UAE most of accounts payable outsourcing companies offers cost effective accounting services that will help the companies effectively in the current situation.
Audit firms in UAE
The intensity of smells can't be exaggerated. Smell has such a solid association with one's passionate express that it revives the recollections that went with our experience, in a few occasions, returning over numerous years. A deliberately built fragrance can lift one's state of mind, and places one out of a positive and innovative outlook. Our fragrance arrangements give you a chance to make the ideal experience for your customers and associates – one in which they remain drew in and associated.
Aroma diffuser
Good Blog, thanks for sharing
everybody want digital marketing company Kerala to have their organization on the highest point of the pursuit list and engage your business. We Provide Best Digital Services, Efficiently total start to finish center abilities without viable thoughts. Powerfully cultivate strategic arrangements without empowered worth. Interfacing your business to the clients who are keen on your administration, and make a chain of best digital marketing in Kochi.
Digital marketing company Kerala
Good Blog, thanks for sharing
the intensity of smells can't be exaggerated. Smell has such a solid association with one's passionate express that it revives the recollections that went with our experience, in a few occasions, returning over numerous years. A deliberately built fragrance can lift one's state of mind, and places one out of a positive and innovative outlook. Our fragrance arrangements give you a chance to make the ideal experience for your customers and associates – one in which they remain drew in and associated.
Aroma diffuser
If you want to survive in the technology world, you have to learn python for data science. In order to gain knowledge, it is recommended to attend some training courses.
Dot net Information:
https://dotnetcourseonline.wordpress.com/
https://dotnetonlinetrainingcourse.blogspot.com/
https://sites.google.com/view/microsoft-certification-course/
https://netdevelopercourse.tumblr.com/
https://learndotnet.wixsite.com/learndotnet/
Data Science Online Training
https://datasciencecourse229747904.wordpress.com/
https://datasciencecourseinusa.blogspot.com/
https://sites.google.com/view/free-data-science-courses/
https://onlinetrainingusa.wixsite.com/datasciencecourses
https://bestdatasciencecourses.tumblr.com/
ISO Certification in Delhi – Genveritas a global ISO Certification Consulting firm represents considerable authority in tweaked and result-situated answers for assisting organizations to actualize change and improve business execution.
Neuro Doctors are a cohesive group of Top Neurosurgeon in Bangalore Neurologists, Intervention Neuroradiologist, pain management specialists who work together to provide comprehensive neurosciences care to our patients.
KEEN SEO Agency – Best Web Design Company in Bangalore . We provide full-service Web Design & Development Solutions that also includes specialized SEO services for Small Businesses. We offer Strategist Local SEO, Ecommerce SEO, website auditing, Paid Search (PPC) strategies including Google Ads, Facebook & Linked In Ads for Small Business (B2B & B2C).
Great article shared.Web Development company Kochi
HI guys,
This is a very good post, and I like it very much. For us, it's insightful and helpful. For a long time, I've been looking for this and I'm just really pleased to say that I'm working with this stuff as well. Thanks for sharing this with us.
Digital Marketing Company in Jaipur
Digital Marketing Company In Bangalore
seo company In Delhi
great blog. Really love your content do check Agricultural Investment Company
Best Agriculture Financial Company in India
Aadi Web Solutions Is a well-established website designing company in Faridabad with 8 years of solid experience.website designing. Every Web design & development needs a different approach to build that’s why we have experts of every kind. We are flexible, versatile, and the pro at it. Our experts are working on CMS that is multi-functional, scalable, and dynamic in web development by utilizing the high end technologies like ASP.Net/PHP. We develop websites for small and Enterprise level business. We are best Web Designing Company In Faridabad If you want to propel your business forward.
I really like reading through a post that can make people think. Also, many thanks for permitting me to comment!
Best SEO Company in Bangalore|
SEO Services Company In Bangalore|
SEO Services In Bangalore|
Mobile App Development Companies In Bangalore|
Best Mobile App Development Companies In Bangalore|
Nice post it is really an interesting article.We are also providing the web design services in mumbai. We are the leading
website development company in mumbai
web design company in mumbai
Thanks for sharing.
We at Antino Labs believe in redefining and refining our model to suit the industry's requirements. Antino Labs' several years of experience in the market has let us register our global presence. Antino Labs' has the vision to become the world's most trusted partner for digital transformation and we aim to become a brand that defines innovation and the latest technology. We offer clients a one-stop solution for all their interests regarding IT consulting services in Gurgaon and UI/UX design services in Gurgaon.
hello very nice blog. it is really an interesting article we are providing some backlinks. I hope these content useful for you.
backlinks
backlinks
backlinks
backlinks
backlinks
backlinks
backlinks
backlinks
Hi this do follow backlink this has a amazing content, thanks for sharing
Do Backlinks
Do Backlinks
Do Backlinks
Hi, thanks for sharing this amazing content.
Backlinks
Backlinks
Backlinks
Backlinks
Great article with fantastic notion I admire your submit thankyou so lots and let preserve on sharing your stuff
Backlinks
Backlinks
Backlinks
Backlinks
hello every one nice blog. it is really an interesting article here providing some backlinks.
backlinks
backlinks
backlinks
backlinks
backlinks
backlinks
Hello everyone this information is very useful for us. We are Hoping that you will continue posting such an useful backlinks having valid information
Backlinks
Backlinks
Hello everyone These are the top Digital Marketing organizing organizations in Dubai business viability with effective development and arrangement.
Backlinks
Backlinks
Backlinks
Backlinks
hello every one nice blog. it is really an interesting article We are also providing some backlinks.
backlinks
backlinks
backlinks
backlinks
backlinks
backlinks
Hi everyone Thanks for your post! Really fascinating blogs. Here is the some greater fascinating and most associated links.
Backlinks
Backlinks
very informative article about "integrating javascripts" found it very useful...
B2B Appointment Setting Services
HTML pages are usually integrated with JavaScript functions. ... JavaScript is the main scripting language used to make web pages dynamic, very informative article about "javascript"
Architectural CAD services
Hello, your blog is greater informational for me thanks for sharing such kind of content material really. It is a very beneficial and information helpful content material like us.
Backlinks
Backlinks
Very Nice post it is really an interesting article. this informative backlink
backlinks
backlinks
backlinks
backlinks
backlinks
backlinks
thanks for sharing very informative post about "integrating javascript tests"
Outsourced Lead Generation Services
must appreciate very informative post about "integrating java scripts"
BIM service providers
Hello Everyone! Great article! This is very necessary archives for us. I like all content material fabric and information. I have observe it. You recognize more about this please go to again
backlinks
backlinks
backlinks
backlinks
backlinks
Good Job! You have Shared your well knowledge... I will refer the people to the best IT Solutions providers click the below link:
IT Company
seo packages
ppc company australia
content marketing agency dubai
social media marketing company
web development australia
app development melbourne
Great blog.Thanks for sharing such a useful information
best seo company in bangalore
best seo services company in bangalore
Certified Digital Marketing course in Panchkula
Digital Marketing Training Institute in Panchkula
Nice blog on JAVA.
Ecommerce development Company
nice post on java script .
We are helping eCommerce businesses by increasing their customer reach and growing revenue through the optimal combination tools, and user-centric solutions. Being India’s leading eCommerce software development company, we help B2B & B2C clients drive their customers’ satisfaction, expand their right potential audiences and boost sales upto 100%.
Ecommerce development Company
Great explanation.. Thanks
Internship providing companies in chennai | Where to do internship | internship opportunities in Chennai | internship offer letter | What internship should i do | How internship works | how many internships should i do ? | internship and inplant training difference | internship guidelines for students | why internship is necessary
A great blog, it has a lot of useful information to me
Village Talkies a top-quality professional corporate video production company in Bangalore and also best explainer video company in Bangalore & animation video makers in Bangalore, Chennai, India & Maryland, Baltimore, USA provides Corporate & Brand films, Promotional, Marketing videos & Training videos, Product demo videos, Employee videos, Product video explainers, eLearning videos, 2d Animation, 3d Animation, Motion Graphics, Whiteboard Explainer videos Client Testimonial Videos, Video Presentation and more for all start-ups, industries, and corporate companies. From scripting to corporate video production services, explainer & 3d, 2d animation video production , our solutions are customized to your budget, timeline, and to meet the company goals and objectives.
As a best video production company in Bangalore, we produce quality and creative videos to our clients.
Deal All, Are you looking IELTS exam preparation guidelines?? We provide Free IELTS Workshop in Panchkula.
Call today: +91 9887046666
rotavator price
rotavator price
Nice blog. Appreciate your thoughts. Ecommerce web development dubai
Great blog, very informative blog on development tips. we are leading Graphic designing company in Bangalore.
We are a search engine optimization company in Dubai, UAE. SEO Services in Dubai We offer our best SEO services to businesses looking to rank higher on Google and get the attention they deserve
Thanks for sharing. Bedigitech presenting SEO Services in India. As a result, our clients have achieved good search engine rankings with high-quality traffic.
I learned JavaScript in past 5 years ago. I found it very professional language and you can become front-end expert by getting expertise in HTML, CSS & JavaScript. Best Ecommerce Products
Thank you for sharing the wonderful guide. you may read my new vlog-
QuickBooks Error 181016 and 181021 and QuickBooks Black And White Screen
Thank you for sharing the wonderful guide
List of Top Engineering Colleges in Ahmednagar
List of Top Engineering Colleges in Aurangabad
List of Top Engineering Colleges in Mumbai
List of Top Engineering Colleges in Nagpur
List of Top Engineering Colleges in Nashik
I really like this blog. This is very helpful information, thanks for sharing Honolulu music lessons workshop provides guitar lessons as well as online guitar lessons for your convenience in Honolulu, Hawaii. Additionally, we offer lessons in piano, ukulele, drum & percussion, electric brass, and violin, as well as music theory, composition, and music therapy. Schedule your private guitar lessons today.
Nice blog. The process in which a web developer in Honolulu manages your website from start to finish, adding the latest content to keep your customers informed, and backing up your data. Hawaii webmaster services use SEO best practices to ensure that your site ranks highly. For more information, call 808-330-5506. Address:-350 Ward Avenue #262, Honolulu HI. 96814.
When you get cracks or chips in your windshield repair in Honolulu , prevent them from becoming a bigger problem by visiting our service center in Honolulu, HI. SuperGlass Hawaii provide best Cracks & chips windshield repair in Honolulu. No matter what make or model car or truck you have, SuperGlass Hawaii can save you time and money on windshield repairs in Honolulu, HI, Call; 808–342–9000, Location: SuperGlass Windshield Repair of Hawai’i P.O. Box 8922 | Honolulu, HI 96830.
Superb Blog. If you are looking for Junk Car Removal Hawaii Company? Finest Towing Oahu is here to provide you with the best towing service available. We provide 24 hour towing service for all types of vehicles and models. Our junk car removal air services also give you a fair price.
Thanks for sharing. Finest Towing Maui is a certified, professional operator’s team expert that provides heavy-duty towing and towing truck services in Maui car removal services as soon as possible, across the island of Maui. Please contact 808-744-1964.
Thanks for sharing. build your own brand with private label cosmetics manufacturers in india , skin care, hair care & personal care products manufacturer.
This blog is useful as well as informative. Keep sharing such blogs I really like your posts.
Digital marketing company in Kharar
"I am very happy to discover your post as it will become on top in my collection of favorite blogs to visit.
Manveen digi World."
Thanks for Sharing. Unlock the full potential of your online presence with digital marketing services in Delhi. From strategic social media campaigns to search engine optimization and compelling content creation, Delhi's digital marketing experts help businesses thrive in the digital realm, attract targeted audiences, and achieve their marketing goals.
Araga Windows is a leading manufacturer of premium uPVC And Aluminium Windows Manufacturers in India ,
This blog is useful as well as informative. Keep sharing such blogs I really like your posts. best home tuition in Chandigarh is krishna home tuition
This blog is much informative for me. Digital Infinizy is one of the leading Best Digital Marketing Agency in Bangalore form the past 6 years.
Very insightful! JavaScript testing is a crucial part of modern web development. Developers and companies looking to streamline their workflow should also consider leveraging digital marketing strategies through digital marketing agencies in Dubai to attract more clients. Thanks for sharing this technical guide.
"Great post! Integrating JavaScript tests directly into the CLI build process is a smart move for ensuring code quality and automating testing workflows. Your step-by-step explanation makes it easy to follow along, especially for developers looking to streamline their CI/CD pipelines. I particularly liked how you covered the various testing frameworks and tools that can be incorporated. It would be awesome to see a follow-up on optimizing test performance for large-scale projects. Thanks for sharing such a practical guide!"
Digital Marketing Course In Hyderabad
"Fantastic post! Incorporating JavaScript tests into the CLI build process is a great strategy for maintaining code quality and automating testing workflows.
Digital Marketing Course In Ameerpet
Useful article,
Thanks and Regards,
Lakshmi
Digital Marketer at smarther a leading Web Design Company in Chennai
Useful article,
Chandigarh Career Group is a reputed institute for NDA Coaching in Chandigarh . They offer comprehensive coaching programs designed to help students prepare for the National Defence Academy (NDA) entrance exam.
Post a Comment