Blog

CSS3 in every browser with SASS and PIE

18 Jul 2010

Recently, more and more popular is the use of the HTML5 and CSS3. Everyone rushed to them and this is perfectly understandable – after all, developers will be able to bet on semantic instead of doing work arounds for some browsers. But the problem is that at the moment even the “modern” browsers have problems handling some common attributes. The most common example is the border-radius, which has up to three versions – so if we want all browsers display the same rounding, we need to write this as:

#round_me {
  -moz-border-radius: 1em;
  -webkit-border-radius: 1em;
  border-radius: 1em;
}

And this isn’t “uniformity”, but hopefully that for some time only one of them will be needed. At the moment we need to put the whole set every time. And we often forget – as the example GitHub who sometimes remember and sometimes not to use “border-radius.”

Fortunately, if you use the help such as SASS (available for many programming languages) then the problem disappears. This framework allows us to define the “mixins” which then can be attached anywhere without the need to rewrite the same code several times (according to the DRY mantra) The easiest way would be to show how:

@mixin rounded-corners($size) {
  -moz-border-radius: $size;
  -webkit-border-radius: $size;
  border-radius: $size;
}

#round_me {
  @include rounded-corners(1em);
}

Mixins themselves are not rendered, but only attached to the other classes so the end user code will be as in the first example.

Some ask why not use the class instead of id – the answer is simple: the separation of HTML and CSS. Because if one day you’ll want to get rid of the rounded corners then with one is easier: to remove the entry from the appropriate id or search through all the HTML pages and remove the class?

There remains the problem of one browser – namely Internet Explorer. It does not support the CSS3 specification and at the moment is still very popular. Thats why there are libraries like the PIE – to facilitate this. For the above border-radius example(and many others) it require only to attach a .htc file and attribute will work well in IE6, IE7 and IE8. The specification requires, however, to attach it to every class in which CSS is used – so our definition this time will look like this:

#round_me {
  -moz-border-radius: 1em;
  -webkit-border-radius: 1em;
  border-radius: 1em;
  behavior: url(PIE.htc);
}

This is another line of which you must remember. And this is the best place where you can see the beauty of SASS – just add this line in one place and will work everywhere. Thus, the final version of the code will look like this:

@mixin rounded-corners($size) {
  -moz-border-radius: $size;
  -webkit-border-radius: $size;
  border-radius: $size;
  behavior: url(PIE.htc);
}

#round_me {
  @include rounded-corners(1em);
}

I hope that this solution will help you to create beautiful and semantic pages a lot easier.

Web standards in mobile browsers

28 Jun 2010

For the past few weeks I focused on developing Socky – new WebSocket based push server for Ruby on Rails. Its main advantage was to be working well in places where there is no flash – like iPhone or iPad. It is therefore obvious that I became very sensitive to standard implementation in so called “modern browsers” – also mobile ones. So, taking the opportunity of just released iOS4, I decided to write a few words about standard support in mobile browsers.

First tested was the latest iOS4 – loudly advertised as a strong HTML5 supporter. A set of rapid tests showed that the most important elements actually works flawlessly – audio and video, geolocation or SVG. Also, elements such as CSS 3 border-radius, CSS animations and reflections are working properly. But the most important feature for me, WebSockets, is missing. I don’t understand why this protocol isn’t there, when in lately released Safari 5 it wasn’t issue. So how are users of the system be able to use chat, or HTML multiplayer games? There is also no support for @font-face or Web Workers. Maybe we see in the near future?

The second largest mobile system is Android – I tested version 2.2(not yet available everywhere). It’s not disappointing – most of the tests passed without error. As in the iOS audio and video tags work properly, geolocation and CSS 3 too, and, moreover, does @font-face. Unfortunately, there also is no support for WebSocket (which Google Chrome has for a long time) – but fortunately, working flash eliminates this problem (thanks to web-socket-js) I was surprised by the lack of SVG support – I understand that this is a very processor aggravating technology, but lack of it is very arduous.

Last discussed system is a WebOS version 1.4.1 (there should be new version 1.4.5 in the next few days) – system is based entirely on HTML and CSS, so you would expect the best support for standards. Unfortunately, there is some disappointment – most of the tests pass, but from presented systems this one have highest number of unsupported technology – @font-face, WebSockets, SVG, and geolocation. Hopefully the next update will improve browser a little.

