Brincando com PhantomJS e CasperJS

Esses dias usei o PhantomJS para converter uma imagem em SVG para PNG, e gostei bastante, tanto que dei uma olhada mais a fundo em como ele pode ser usado. E umas das formas é para testar aplicações.

Com o Phantom você pode testar de forma headless, ou seja, sem usar a interface gráfica, o que resulta numa diminuição do tempo dos testes.

Mas ele por si só não é um framework de teste, portanto quando usado pra testar você geralmente vai querer usar algum framework de teste como o Jasmine.

Pra minha brincadeira eu acabei usando apenas o CasperJS, que abstrai o PhantomJS visando a execução de testes. Assim sendo ele nos dá funções de navegação, preenchimento de campos, screenshot, etc.

Casper

Após instalar o PhantomJS e o CasperJS (versão 1.1 beta 1). Dei uma olhada nos samples do Casper, e usei o “googletesting.coffee”, para entender como ele funciona, e fiz uma pequena mudança, para capturar um screenshot no último teste, como pode ser visto no código abaixo:


# Google sample testing.
#
# Usage:
# $ casperjs test googletesting.coffee
casper.test.begin 'Google search retrieves 10 or more results', 5, (test) ->
casper.start "http://www.google.fr/", ->
test.assertTitle "Google", "google homepage title is the one expected"
test.assertExists 'form[action="/search"]', "main form is found"
@fill 'form[action="/search"]', q: "foo", true
casper.then ->
test.assertTitle "foo – Recherche Google", "google title is ok"
test.assertUrlMatch /q=foo/, "search term has been submitted"
test.assertEval (->
__utils__.findAll("h3.r").length >= 10
), "google search for \"foo\" retrieves 10 or more results"
casper.run -> test.done()
casper.test.begin "Casperjs.org is first ranked", 1, (test) ->
casper.start "http://www.google.fr/", ->
@fill "form[action=\"/search\"]", q: "casperjs", true
casper.then ->
test.assertSelectorContains ".g", "casperjs.org", "casperjs.org is first ranked"
@capture './search_for_casperjs.png'
casper.run -> test.done()

Como vocês podem ver, a declaração do teste é bem similar a qualquer xUnit da vida.

Portanto, se você tiver procurando formas de testar usando Javascript/CoffeeScript, o PhantomJS pode te ajudar com certeza, e o CasperJS pode ser uma opção dentre as várias existentes.

Se quiser saber mais, sobre o PhantomJS o projeto é bem documentado, portanto dê uma olhada na doc e pra saber do CasperJS, veja a documentação e principalmente os samples.

Crowdtest – Minhas impressões

Nos últimos anos surgiram plataformas que utilizam o “poder das massas”. Essas plataformas são chamadas de crowdsourcing, e muitas delas são focadas no financiamento de projetos, como por exemplo o famoso KickStarter e a “versão” brasileira Catarse.me.

Nessa onda de crowdsourcing nasceu o Crowdtest em 2010, criado pela empresa mineira Base2 Tecnologia. O Crowdtest se apoia no crowdsourcing para fornecer serviços de Teste de Software. O seu foco é “organizar mão-de-obra disponível na Internet para execução de testes”.

Participei recentemente de um projeto no Crowdtest, mas antes de falar da minha impressão sobre a plataforma. Vou explicar como ela funciona.

Primeiro é bom entender que a Crowdtest faz a interface entre os Testadores e os clientes. O cliente cadastra o projeto no site e a equipe da Crowdtest monta a equipe de Testadores, avisando os Testadores já cadastrados no site.

Após montada a equipe, o projeto tem início e os Testadores testam os projetos com o objetivo de encontrar bugs. Cada bug encontrado é validado pela equipe da Crowdtest em conjunto com o cliente, e caso aprovado irá render uma remuneração ao Testador, de acordo com o tipo de bug encontrado (interface, funcional e impeditivo).

Eu participei do projeto de teste do novo portal da Natura e foi um experiência bem interessante, pois além de Testador, tenho a Vizir, portanto olhei o Crowdtest das duas perspectivas.

