quarta-feira, 22 de agosto de 2007

Selenium IDE aplicado a interfaces com Ajax

Fazer testes automatizados com o Selenium IDE numa interface web que possui chamadas assíncronas pode se tornar um problemão. Um exemplo simples é de um link que buscaria através de HttpRequest um recurso no servidor:

<a href="javascript:void(buscarDados())">Clique aqui</a>
Neste caso para testar o clique no link usando o selenium temos dois problemas. Se usarmos a função click o teste seguinte poderá falhar caso o servidor demore alguns segundos para responder. E se usarmos a função clickAndWait ocorrerá um timeout pois o selenium espera que a página inteira mude.

Para resolver esse tipo de situação é que existem as funções iniciadas por wait. Elas aguardam um bom período até que determinado elemento ou texto sejam impressos na página. Dessa forma podemos utilizar a função click e posteriormente fazer aguardar por determinado recurso, o que indicaria que o servidor respondeu.

No caso do nosso exemplo a função de callback do ajax, ao receber os dados do servidor imprime uma tag H1 do html com o texto "Dados do servidor". Dessa forma o teste ficaria na seguinte ordem:

open/teste.html
clicklink=Clique aqui
waitForElementPresent//h1
verifyText//h1Dados do servidor

Repare que após o clique no link, utilizamos a função waitForElementPresent para aguardar que o html tenha inserido uma tag h1 em qualquer parte dele. Depois que este é impresso eu utilizo a função verifyText para verificar se há alguma tag h1 inserida no html que possua o texto "Dados do servidor".

A versão do Selenium IDE utilizada é 0.8.7 e a versão do Firefox 2.0.0.1

sexta-feira, 17 de agosto de 2007

O atributo length de objetos e arrays em javascript

Eu não resisti e usei Javascript essa ultima semana inteira - É a minha confissão de hoje na reunião de javascriptólatras anônimos - Passei um perrengue danado por causa de suas peculiaridades. Tudo causado por falta de atenção a uma das pequenas regrinhas que essa linguagem viciante tem. Para evitar que outros passem pelo mesmo problema, compartilho aqui minha dor.

Em primeiro lugar é preciso verificar que somente arrays têm o atributo length. Em segundo que arrays podem se comportar como objetos, e objetos podem se comportar como arrays, e que arrays são objetos. Entendeu? Bom, vamos aos exemplos pra ficar mais claro.
 var eu = { nome:"Tiago",idade:25 }; //Criando um objeto dinâmico com {}
alert( eu["nome"] ) //Acessando como se fosse um mapa
alert( eu.idade ) //Acessando como um atributo
eu.sexo = "Sempre"; //Criando um novo atributo
alert( eu["sexo"] );
eu["altura"] = 1.75; //Criando um novo atributo como mapa
alert( eu.altura );
No exemplo acima eu crio um objeto dinamicamente utilizando chaves além de acessar e criar novos atributos, de duas formas possíveis: Como se fosse um mapa, utilizando o operado colchetes, e da maneira mais comum, que é utilizando o operador ponto. Vamos seguir então para o próximo exemplo:
 var eu = []; //Criando um array com []
eu.nome = "Tiago"; //Setando atributo em um array? Que coisa estranha
eu["idade"] = 25 //Não era um array, por que está aceitando uma chave como se fosse um mapa?
alert( eu["nome"] ) //Acessando como se fosse um mapa
alert( eu.idade ) //Acessando como um atributo
eu.sexo = "Sempre"; //Criando um novo atributo
alert( eu["sexo"] );
eu["altura"] = 1.75; //Criando um novo atributo como mapa
alert( eu.altura );
Neste exemplo criamos um array ao invés de um objeto, e qual não é nossa surpresa ao perceber que, exceto pela incialização, o comportamento dele fica exatamente igual ao de um objeto. Isso ocorre porque um array é um objeto. Até aí tudo parece maravilhoso. A coisa desanta quando você resolve usar o comportamento de array, num array tratado como objeto. Vejamos:
 var eu = []; 
alert(eu.length); //Tamanho 0 oras, não coloquei nada aqui ainda
eu["nome"] = "Tiago"; //Inserindo um atributo
alert(eu.length); //Tamanho 0?? E o atributo que botei?
eu.idade = 25; //Inserindo outro atributo
alert(eu.length); //Tamanho 0 de novo????
eu[0] = "primeiro indice"; //Adicionando elementos ao array
eu[1] = "segundo indice";
alert(eu.length); //Ahhh agora sim
eu.push("terceiro indice");
eu.push("quarto indice");
alert(eu.length); //Ahhh agora sim de novo
No exemplo acima podemos perceber o quão confuso começa a ficar. Basicamente o atributo length passa a só funcionar quando inserimos elementos no array. Ou seja, os atributos inseridos no array ficam fora do array. Isso fica mais evidente fazendo as iterações como as do exemplo abaixo:
 //agora vamos iterar só pelos elementos do array
for(i=0; i {
alert("elemento do array: " + eu[i]);
}
//agora vamos iterar sobre tudo que tiver aí
for(i in eu)
{
alert("atributo do objeto: " + eu[i]);
}
//De novo somente elementos do array
while(eu.length>0)
{
alert("Remove elemento: " + eu.pop());
}
Na primeira eu itero sobre números, logo somente sobre os elementos do array. No segundo eu itero sobre as chaves, sejam elas numéricas, sejam de texto. E finalmente no terceiro eu itero removendo os elementos do array. Mas ainda sim podem ocorrer algumas curiosidades que podem nos confundir, veja abaixo:
 var eu = []; 
eu[0] = "primeiro indice";
eu[1] = "segundo indice";
eu["1"] = "substitui segundo indice"; //Isso aqui é o mesmo que eu[1]
alert(eu[1]); //Vai alertar a mesma coisa que o alerta de baixo
alert(eu["1"]);
eu["length"] = 0; //Vou sacanear o array agora
alert(eu.length); //Zerei o tamanho
alert(eu[4]); //Ih!!! Apagou tudo
Perceba que adicionar um elemento ao indice 1 como número é o mesmo que adicionar ao 1 como string. Além disso, veja que podemos alterar o valor do tamanho, atributo length, do array somente atribuindo um valor a ele. Isso pode causar grandes problemas, ainda mais se você estiver guardando dados digitados pelo usuário como se o array fosse um mapa.

Um exemplo simples é um usuário preenchendo um formulário com campo de nome e valor, ao clicar em um botão esses dados são inseridos no array como se fosse um mapa. Digamos que ele preencha da seguinte maneira:
Nome     | Valor: 
---------------------
"país" | "brasil"
10 | "isso"
7 | "hoje"
"olha" | "caso"
"length" | 0
Neste caso quando ele digitar o nome 10, o array passará a ter 10 posições, mas no momento que ele digitar "length" com o valor 0, os dados que ele inseriu com nome 10 e 7 desaparecerão. Para debugar isso você terá que arrancar vários tufos de seu cabelo.

E é aí finalmente que chegamos ao ponto crucial. Para evitar esse tipo de problema o ideal é deixar bem separado o que é array, inicializando com [] e o que é objeto inicializando com {}. Isso porque para objetos, um atributo com nome length não tem poder algum, exceto se você implementá-lo para que tenha.