Les objets de page peuvent être utilisés comme une puissante méthode d'abstraction (isolation) de vos tests de la mise en œuvre technique. Il est important de se rappeler qu'ils (objets de page) peuvent être utilisés pour augmenter la stabilité des tests et maintenir le principe de
SEC (ne vous répétez pas) - en encapsulant la fonctionnalité (site Web) dans des méthodes simples.
En d'autres termes
Page Object est une instance d'une classe qui extrait (isole) l'interface utilisateur de l'environnement de test, présente des méthodes d'interaction avec l'interface utilisateur et extrait les informations nécessaires.
Terminologie
Le terme
objet de page est un concept trop général. D'après mon expérience,
Page Object comprend les 3 types suivants:
- Les objets composants représentent des composants ou widgets spécifiques dans l'interface utilisateur. Par exemple: tableaux, menus, articles et autres blocs contenant un groupe de composants.
- L'objet de page décrit une zone ou une interface utilisateur spécifique dans une application Web. Il peut être composé de plusieurs objets composants et peut contenir des méthodes pratiques pour interagir avec l'abstraction contenue dans cet objet.
- L'expérience est utilisée pour regrouper des fonctionnalités complexes, dont le test nécessite plusieurs étapes ou une interaction avec plusieurs pages. D'après ma propre expérience, j'ai utilisé ce concept pour résumer un comportement complexe sur une page (tester des pages éducatives, créer un nouvel utilisateur, etc.)
Des exemples
Prenons le simple test
RSpec Capybara , qui crée des blogs et n'utilise pas d'objets de page:
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
Examinons de plus près le code, il a plusieurs problèmes. Il y a les actions suivantes: passer à la page correspondante, interagir avec la page et vérifier le contenu. Une partie du code est dupliquée, mais cela peut être corrigé en adhérant au principe
DRY .
Il est important de comprendre que ce code est difficile à maintenir s'il y a des changements dans l'application testée. Par exemple, les classes d'éléments, les noms et les identificateurs peuvent changer, ce qui nécessite des mises à jour régulières du code de test.
De plus, dans ce code, il n'y a pas de «contexte sémantique», il est difficile de comprendre quelles lignes de code sont regroupées logiquement.
Introduction aux objets de page
Comme indiqué dans la section terminologie, les
objets de page peuvent être utilisés pour représenter des abstractions au niveau de la présentation.
En prenant l'exemple précédent et en utilisant
Page Object pour créer de nouveaux blogs et afficher des blogs, nous pouvons effacer le code de l'exemple précédent.
Après s'être débarrassé des informations spécifiques sur l'implémentation technique, le résultat final (code) doit être lisible et ne doit pas contenir d'informations spécifiques sur l'interface utilisateur (id, classes 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
Création d'objets de pageLa première étape de la création d'
objets de page consiste à créer une structure de
classe de page de base :
module Pages class NewBlog include RSpec::Matchers include Capybara::DSL
Connexion (activation)
Capybara :: DSL pour permettre aux instances de
Page Objects d'utiliser les méthodes disponibles dans
Capybara has_css? '.foo' has_content? 'hello world' find('.foo').click
De plus, j'ai utilisé
inclure RSpec :: Matchers
dans les exemples ci-dessus pour utiliser la bibliothèque RSpec d'
attente .
Ne violez pas l'accord, les
objets de page ne doivent pas inclure les
attentes (attentes) . Cependant, le cas échéant, je préfère que cette approche s'appuie sur
les mécanismes intégrés
de Capybara pour gérer les conditions.
Par exemple, le code
Capybara suivant
s'attendra à la présence de
'foo' à
l' intérieur des
objets Page (dans ce cas,
self ):
expect(self).to have_content 'foo'
Cependant, dans le code suivant:
expect(page_object.content).to match 'foo'
Des erreurs inattendues sont possibles (un test flottant peut se produire), car
page_object.content est immédiatement vérifié pour la conformité avec la condition, et peut ne pas avoir été encore déclaré. Pour plus d'exemples, je recommanderais de lire
les tests d'intégration asynchrone fiables de penséebot avec Capybara .
Création de méthode
Nous pouvons abstraire (décrire) le lieu (région) à partir duquel nous voulons obtenir des données, dans le cadre d'une méthode:
def visit_location visit '/blogs/new'
Il est important de choisir les noms
sémantiquement corrects pour les méthodes de vos
objets de page def create(title:, text:)
En général, il est important de suivre le principe des méthodes fonctionnellement intégrées et, si possible, d'adhérer au principe de la responsabilité unique (principe de responsabilité unique).
Objets composants
Dans notre exemple, nous utilisons la classe NewBlog, mais il n'y a aucune implémentation à créer.
Puisque nous interagissons avec le formulaire, nous pourrions en outre introduire une classe pour représenter ce composant:
Où l'implémentation des méthodes pour BlogForm peut être masquée:
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
Tous ensemble
En utilisant les classes ci-dessus, vous pouvez désormais interroger et instancier les
objets de page
de votre page dans le cadre de la description de l'objet.
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 }
Remarque: J'ai intentionnellement créé un objet page manuellement en haut du fichier objet. Dans certains tests RSpec, il peut être pratique de télécharger automatiquement tous les fichiers de prise en charge et de leur donner accès dans des fichiers objets, mais cela peut entraîner des charges de travail excessives lors de l'utilisation de gros morceaux de code. En particulier, cela entraînera un démarrage lent et des dépendances cycliques imprévues potentielles.
Appeler des objets de page
Désormais, dans chaque scénario, nous aurons accès aux instances de
new_blog_page et
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
Conventions de dénomination / méthodes de prédicat
Comme avec la plupart des choses dans Rails / Ruby, il existe des conventions qui peuvent sembler insignifiantes (non contraignantes) d'un coup d'œil.
Dans nos tests, nous avons interagi avec l'objet page à l'aide de
have_loaded et de
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'
Cependant, les noms de méthode de notre objet page ont réellement
has_loaded? et
has_blog? :
def has_loaded?
Il s'agit d'une distinction subtile qui nécessite de l'attention. Pour plus d'informations sur cette convention, je vous recommande de lire le lien des
appariements de prédicats suivant .
Git, le code source utilisé dans les exemplesL'original