Primeiro vou falar da perspectiva de Testador.

A primeira coisa que me impressionou foi o profissionalismo. Logo que você entra no projeto de teste, há uma página informando detalhes do projeto. Nela é bem explícito os objetivos do teste e quais áreas que o Testador deve ter mais foco.

Uma vez ciente das informações de acesso e dos objetivos, vamos à diversão, ou melhor, aos testes. Aí é hora de arregaçar as mangas e fazer o máximo de testes exploratórios que você puder.

A cada bug encontrado, você primeiro pesquisa se o mesmo, já não foi encontrado por outro testador. Se o bug é “novinho em folha”, você cadastra todas as informações dele, e cadastra de forma bem completa, informando todos os dados necessários para o pessoal da Crowdtest poder reproduzir e identificar a ocorrência.

E qual a graça disso tudo, você pode está se perguntando? Bem, se você é Testador e gosta do que faz, só o fato de existir um sistema que acabou de nascer, já basta para você fazer uma boa “caça”. Mas brincadeiras à parte, o que eu achei interessante, foi que o Crowdtest te permite ter uma experiência real no mundo do Teste de Software, pois mesmo se você não estiver nessa área, o “reporte” de outros bugs irão te ensinar como fazer um bom “reporte” e dá dicas de onde procurar por bugs. Além disso, ao cadastrar o bug e ele ser aceito pela equipe da Crowdtest (eles verificam se não há duplicidade no bug e se ele realmente ocorre para poder validá-lo), você recebe uma remuneração.

Agora da perspectiva da empresa, a Crowdtest surge como uma boa alternativa para estressar o sistema que está para ser lançado.

Aqui na Vizir por exemplo, nós não temos uma equipe dedicada exclusivamente para os testes, portanto ter a rede de pessoas que a Crowdtest tem, testando sistemas que desenvolvemos em fase beta, seria uma boa opção. Uma vez que isso iria reduzir o número de bugs que nossos clientes encontrariam na realização de seus testes.

Principalmente para sistemas que vão alcançar um grande público, como por exemplo o próprio portal da Natura, usar a plataforma da Crowdtest faz com que o sistema seja testado por centenas/milhares de Testadores usando diferentes sistemas operacionais (mobile e desktop) e versões de navegadores. Só esse fato por si só, já te dá um feedback do nível de interoperabilidade do seu sistema.

Resumindo, a Crowdtest é uma ótima forma de você profissional de Teste de Software, viver experiências novas. E para você que está lançando um novo site/sistema/app, seja mobile ou web, aí está uma boa forma e a baixo custo, de você reduzir surpresas em suas apresentações para o cliente ou até mesmo em produção.

Para saber mais sobre o Crowdtest, veja o blog dele: http://crowdtest.me/crowdtest/

Automação de testes: Aproximando contextos

A área de Teste evoluiu muito desde quando eu iniciei nela, em 2007. Hoje mesmo não atuando nela, eu vejo o quanto é diferente a forma de se testar, de quando eu comecei na área.

Naquela época automação de testes era um assunto ainda muito pouco difundido e as ferramentas usadas pagas, de grandes suítes de teste como a Rational.

Hoje em 2013 o cenário muito bastante, práticas ágeis estão mais difundidas, a comunidade brasileira está mais madura e ferramentas open sources dão todo o “ferramentário” necessário para a automação.

Para chegarmos no estado de hoje, várias mudanças e revoluções ocorreram. E como toda revolução, ela promete algo muito melhor ao status quo e para se prevalecer, muitas vezes ela se apoia em dizer que tudo está errado.

A automação de testes surgiu como um “lobo mau” para muitos Testers e outros se agarram a ela, como se ela fosse o único caminho de se testar. Com o tempo perceberam que ela nada mais é do que uma evolução. Ela não veio pra expurgar testes manuais, ela veio pra trazer maior eficiência aos testes e evitar o “caos” das manutenções de software.

