Los objetos de página se pueden usar como un poderoso método de abstracción (aislamiento) de sus pruebas de la implementación técnica. Es importante recordar que estos (Objetos de página) se pueden usar para aumentar la estabilidad de las pruebas y mantener el principio de
DRY (no se repita), encapsulando la funcionalidad (sitio web) en métodos simples.
En otras palabras
Page Object es una instancia de una clase que abstrae (aísla) la interfaz de usuario del entorno de prueba, presenta métodos para interactuar con la interfaz de usuario y extrae la información necesaria.
Terminología
El término
Objeto de página es un concepto demasiado general. En mi experiencia,
Page Object incluye los siguientes 3 tipos:
- Los objetos componentes representan componentes o widgets específicos en la interfaz de usuario. Por ejemplo: tablas, menús, artículos y otros bloques que contienen un grupo de componentes.
- Objeto de página describe un área específica o interfaz de usuario en una aplicación web. Puede consistir en varios Objetos componentes y puede contener métodos convenientes para interactuar con la abstracción contenida dentro de este objeto.
- La experiencia se utiliza para agrupar funcionalidades complejas, cuya prueba requiere varios pasos o interacción con varias páginas. Desde mi propia experiencia, utilicé este concepto para abstraer comportamientos complejos en una página (probar páginas educativas, crear un nuevo usuario, etc.)
Ejemplos
Considere la simple prueba
RSpec Capybara , que crea blogs y no utiliza objetos de página:
require 'feature_helper' feature 'Blog management', type: :feature do scenario 'Successfully creating a new blog' do visit '/' click_on 'Form Examples' expect(page).to have_content('Create Blog') fill_in 'blog_title', with: 'My Blog Title' fill_in 'blog_text', with: 'My new blog text' click_on 'Save Blog' expect(page).to have_selector('.blog--show') expect(page).to have_content('My Blog Title') expect(page).to have_content('My new blog text') end scenario 'Entering no data' do visit '/' click_on 'Form Examples' expect(page).to have_content('Create Blog') click_on 'Save Blog' expect(page).to have_content('4 errors stopped this form being submitted') expect(page).to have_content("Title can't be blank") expect(page).to have_content("Text can't be blank") expect(page).to have_content('Title is too short') expect(page).to have_content('Text is too short') end end
Echemos un vistazo más de cerca al código; tiene varios problemas. Existen las siguientes acciones: transición a la página correspondiente, interacción con la página y verificación del contenido. Parte del código está duplicado, pero esto puede solucionarse siguiendo el principio
DRY .
Es importante comprender que este código es difícil de mantener si hay cambios en la aplicación bajo prueba. Por ejemplo, las clases de elementos, los nombres y los identificadores pueden cambiar, lo que requiere actualizaciones periódicas del código de prueba.
Además, en este código no existe un 'contexto semántico', es difícil entender qué líneas de código están agrupadas lógicamente.
Introducción a los objetos de página
Como se discutió en la sección de terminología, los
objetos de página se pueden usar para representar abstracciones a nivel de presentación.
Tomando el ejemplo anterior y usando
Page Object para crear nuevos blogs y ver blogs, podemos borrar el código del ejemplo anterior.
Una vez eliminada la información específica sobre la implementación técnica, el resultado final (código) debe ser legible y no debe contener información específica sobre la interfaz de usuario (id, clases css, etc.).
require 'feature_helper' require_relative '../pages/new_blog' require_relative '../pages/view_blog' feature 'Blog management', type: :feature do let(:new_blog_page) { ::Pages::NewBlog.new } let(:view_blog_page) { ::Pages::ViewBlog.new } before :each do new_blog_page.visit_location end scenario 'Successfully creating a new blog' do new_blog_page.create title: 'My Blog Title', text: 'My new blog text' expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text' end scenario 'Entering no data' do new_blog_page.create title: '', text: '' expect(view_blog_page).to_not have_loaded expect(new_blog_page).to have_errors "Title can't be blank", "Text can't be blank", "Title is too short", "Text is too short" end end
Crear objetos de páginaEl primer paso para crear
objetos de página es crear una estructura de
clase de página básica :
module Pages class NewBlog include RSpec::Matchers include Capybara::DSL
Conectar (habilitar)
Capybara :: DSL para permitir que las instancias de
Objetos de página utilicen los métodos disponibles en
Capybara has_css? '.foo' has_content? 'hello world' find('.foo').click
Además, usé
incluye RSpec :: Matchers
en los ejemplos anteriores para usar la biblioteca de
expectativas RSpec.
No viole el acuerdo, los
Objetos de página no deben incluir
esperar (expectativas) . Sin embargo, donde sea apropiado, prefiero este enfoque para confiar en
los mecanismos incorporados
de Capybara para manejar las condiciones.
Por ejemplo, el siguiente código de
Carpincho esperará la presencia de
'foo' dentro de
Page Objects (en este caso,
self ):
expect(self).to have_content 'foo'
Sin embargo, en el siguiente código:
expect(page_object.content).to match 'foo'
Son posibles errores inesperados (puede ocurrir una prueba flotante), ya que
page_object.content se verifica de inmediato para verificar el cumplimiento de la condición y es posible que aún no se haya declarado. Para obtener más ejemplos, recomendaría leer
las pruebas de integración asincrónica confiables de escribir de thinkbot con Capybara .
Método de creación
Podemos abstraer (describir) el lugar (región) del que queremos obtener datos, en el marco de un método:
def visit_location visit '/blogs/new'
Es importante elegir los nombres
semánticamente correctos para los métodos para sus
Objetos de página def create(title:, text:)
En general, es importante seguir el principio de métodos funcionalmente integrados y, cuando sea posible, adherirse al principio de responsabilidad única (Principio de responsabilidad única).
Objetos componentes
En nuestro ejemplo, usamos la clase NewBlog, pero no hay implementación para crear.
Como interactuamos con el formulario, también podríamos introducir una clase para representar este componente:
Donde se puede ocultar la implementación de métodos para BlogForm:
module Components class BlogForm include RSpec::Matchers include Capybara::DSL def create(title:, text:) within blog_form do fill_in 'blog_title', with: title fill_in 'blog_text', with: text click_on 'Save Blog' end end private def blog_form find('.blog--new') end end end
Todos juntos
Usando las clases anteriores, ahora puede consultar e instanciar los
Objetos de página
de su página como parte de la descripción del objeto.
require 'feature_helper' require_relative '../pages/new_blog' require_relative '../pages/view_blog' feature 'Blog management', type: :feature do let(:new_blog_page) { ::Pages::NewBlog.new } let(:view_blog_page) { ::Pages::ViewBlog.new }
Nota: intencionalmente creé un objeto de página manualmente en la parte superior del archivo de objeto. En algunas pruebas de RSpec, puede ser conveniente descargar automáticamente todos los archivos de soporte y proporcionarles acceso a ellos en archivos de objetos, sin embargo, esto puede generar cargas de trabajo excesivas cuando se utilizan grandes piezas de código. En particular, esto conducirá a un inicio lento y posibles dependencias cíclicas no intencionadas.
Llamar a objetos de página
Ahora, en cada escenario, tendremos acceso a las instancias de
new_blog_page y
view_blog_page :
scenario 'Successfully creating a new blog' do new_blog_page.create title: 'My Blog Title', text: 'My new blog text' expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text' end
Convenciones de nomenclatura / métodos de predicado
Como con la mayoría de las cosas en Rails / Ruby, hay convenciones que pueden parecer insignificantes (no vinculantes) completamente de un vistazo.
En nuestras pruebas, interactuamos con el objeto de página usando
have_loaded y
have_blog :
expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text'
Sin embargo, ¿los nombres de los métodos de nuestro objeto de página realmente se han
cargado? y
has_blog? :
def has_loaded?
Esta es una distinción sutil que necesita atención. Para obtener más información sobre esta convención, recomendaría leer el siguiente enlace de
comparadores de predicados .
Git, el código fuente usado en los ejemplosEl original