All tests were performed using a Modernizr, and some of them tested manually (audio, video, WebSockets and geolocation)

Cucumber testing for multiple users - continuation

03 Mar 2010

A little over a month ago you could read my article about real-time applications testing using Cucumber. In the meantime a new version of Cucumber emerged with Capybara support added. Since the previous method posed several problems during system to system migration (especially Snow Leopard hacks occasionally did not work in other systems) we’ll try a new approach – this time working OOTB in every system.

As always, I will not describe the installation nor configuration process (you can find this in Capybara’s documentation) so let’s get straight to the practice. By default Capybara does not allow you to open multiple browser instances. According to the main developer – “it’s impossible by design”. As they say – It’s impossible. But doable ;) After taking a glance at Capybara’s code (I must admit that it is written quite nice) I found a couple of points where you could bypass this annoying limitation.

It is time to get to work. Let’s create a new set of steps in the features/step_definitions – call it e.g “selenium_steps.rb”. For now let’s assume that we want to open two browser instances (you can change that to different value if you want to). Create a method examining whether any browser instance exists (and creating new one if there’s none):

def check_selenium_browsers
  Capybara.instance_variable_set('@current_driver', :selenium)
  if $browsers.nil?
    $browsers = {}
    current_url
    $browsers[1] = get_selenium_browser
    set_selenium_browser(nil)
    current_url
    $browsers[2] = get_selenium_browser
    set_selenium_browser(1)
  end
end

The second line verifies that you use a correct test engine (in this case selenium). “current_url” method call guarantees that browser current state won’t be lost during process. Next save to a variable state of the browser and then reset it so Capybara can open a new instance. Why not do that by ourselves? The reason is simple – Capybara uses quite a lot of tricks so you could run into potential problems after some code refactoring done by developers. For now this method is sufficient and guarantees stable (or at least predictable) behavior. Next call to “current_url” will open a new brower. Finally restore browser to its initial state. Look at the save/restore browser state functions code below:

def get_selenium_browser
  {
    :session => Capybara.current_session,
    :driver  => Capybara::Driver::Selenium.instance_variable_get('@driver')
  }
end

As you can see, this method is very simple – it only acquires the current session (for Selenium) and “driver” (which is actually a reference to the Selenium server instance).

def set_selenium_browser(browser_id)
  browser = $browsers[browser_id] || {}
  if browser[:session].nil?
    Capybara.instance_variable_set('@session_pool', {})
    Capybara::Driver::Selenium.instance_variable_set('@driver', nil)
  else
    Capybara.instance_variable_set('@session_pool', {"selenium#{Capybara.app.object_id}" => browser[:session]})
    Capybara::Driver::Selenium.instance_variable_set('@driver', browser[:driver])
  end
end

There’s no magic here too – you just save the session state and override reference to the Selenium Server. Now we can write browser change functions with ease:

Given /^I am using first browser$/ do
  check_selenium_browsers
  set_selenium_browser(1)
end

Given /^I am using second browser$/ do
  check_selenium_browsers
  set_selenium_browser(2)
end

This way we may use any number of browsers to test Juggernaut, WebSockets and different functionalities (even a simple, ajax communication between users, thus saving time needed to reauth in subsequent requests) in parallel.
Notice that during Cucumber shutdown phase not all browser instances will be closed (only the active one). To fix this add this code to the one of initialization (features/support) files:

at_exit do
  $browsers && $browsers.each { |id, browser| browser[:driver].quit rescue nil }
end

Testing Juggernaut using Cucumber and Selenium

24 Jan 2010

During tests development you usually use well-known and tested solutions like RSpec or Cucumber + Webrat. They let you develop in a fast and convinient way. Unfortunately there are times when you need to get you hands dirty. If your application use only javascript then Celerity may help. But as soon as you incorporate Flash or Java into your application things get much harder. For such situations the Selenium framework should be well-suited. It lets you test your app by simulating user actions through any (supported) web browser. Connection of Selenium and Cucumber seem to be very interesting option too – for more details look at Cucumber wiki.

By default Cucumber uses one browser to open your application and perform selected tests. But from time to time you may need more instances to run in parallel mode – e.g to test real-time user communication. Luckily there is an easy way to override Selenium scripts in Cucumber letting you open another browser instance and switch between them. You just need to add following lines into “env.rb” (or selenium dedicated configuration file).