Um dos efeitos colaterais da automação de teste, foi a aproximação da área de Teste com a área de Desenvolvimento. Ela ocorreu fisicamente com os Testadores sentando mais próximos dos Desenvolvedores, pra facilitar a comunicação quando é preciso tirar dúvidas sobre programação.

Mas ela ocorreu mais ainda na formação do Tester, no próprio DNA do Tester, uma vez que programar não é mais uma arte usada apenas para construir, ela também é usada para “quebrar ” (testes de carga usando JMeter), pra verificar (testes de aceitação usando Jasmine) e pra dá segurança para mudar (testes unitários com RSpec).

Com isso dois contextos que de forma errada muitas vezes eram separados, hoje estão mais próximos. Afinal, Desenvolvimento e testes sempre existiram pelo mesmo propósito, entregar software de qualidade.

Com a dinâmica atual do mercado de TI, não há mais espaço pra guerrinhas setorias, nem para Desenvolvedores que só cospem código, e nem pra Testadores que só navegam por telinhas. Não é mais questão de ser generalista, é questão que saber analisar um problema é tão básico para um Desenvolvedor, como programar é para o Testador.

Retornando?

Olá pessoal! A quanto tempo não escrevo no QualidadeBR! Bom estar de voltar.

Na verdade eu nunca estive longe, só estive ausente mesmo.

Essa ausência que ocasionou mais de um ano de lacuna nas publicações tem vários motivos, sendo o mais importante, o fato de eu não mais atuar na área de Teste de Software.

Quem me acompanha no Twitter e Facebook, deve ter percebido as mudanças que ocorreram tanto na minha vida profissional, quanto na pessoal. Muita coisa mudou nesse 1 ano que estive digamos de “férias” do QualidadeBR.

Para aqueles que não sabem o porquê desse período sabático, e gostariam de saber, abaixo eu explico o motivo principal, para os que não querem, podem pular o próximo parágrafo. 🙂

Como eu sair da área de Testes, aliás, isso ocorreu bem antes do início do período sabático, foi no começo de 2010. A saída da área inicialmente não era total e eu ainda participava das Mesas Redondas que ocorriam no DFTestes (aliás, hoje em dia as Mesas Redondas ocorrem até em eventos, muito bom ver o pessoal continuando o trabalho e ampliando!). Porém com o tempo e principalmente com o início da Vizir, empresa na qual eu atuo e sou sócio-fundador, o recurso tempo ficou mais escasso, e nesse momento decidi por sair das minhas atividades relacionadas a Teste de Software, pois vi que não consegueria manter tudo e principalmente o nível de qualidade dos posts.

Agora sobre o título do post, não sei ao certo se estou realmente retornando as atividades do blog. De momento, o que posso dizer é que tenho algumas ideias de publicações sobre a área. 🙂

Agora de outra perspectiva, acredito que há até bastante coisa pra escrever sobre Teste de Software, mesmo não atuando na área (isso não é tão verdade assim, uma vez que parar de testar,  eu nunca parei hehe).

Abraço! E desculpas pelo longo período sabático, e obrigado a todos que ainda consultam o blog e assinam o feed, eu fico muito feliz em saber que o conteúdo aqui publicado ao longo dos anos, ajuda tantas pessoas. 😀

Seja o “Curador” das Mesas Redondas

Como puderam perceber, há um bom tempo que não atualizo o blog. E não apenas ele, mas também a minha participação em grupos de discussão da nossa área, twitter, etc foram afetadas devido ao ritmo frenético do trabalho, o que é bom por um lado, mas tem suas consequências e demanda de certos “sacrifícios” e escolhas (o que é normal na vida de qualquer adulto).

Uma das atividades que faço na nossa comunidade e que foi afetada, é a organização das Mesas Redondas que ocorrem no grupo DFTestes. Atualmente eu apenas abro as votações e inicio as discussões,  não conseguindo mais acompanhar, participar e resumir as discussões, como eu fazia antigamente.

