Test de la fonctionnalité utilisateur du site Web avec les objets de page Capybara

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 page

La 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 # ... end end 

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' # It can be beneficial to assert something positive about the page # before progressing with your tests at this point # # This can be useful to ensures that the page has loaded successfully, and any # asynchronous JavaScript has been loaded and retrieved etc. # # This is required to avoid potential race conditions. expect(self).to have_loaded end def has_loaded? self.has_selector? 'h1', text: 'Create Blog' end 

Il est important de choisir les noms sémantiquement corrects pour les méthodes de vos objets de page

 def create(title:, text:) # ... end def has_errors?(*errors) # ... end def has_error?(error) # ... end 

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:

 # ... def create(title:, text:) blog_form.new.create title: title, text: text end # ... private def blog_form ::Components::BlogForm end 

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 } # ... end 

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? # ... end def has_blog?(title:, text:) # ... end 

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 exemples
L'original

Source: https://habr.com/ru/post/fr466527/


All Articles