$browser2 = Webrat::SeleniumSession.new.selenium
$browser = nil
$browser1 = Webrat::SeleniumSession.new.selenium

The second line is necessary to proper initialization of the second browser by Cucumber. After that, you just need to write appropriate steps for Selenium, say:

Given /^I am using first browser$/ do
  if Webrat.configuration.mode == :selenium
    $browser = $browser1
  end
end

Given /^I am using second browser$/ do
  if Webrat.configuration.mode == :selenium
    $browser = $browser2
  end
end

By default the first browser will be active one. But you should not forget that lastly used browser is not restarted between two consecutive tests. You should always switch to the first browser at the end of your test. In addition, there are two useful steps:

When /^I wait for page to load$/ do
  if Webrat.configuration.mode == :selenium
    selenium.wait_for_page_to_load
  end
end

Given /^I wait for juggernaut$/ do
  if Webrat.configuration.mode == :selenium
    sleep(3)
  end
end

First one ensures that all scripts are loaded before moving on to the next steps. Second one gives Juggernaut time to deliver messages. With this configuration you can use all delivered by Cucumber steps without worrying about compatibility problems.

Accents in Sphinx

24 Jan 2010

When writing web applications you need to use full-text search from time to time. Sphinx or Ferret may be of interest to you in such situation. I do prefer the Sphinx because of its speed and ease of use. But it can pose a problem for you from time to time – especially when searching in languages with accents. By default it does not convert UTF-8 properly – Sphinx treats characters outside the ASCII default set as the separation marks. You can find many pages with a simple solution to this issue – just add appropriate conversion rules to the configuration file:

charset_table: "0..9, a..z, _, A..Z->a..z, U+00C0->a, U+00C1->a, U+00C2->a, U+00C3->a, U+00C4->a, U+00C5->a, U+00C7->c, U+00C8->e, U+00C9->e, U+00CA->e, U+00CB->e, U+00CC->i, U+00CD->i, U+00CE->i, U+00CF->i, U+00D1->n, U+00D2->o, U+00D3->o, U+00D4->o, U+00D5->o, U+00D6->o, U+00D9->u, U+00DA->u, U+00DB->u, U+00DC->u, U+00DD->y, U+00E0->a, U+00E1->a, U+00E2->a, U+00E3->a, U+00E4->a, U+00E5->a, U+00E7->c, U+00E8->e, U+00E9->e, U+00EA->e, U+00EB->e, U+00EC->i, U+00ED->i, U+00EE->i, U+00EF->i, U+00F1->n, U+00F2->o, U+00F3->o, U+00F4->o, U+00F5->o, U+00F6->o, U+00F9->u, U+00FA->u, U+00FB->u, U+00FC->u, U+00FD->y, U+00FF->y, U+0100->a, U+0101->a, U+0102->a, U+0103->a, U+0104->a, U+0105->a, U+0106->c, U+0107->c, U+0108->c, U+0109->c, U+010A->c, U+010B->c, U+010C->c, U+010D->c, U+010E->d, U+010F->d, U+0112->e, U+0113->e, U+0114->e, U+0115->e, U+0116->e, U+0117->e, U+0118->e, U+0119->e, U+011A->e, U+011B->e, U+011C->g, U+011D->g, U+011E->g, U+011F->g, U+0120->g, U+0121->g, U+0122->g, U+0123->g, U+0124->h, U+0125->h, U+0128->i, U+0129->i, U+012A->i, U+012B->i, U+012C->i, U+012D->i, U+012E->i, U+012F->i, U+0130->i, U+0134->j, U+0135->j, U+0136->k, U+0137->k, U+0139->l, U+013A->l, U+013B->l, U+013C->l, U+013D->l, U+013E->l, U+0143->n, U+0144->n, U+0145->n, U+0146->n, U+0147->n, U+0148->n, U+014C->o, U+014D->o, U+014E->o, U+014F->o, U+0150->o, U+0151->o, U+0154->r, U+0155->r, U+0156->r, U+0157->r, U+0158->r, U+0159->r, U+015A->s, U+015B->s, U+015C->s, U+015D->s, U+015E->s, U+015F->s, U+0160->s, U+0161->s, U+0162->t, U+0163->t, U+0164->t, U+0165->t, U+0168->u, U+0169->u, U+016A->u, U+016B->u, U+016C->u, U+016D->u, U+016E->u, U+016F->u, U+0170->u, U+0171->u, U+0172->u, U+0173->u, U+0174->w, U+0175->w, U+0176->y, U+0177->y, U+0178->y, U+0179->z, U+017A->z, U+017B->z, U+017C->z, U+017D->z, U+017E->z, U+01A0->o, U+01A1->o, U+01AF->u, U+01B0->u, U+01CD->a, U+01CE->a, U+01CF->i, U+01D0->i, U+01D1->o, U+01D2->o, U+01D3->u, U+01D4->u, U+01D5->u, U+01D6->u, U+01D7->u, U+01D8->u, U+01D9->u, U+01DA->u, U+01DB->u, U+01DC->u, U+01DE->a, U+01DF->a, U+01E0->a, U+01E1->a, U+01E6->g, U+01E7->g, U+01E8->k, U+01E9->k, U+01EA->o, U+01EB->o, U+01EC->o, U+01ED->o, U+01F0->j, U+01F4->g, U+01F5->g, U+01F8->n, U+01F9->n, U+01FA->a, U+01FB->a, U+0200->a, U+0201->a, U+0202->a, U+0203->a, U+0204->e, U+0205->e, U+0206->e, U+0207->e, U+0208->i, U+0209->i, U+020A->i, U+020B->i, U+020C->o, U+020D->o, U+020E->o, U+020F->o, U+0210->r, U+0211->r, U+0212->r, U+0213->r, U+0214->u, U+0215->u, U+0216->u, U+0217->u, U+0218->s, U+0219->s, U+021A->t, U+021B->t, U+021E->h, U+021F->h, U+0226->a, U+0227->a, U+0228->e, U+0229->e, U+022A->o, U+022B->o, U+022C->o, U+022D->o, U+022E->o, U+022F->o, U+0230->o, U+0231->o, U+0232->y, U+0233->y, U+1E00->a, U+1E01->a, U+1E02->b, U+1E03->b, U+1E04->b, U+1E05->b, U+1E06->b, U+1E07->b, U+1E08->c, U+1E09->c, U+1E0A->d, U+1E0B->d, U+1E0C->d, U+1E0D->d, U+1E0E->d, U+1E0F->d, U+1E10->d, U+1E11->d, U+1E12->d, U+1E13->d, U+1E14->e, U+1E15->e, U+1E16->e, U+1E17->e, U+1E18->e, U+1E19->e, U+1E1A->e, U+1E1B->e, U+1E1C->e, U+1E1D->e, U+1E1E->f, U+1E1F->f, U+1E20->g, U+1E21->g, U+1E22->h, U+1E23->h, U+1E24->h, U+1E25->h, U+1E26->h, U+1E27->h, U+1E28->h, U+1E29->h, U+1E2A->h, U+1E2B->h, U+1E2C->i, U+1E2D->i, U+1E2E->i, U+1E2F->i, U+1E30->k, U+1E31->k, U+1E32->k, U+1E33->k, U+1E34->k, U+1E35->k, U+1E36->l, U+1E37->l, U+1E38->l, U+1E39->l, U+1E3A->l, U+1E3B->l, U+1E3C->l, U+1E3D->l, U+1E3E->m, U+1E3F->m, U+1E40->m, U+1E41->m, U+1E42->m, U+1E43->m, U+1E44->n, U+1E45->n, U+1E46->n, U+1E47->n, U+1E48->n, U+1E49->n, U+1E4A->n, U+1E4B->n, U+1E4C->o, U+1E4D->o, U+1E4E->o, U+1E4F->o, U+1E50->o, U+1E51->o, U+1E52->o, U+1E53->o, U+1E54->p, U+1E55->p, U+1E56->p, U+1E57->p, U+1E58->r, U+1E59->r, U+1E5A->r, U+1E5B->r, U+1E5C->r, U+1E5D->r, U+1E5E->r, U+1E5F->r, U+1E60->s, U+1E61->s, U+1E62->s, U+1E63->s, U+1E64->s, U+1E65->s, U+1E66->s, U+1E67->s, U+1E68->s, U+1E69->s, U+1E6A->t, U+1E6B->t, U+1E6C->t, U+1E6D->t, U+1E6E->t, U+1E6F->t, U+1E70->t, U+1E71->t, U+1E72->u, U+1E73->u, U+1E74->u, U+1E75->u, U+1E76->u, U+1E77->u, U+1E78->u, U+1E79->u, U+1E7A->u, U+1E7B->u, U+1E7C->v, U+1E7D->v, U+1E7E->v, U+1E7F->v, U+1E80->w, U+1E81->w, U+1E82->w, U+1E83->w, U+1E84->w, U+1E85->w, U+1E86->w, U+1E87->w, U+1E88->w, U+1E89->w, U+1E8A->x, U+1E8B->x, U+1E8C->x, U+1E8D->x, U+1E8E->y, U+1E8F->y, U+1E96->h, U+1E97->t, U+1E98->w, U+1E99->y, U+1EA0->a, U+1EA1->a, U+1EA2->a, U+1EA3->a, U+1EA4->a, U+1EA5->a, U+1EA6->a, U+1EA7->a, U+1EA8->a, U+1EA9->a, U+1EAA->a, U+1EAB->a, U+1EAC->a, U+1EAD->a, U+1EAE->a, U+1EAF->a, U+1EB0->a, U+1EB1->a, U+1EB2->a, U+1EB3->a, U+1EB4->a, U+1EB5->a, U+1EB6->a, U+1EB7->a, U+1EB8->e, U+1EB9->e, U+1EBA->e, U+1EBB->e, U+1EBC->e, U+1EBD->e, U+1EBE->e, U+1EBF->e, U+1EC0->e, U+1EC1->e, U+1EC2->e, U+1EC3->e, U+1EC4->e, U+1EC5->e, U+1EC6->e, U+1EC7->e, U+1EC8->i, U+1EC9->i, U+1ECA->i, U+1ECB->i, U+1ECC->o, U+1ECD->o, U+1ECE->o, U+1ECF->o, U+1ED0->o, U+1ED1->o, U+1ED2->o, U+1ED3->o, U+1ED4->o, U+1ED5->o, U+1ED6->o, U+1ED7->o, U+1ED8->o, U+1ED9->o, U+1EDA->o, U+1EDB->o, U+1EDC->o, U+1EDD->o, U+1EDE->o, U+1EDF->o, U+1EE0->o, U+1EE1->o, U+1EE2->o, U+1EE3->o, U+1EE4->u, U+1EE5->u, U+1EE6->u, U+1EE7->u, U+1EE8->u, U+1EE9->u, U+1EEA->u, U+1EEB->u, U+1EEC->u, U+1EED->u, U+1EEE->u, U+1EEF->u, U+1EF0->u, U+1EF1->u, U+1EF2->y, U+1EF3->y, U+1EF4->y, U+1EF5->y, U+1EF6->y, U+1EF7->y, U+1EF8->y, U+1EF9->y"