E é exatamente esse o trabalho que estou querendo passar para outra pessoa, pois hoje a minha contribuição é muito baixa, e não consigo dedicar o tempo que seria necessário para fazer um bom trabalho.

Basicamente, as atividades do “Curador” das Mesas Redondas são:

  • Abrir a votação 1/2 semanas antes do final do mês;
  • Fechar a votação no primeiro dia do mês;
  • Abrir a discussão no primeiro dia do mês, do tema mais votado;
  • Abrir a discussão no dia 15 do mês, com o tema que ficou em segundo lugar na votação.
A criação do resumo de cada discussão, também é algo que seria legal de fazer, uma vez que o conteúdo gerado nas Mesas Redondas costuma ser de alta qualidade e relevância, mas essa atividade não é mandatória da função, embora altamente desejável.
Se você tem interesse em ser o “Curador” das Mesas Redondas, me mande um e-mail para ffc.fabricio@gmail.com. E qualquer dúvida, só me mandar um e-mail ou postar um comentário.

IEEE 1028 – Padrão para Revisões de Software

Um amigo meu, o Edimilson Estevam, fez o último exame da CTFL, e comentou comigo que caiu algumas questões a respeito desse padrão do IEEE.

Eu particularmente nunca tinha ouvido falar a respeito dele, nem na época que me preparei para a CTFL.

O intuito desse post, é compartilhar esse padrão (enviado pelo Edimilson – obrigado!), e também fazer um resumo a respeito dele.

O que é o IEEE 1028?

Como todo padrão elaborado pelo IEEE (lê-se “I três E”), o 1028 é fruto do trabalho voluntário de alguns membros do IEEE. E sendo um padrão, eles nos traz algumas importantes e relevantes informações a respeito de revisão de software. Mas é sempre bom lembrar, que ele deve ser usado com bom senso, pois o contexto sempre prevalece sob o padrão (ou deveria prevalecer).

O IEEE 1028 nos traz cinco tipos de revisão de software, junto com os procedimento necessários para a execuçaõ de cada tipo. Está fora do escopo do padrão questões como: quando uma revisão se faz necessária? como escolher qual tipo de revisão deve ser usado?

Os 5 tipos de revisão abordados são:

  • Revisões gerenciais;
  • Revisões técnicas;
  • Inspeções;
  • Walk-throughs;
  • Auditórias.

Os cinco tipos de revisão

Segue abaixo, a tradução do anexo B do padrão, que contém uma tabela comparativa entre os tipos de revisão (o texto original está numa linguagem meio chata de entender, e não consegui melhorar muito na tradução – se notarem algum erro ou melhoria, por favor me avisem):

Característica Revisão gerencial Revisão técnica Inspeção Walk-through Auditória
Objetivo Garantir o progresso; recomendar ações corretivas; garantir alocação correta dos recursos Avaliar a conformidade do estado atual com as especificações e planos; garantir integridade da mudança Encontrar anomalias; verificar decisões; verificar a qualidade do produto Encontrar anomalias; examinar alternativas; melhorar o produto; fórum para aprendizado Avaliação independente de cumprimento com os objetivos de padrões e regulamentos
Tomada de decisão A equipe de gerenciamento traça o curso da ação; decisões são feitas na reunião ou como resultado das recomenda-ções A equipe de revisão solicita aos gerentes ou a liderança técnica que atuem nas recomendações A equipe de revisão escolhe as disposições pré-definidas do produto; os defeitos devem ser removidos A equipe concorda com as mudanças para serem feitas pelo autor Organização auditada, iniciador, comprador, cliente ou usuário
Verificação das mudanças O líder verifica que itens são fechados; a verificação das mudanças é deixada para outros controles do projeto O líder verifica que itens são fechados; a verificação das mudanças é deixada para outros controles do projeto O líder verifica que itens são fechados; a verificação das mudanças é deixada para outros controles do projeto O líder verifica que itens são fechados; a verificação das mudanças é deixada para outros controles do projeto Responsabili-dade da organização auditada
Tamanho recomendado do grupo Duas ou mais pessoas Três ou mais pessoas Três a seis pessoas Duas a sete pessoas Uma a sete pessoas
Quem participa Gerentes, liderença técnica e algumas pessoas de outras áreas Liderença técnica e algumas pessoas de outras áreas Pessoas da área com acompanhe-mento documen-tado Liderença técnica e algumas pessoas de outras áreas Auditores, organização auditada, pessoal de gerência e técnico
Grupo da liderança Normalmente o gerente responsável Normalmente o engenheiro líder Um facilitador treinado O facilitador ou o autor O auditor líder
Volume de materiais Moderado para muito, depende dos objetivos da reunião Moderado para muito, depende dos objetivos da reunião Relativa-mente baixo Relativa-mente baixo Moderado para muito, depende dos objetivos da reunião

Abaixo, segue o link para baixar o padrão IEEE 1028:

http://bit.ly/ieee_1028

3 anos!!!

Como o tempo passa, parece que foi ontem que estava escrevendo o primeiro post do QualidadeBR.

É… já fazem três anos que o QualidadeBR nasceu, e com certeza há muita história pra contar sobre esses três anos, além do 282 posts que já foram publicados nesse período.

Mas hoje vou deixar um pouco de lado a temática do blog, e falar a respeito de 3 coisas que o QualidadeBR me proporcionou nesses 3 anos.

Conhecer pessoas

Sem dúvidas um dos pontos mais legais de ter criado o QualidadeBR foi ter a oportunidade de conhecer tantas pessoas, desde outros blogueiros até leitores. Vários deles considero verdadeiros amigos, mesmo não conhecendo alguns pessoalmente.

Acredito que esse seja uma das coisas mais bacanas ao se criar um blog, independente do assunto.

Ajudar pessoas

Como sabem o QualidadeBR não possui nenhum tipo de apoio financeiro ou veiculação de propaganda. Nunca ganhei nenhum centavo com ele, e aliás, essa nunca foi a minha intenção (não que eu ache ruim apoiadores ou veicular propagandas, muito pelo contrário).

Mas algo que ganhei e isso não tem preço, é o prazer de poder ajudar as pessoas, seja por meio do próprio conteúdo do blog, ou via e-mail.

Então se você gosta de ajudar as pessoas, criar um blog pode ser uma boa forma de fazer isso.

Aprendizado

A minha intenção inicial com o QualidadeBR era me motivar a continuar os estudos na área de Teste de Software, pois tinha acabado de obter a CBTS, e não queria me acomodar, muito pelo contrário, queria estudar sobre novos assuntos e compartilhar experiências e o pouco que conhecia da área na época.

O QualidadeBR me fez e ainda faz aprender muito, seja durante as pesquisas para um post, firmando conhecimentos durante a escrita ou até mesmo por meio dos comentários dos leitores.

Se você gosta de estar sempre aprendendo, um blog pode ser uma boa forma de motivar os seus estudos e além disso, você estará compartilhando eles com outras pessoas.

Bem é isso. Obrigado a todos leitores por acompanhar o QualidadeBR. 🙂

Software Testing Weekly

Se você gosta de receber newsletters informativos (não spams) e se interessa por Teste de Software, o Software Testing Weekly lhe será útil.

Baseado no Ruby Weekly e derivados (ex.: NoSQL Weekly), o Software Testing Weekly irá trazer toda segunda-feira, um resumo do que ocorreu na comunidade de Teste de Software, tanto em blogs como em grupos de discussão.

Para receber, basta se inscrever no seguinte endereço: http://eepurl.com/3Yvc. E veja um piloto aqui: http://bit.ly/exemplo_stw.

Escrevendo testes melhores com o Watir

Vimos no último post sobre o Watir, como melhorar os nossos testes usando o Rspec.

Já nesse post, iremos vê como melhorar os testes que escrevemos, utilizando um helper criado pelo Jeff Morgan.

Mas antes disso, deixa eu contar uma breve história sobre os nossos testes.