It turns out that it is not enough – even after adding this piece of code some of the polish (that’s my native language) signs were not recognized properly. After some searching I found a reason – polish “tails” were written not as characters in UTF-8 but as an html entities. There’s a simple and clean solution – add following line to your configuration file:

html_strip: 1

And now everything should work great – the only drawback is that the Sphinx ignores accents and converts them into ASCII characters. But that’s a low price in exchange for functionality that Sphinx gives to you.

Ruby-flavored shoes

03 Jan 2010

Recently, working on my BSc thesis, I had to choose programming language and some framework to create graphical interface. The first thing that came to my mind was C++ and QT library. But that would be too easy :). It’s time to learn something new. Something connected with Ruby (cause that’s the language I currently work with). There’s a couple of nice bindings for QT in Ruby, but all of them seemed not enough “ruby-way” to me. Most of them didn’t comfort to some of my needs (multi-platform, should work OOTB, with acceptable performance) too. Finally I found really interesting framework, namely, Ruby Shoes.

After quick installation on OS X, virtualised Windows and Ubuntu it is time for our beloved “hello world” test. Next, few more advanced test. Everything works great. Applications work on all three platforms, contain Ruby Shoes (statically linked), so there’s no need for any dependencies installation. The only problem is weak support for gems containing native extensions. This especially shows with RMagick. It compiles and starts but as soon as you try to call any function it fails with message that some (existing) bundle doesn’t exist. You should also be careful when searching for documentation – not every function is described in RDoc. If you can’t find one then try to look into manual or google it.