O Twitter mudou e quebrou os nossos testes

Como vocês devem ter notado, o Twitter recentemente (coisa de 1/2 meses), mudou a página de login. E a alteração também afetou os ids de alguns elementos da página, como por exemplo, o do text field do login e senha.

Os posts já foram atualizados, mas o que ocorreu é interessante pra ilustrar a fragilidade dos testes que dependem da interface.

Pode parecer estranho, mas tanto testar como desenvolver front-end são tarefas complicadas, principalmente Web (só lembrar das várias tecnologias usadas numa interface Web e os vários navegadores existentes). A sensação e de que quando falamos de sistemas front-end, pisamos num chão mais gelatinoso, e qualquer mudança faz ele tremer.

Para evitar o que ocorreu, o ideal é a equipe de desenvolvimento NÃO MUDAR o id dos elementos,  afinal de contas, para o usuário tanto faz se o text field tem o id username ou login, mas para os Testadores isso importa, e muito. Aliás, para ajudar na nomeação desses ids, estabelecer um padrão pra equipe ou boas práticas pode ajudar.

Bem era isso, agora vamos falar do helper que irá melhorar os nossos testes.

Melhorando os testes

Esse helper é uma mão na roda pra quem utiliza o Watir, com ele vamos passar a escrever menos código e será mais fácil de manter os nossos testes.

O helper mágico que estou falando é o seguinte:

module WatirHelper
  # A helper class to make accessing web elements via Watir easier.
  # All methods take an identifier parameter.  This parameter is
  # an array of hashes that are used to identify an element on the
  # page.  On elements that support multiple attributes you can
  # provide multiple identifiers.
  #
  # This module assumes there is a @browser variable available.

  def self.included(cls)
    cls.extend ClassMethods
  end

  module ClassMethods
    # adds three methods - one to put data in a text field, another
    # to fetch that data, and another to return the actual text_field.
    #
    # Example:  text_field(:first_name, {:id => "first_name})
    # will generate the 'first_name', 'first_name=', and
    # 'first_text_field' methods
    def text_field(name, identifier)
      define_method(name) do
        @browser.text_field(identifier).value
      end
      define_method("#{name}=") do |_value|
        @browser.text_field(identifier).value = _value
      end
      define_method("#{name}_text_field") do
        @browser.text_field(identifier)
      end
    end

    # adds three methods - one to put data in a hidden field, another
    # to fetch that data, and a third to return the hidden field.
    #
    # Example:  hidden(:first_name, {:id => "first_name})
    # will generate the 'first_name', 'first_name=' and
    # 'first_hidden' methods
    def hidden(name, identifier)
      define_method(name) do
        @browser.hidden(identifier).value
      end
      define_method("#{name}=") do |value|
        @browser.hidden(identifier).set(value)
      end
      define_method("#{name}_hidden") do
        @browser.hidden(identifier)
      end
    end

    # adds three methods - one to select an item in a drop-down,
    # another to fetch the currently selected item, and another
    # to return the select_list.
    #
    # Example:  select_list(:state, {:id => "state"})
    # will generate the 'state', 'state=' and 'state_select_list'
    # methods
    def select_list(name, identifier)
      define_method(name) do
        @browser.select_list(identifier).value
      end
      define_method("#{name}=") do |value|
        @browser.select_list(identifier).set(value)
      end
      define_method("#{name}_select_list") do
        @browser.select_list(identifier)
      end
    end

    # adds three methods - one to check, one to uncheck and
    # a third to return a checkbox
    #
    # Example: checkbox(:active, {:name => "is_active"})
    # will generate the 'check_active', 'uncheck_active', and
    # 'active_checkbox' methods
    def checkbox(name, identifier)
      define_method("check_#{name}") do
        @browser.checkbox(identifier).set
      end
      define_method("uncheck_#{name}") do
        @browser.checkbox(identifier).clear
      end
      define_method("#{name}_checkbox") do
        @browser.checkbox(identifier)
      end
    end

    # adds three methods - one to select, another to clear and
    # another to return a radio button
    #
    # Example:  radio_button(:north, {:id => "north"})
    # will generate 'select_north', 'clear_north', and
    # 'north_radio_button' methods
    def radio_button(name, identifier)
      define_method("select_#{name}") do
        @browser.radio(identifier).set
      end
      define_method("clear_#{name}") do
        @browser.radio(identifier).clear
      end
      define_method("#{name}_radio_button")  do
        @browser.radio(identifier)
      end
    end

    # adds three methods - one click a button, another
    # to click a button without waiting for the action to
    # complete, and a third to return the button.
    #
    # Example:  button(:save, {:value => "save"})
    # will generate the 'save', 'save_no_wait', and
    # 'save_button' methods
    def button(name, identifier)
      define_method(name) do
        @browser.button(identifier).click
      end
      define_method("#{name}_no_wait") do
        @browser.button(identifier).click_no_wait
      end
      define_method("#{name}_button") do
        @browser.button(identifier)
      end
    end

    # adds three methods - one to select a link, another
    # to select a link and not wait for the corresponding
    # action to complete, and a third to return the link.
    #
    # Example:  link(:add_to_cart, {:text => "Add to Cart"})
    # will generate the 'add_to_cart', 'add_to_cart_no_wait',
    # and 'add_to_cart_link' methods
    def link(name, identifier)
      define_method(name) do
        @browser.link(identifier).click
      end
      define_method("#{name}_no_wait") do
        @browser.link(identifier).click_no_wait
      end
      define_method("#{name}_link") do
        @browser.link(identifier)
      end
    end

    # adds a method that returns a table element
    #
    # Example:  table(:shopping_cart, {:index => 1})
    # will generate a 'shopping_cart' method
    def table(name, identifier)
      define_method(name) do
        @browser.table(identifier)
      end
    end

    # adds two methods - one to return the text within
    # a row and one to return a table row element
    #
    # Example: row(:header, {:id => :header}) will
    # generate a 'header' and 'header_row' method
    def row(name, identifier)
      define_method(name) do
        @browser.row(identifier).text
      end
      define_method("#{name}_row") do
        @browser.row(identifier)
      end
    end

    # adds a method to return the text of a table data <td> element
    # and another one to return the cell object
    #
    # Example:  cell(:total, {:id => "total"})
    # will generate a 'total' method and a 'total_cell'
    # method
    def cell(name, identifier)
      define_method(name) do
        @browser.cell(identifier).text
      end
      define_method("#{name}_cell") do
        @browser.cell(identifier)
      end
    end

    # adds a method that returns the content of a <div>
    # and another method that returns the div element
    #
    # Example: div(:header, {:id => "banner"})
    # will generate a 'header' and 'header_div' methods
    def div(name, identifier)
      define_method(name) do
        @browser.div(identifier).text
      end
      define_method("#{name}_div") do
        @browser.div(identifier)
      end
    end

    # adds a method that returns the content of a <dd>
    # and another method that returns the dd element
    def dd(name, identifier)
      define_method(name) do
        @browser.dd(identifier).text
      end
      define_method("#{name}_dd") do
        @browser.dd(identifier)
      end
    end

    # adds a method that returns the content of a <dl>
    # and another that returns the dl element
    def dl(name, identifier)
      define_method(name) do
        @browser.dl(identifier).text
      end
      define_method("#{name}_dl") do
        @browser.dl(identifier)
      end
    end

    # adds a method that returns the content of a <dt>
    # and another that returns the dt element
    def dt(name, identifier)
      define_method(name) do
        @browser.dt(identifier).text
      end
      define_method("#{name}_dt") do
        @browser.dt(identifier)
      end
    end

    # adds a method that returns the content of a
    # <form> element and another that returns the
    # form element
    def form(name, identifier)
      define_method(name) do
        @browser.form(identifier).text
      end
      define_method("#{name}_form") do
        @browser.form(identifier)
      end
    end

    # adds a method that returns a the content of a
    # <frame> element and another that returns the
    # frame element
    def frame(name, identifier)
      define_method(name) do
        @browser.frame(identifier).text
      end
      define_method("#{name}_frame")  do
        @browser.frame(identifier)
      end
    end

    # adds a method that returns an image <image> element
    def image(name, identifier)
      define_method(name) do
        @browser.image(identifier)
      end
    end
  end

  def content
    @browser.text
  end

  def visit_page(page_url)
    @browser.goto(page_url)
  end

  def page_title
    @browser.title
  end

  def wait_for_page
    @browser.wait
  end
end

Os comentários feitos pelo Jeff já explicam muito bem o que esse helper faz. Mas mesmo assim deixa eu dá uma explicação rápida, sobre a magia por de trás dele: Basicamente ele facilita a definição dos elementos da página. Provendo tanto o setter quanto getter dos elementos que iremos interagir durante o teste, além de prover o objeto do próprio elemento.

Pra utilizar esse helper, basta fazer o include, o que irá fazer com que a classe HomePage “ganhe” todos os métodos dele, como por exemplo o método page_title, que irá retornar o título da página que está aberta.

Abaixo está como ficou o código dos nossos testes com a utilização desse helper.

require 'rubygems'
#require 'watir'
require 'firewatir'
require 'lib/watir_helper.rb'

module Twitter

  class HomePage
    include WatirHelper

    HOME_PAGE = 'twitter.com'

    link(:tweet_button, :class => 'tweet-button button')
    link(:tweet_button_disable, :class => 'tweet-button button disabled')
    text_field(:username, :name => 'session[username_or_email]')
    text_field(:password, :name => 'session[password]')
    text_field(:editor, :class => 'twitter-anywhere-tweet-box-editor')
    button(:sign_in_submit, :class => 'submit button')
    div(:message, :class => 'tweet-text')

    def initialize
      @browser = Watir::Browser.new
    end

    def visit
      @browser.goto(HOME_PAGE)
    end

    def login(username, password)
      self.username = username
      self.password = password
      self.sign_in_submit
    end

    def type_message(message)
      self.editor = message
      self.editor_text_field.fire_event('onMouseDown')
    end

    def tweet
      self.tweet_button
    end

    def message_exists?(message)
      @browser.wait_until {self.message_div.text == message}
    end

    def alert_message_exists?(message)
      @browser.wait_until {@browser.text.include? message}
    end

    def tweet_button_is_disabled?
      @browser.link(self.tweet_button_disable_link.exists?)
    end

  end
end

O que ganhamos utilizando esse helper, você pode estar se perguntando. Basicamente duas vantagens.

Definição mais simples dos elementos web

Ganhamos três métodos  pra cada elemento:

  • um pra setar o valor do elemento (setter): self.username =
  • outro pra retornar o valor do elemento (getter): self.username
  • e por fim um que retorna o próprio objeto do elemento: username_text_field (podemos utilizar qualquer método do objeto TextField)
  • o NOME_DO_SEU_ELEMENTO_text_field é para o caso do elemento ser da classe TextField. Se for um div, seria _div, se fosse um button seria _button e assim vai.

Manutenção mais controlada

Como você viu no começo do post, não é difícil a gente precisar dá manutenção nos nossos testes, e definindo desta forma os elementos que utilizamos, precisamos alterar apenas em um lugar eles.

Por exemplo, se a classe do div com o tweet mudou de tweet-text para apenas tweet, basta mudar a definição desse div, ficando assim: div(:message, :class => ‘tweet’)

Ambas vantagens são mais evidentes ainda, quando temos suítes maiores de testes.

Qualquer dúvida a respeito do uso do helper ou do próprio Watir, abuse e use dos comentários. Lembrando que o helper, foi disponibilizado pelo Jeff Morgan no seu GitHub.

O projeto completo deste post, está disponível no meu GitHub. Aliás, você pode vê a evolução do projeto navegando pelas tags do projeto.

Abraços!