Création d'un outil pour écrire rapidement et efficacement des autotests sur Selenium

Automatisation des blocs de construction fondamentaux - Test
Rod Johnson
image

Je ne suis pas un ambassadeur pour tester les interfaces Web, mais cet essai est plus susceptible d'être utile aux camarades qui ont déjà de l'expérience dans ce domaine.

Il sera également utile pour les débutants, car Je fournis le code source où vous pouvez voir comment l'interaction avec le sélénium est organisée dans le produit final.

Je vais parler de la façon dont, à partir de zéro, ayant peu d'expérience en développement, j'ai écrit une plate-forme pour exécuter des tests, et de la plate-forme elle-même. Je crois moi-même que mon produit s'est avéré très efficace, ce qui signifie qu'il sera utile à beaucoup et a une place pour la considération.

Du concept


Le processus de test dépend du système d'information.

Pour me souvenir de mon concept, il est nécessaire de comprendre sur quels systèmes je me concentre en premier lieu - ce sont des systèmes où il existe généralement des processus métier linéaires spécifiques qui sont définis comme la clé lors des tests de régression.

Donc, un système comme srm. Les offres des fournisseurs constituent une entité commerciale clé. La considération clé dans la conduite des tests de régression est l'intégrité du processus métier.
Le processus commercial commence à partir de l'enregistrement du fournisseur dans le système, puis la création de la proposition commerciale se poursuit - elle passe au stade de la considération, qui est effectué par différents types d'utilisateurs internes (et chaque utilisateur a une interface unique) jusqu'à ce que la décision sur la considération de la proposition au fournisseur soit retournée.

Il s'avère que nous passons par un certain nombre d'interfaces différentes et travaillons presque toujours avec des interfaces différentes. En fait - si vous le regardez directement - cela ressemble à regarder une cassette vidéo - c.-à-d. c'est un processus qui a un début et une fin, et il est absolument linéaire - pas de branchement, lors de l'écriture d'un test, nous connaissons toujours le résultat attendu. C'est-à-dire Je veux dire que déjà en regardant cette image, nous pouvons conclure que la réalisation de tests polymorphes a peu de chances de réussir. Compte tenu de cela, lors de la création d'une plate-forme pour l'exécution de tests, le facteur clé que j'ai défini la vitesse d'écriture des tests.

Les concepts que je me suis fixés:

  1. L'autotest doit être créé le plus rapidement possible. Si vous y parvenez qualitativement, d'autres aspects, tels que la fiabilité et la facilité d'utilisation, devraient venir d'eux-mêmes.
  2. Les tests doivent être déclarés de manière déclarative et vivre séparément du code. Je n'ai même pas vu d'autre option. Cela augmente la vitesse d'écriture, car si vous avez un interprète prêt à l'emploi - notre plate-forme, alors vous n'avez pas besoin d'ajouter quoi que ce soit plus tard, vous n'avez pas à entrer de nouveau dans le code - en général, vous pouvez oublier la plate-forme terminée à l'aide de l'IDE. Les tests sont donc plus faciles à maintenir. Sous cette forme, ils sont plus faciles à apprendre à écrire, car les compétences de développement ne sont pas nécessaires, mais seulement une compréhension du langage de balisage. Sous cette forme, ils sont compréhensibles pour tous les participants au processus.

Ce que j'ai décidé de refuser au départ:

  1. NE PAS envelopper votre système dans un cadre de test. Vous pouvez démarrer l'exécution d'un processus sans framework de test. "Vous voulez inventer un vélo!" - Beaucoup diront. Je pense différemment. Les frameworks de test utilisés populaires ont été créés principalement pour tester le code de l'intérieur, et nous allons tester la partie externe du système de l'extérieur. C'est comme si j'avais un vélo de route et que je devais descendre la montagne hors route (désagréable, mais le train de pensée reflète). En général, nous écrirons le framework nous-mêmes - avec le blackjack et ... (bien que je sache que, par exemple, JUnit 5 est déjà beaucoup plus adapté pour de telles tâches).
  2. Refus d'utiliser des emballages pour le sélénium. En fait, la bibliothèque de clés elle-même est petite. Pour comprendre que vous devez utiliser 5% de ses fonctionnalités, tout en le pelliculant, cela prendra plusieurs heures. Arrêtez de chercher partout un moyen d'écrire moins de code et de vous habituer au petit pot. Dans le monde moderne, ce désir conduit souvent à l'absurdité et cause presque toujours des dommages à la flexibilité (je veux dire les approches pour «écrire moins de code» et non les cas des cadres architecturaux).
  3. Une belle présentation des résultats n'est pas nécessaire. Introduit cet article, car Je n'y suis pas confronté une seule fois. Lorsque l'autotest est terminé, j'ai besoin de savoir 2 choses: le résultat global (positif / négatif) et s'il y a eu une erreur - où exactement. Peut-être avez-vous encore besoin de tenir des statistiques. Tout le reste en termes de résultats n'est ABSOLUMENT pas indispensable. Considérer un beau design comme un atout important, ou passer du temps sur ce beau design dans les étapes initiales - sont des démonstrations superflues.

Je vais parler un peu plus du niveau de développement dans l'entreprise et des conditions de création de l'outil afin de clarifier complètement certains détails.

En raison de certaines circonstances confidentielles, je ne divulgue pas l'entreprise où je travaille.

Dans notre entreprise, le développement existe déjà depuis de nombreuses années et tous les processus sont donc établis depuis longtemps. Cependant, ils sont loin derrière les tendances actuelles.
Tous les représentants de l'informatique comprennent qu'il est nécessaire de couvrir le code avec des tests, d'écrire des scripts pour les autotests au moment de coordonner les exigences pour les fonctionnalités futures, les technologies flexibles économisent considérablement du temps et des ressources, et le CI qui prend et simplifie simplement la vie. Mais tout cela jusqu'à présent ne nous parvient que lentement ...

Il en va de même pour le service de contrôle de la qualité des logiciels - tous les tests sont effectués manuellement, si vous regardez le processus «d'en haut» - c'est le «goulot d'étranglement» de l'ensemble du processus de développement.

Description de l'assemblage


La plateforme est écrite en Java à l'aide de JDK 12

Outils d'infrastructure clés - Selenium Web Driver, OJDBC

Pour que l'application fonctionne, la version 52 du navigateur FireFox doit être installée sur le PC

Composition de construction d'application


image

Avec l'application, 3 dossiers et 2 fichiers sont nécessaires:

• Dossier BuildKit - contient:

  • jdk12, par lequel l'application est lancée (JVM);
  • geckodriver.exe - pour que Selenium Web Driver fonctionne avec le navigateur FireFox;
  • SprintAutoTest.jar - directement l'instance d'application

• Dossier Rapports - les rapports y sont enregistrés une fois que l'application a terminé le scénario de test. Il doit également contenir le dossier ErrorScreens, où la capture d'écran est enregistrée en cas d'erreur

• Dossier TestSuite - packages Web, javascripts, un ensemble de cas de test (remplir ce dossier sera discuté en détail séparément)

• fichier config.properties - contient une configuration pour la connexion à une base de données Oracle et des valeurs d'attentes explicites pour WebDriverWait

• starter.bat - fichier pour lancer l'application (il est possible de lancer automatiquement l'application sans spécifier manuellement TestCase si vous entrez le nom TestCase comme paramètre à la fin).

Brève description de l'application


L'application peut être lancée avec le paramètre (nom TestCase) ou sans lui - dans ce cas, vous devez entrer le nom du cas de test dans la console vous-même.

Un exemple du contenu général d'un fichier de chauve-souris à exécuter sans paramètre : démarrez le lanceur "AutoTest"% cd% \ BuildKit \ jdk-12 \ bin \ java.exe -Xmx768M -jar --enable-preview% cd% \ BuildKit \ SprintAutoTest.jar

Au lancement général de l'application, il regarde les fichiers xml situés dans le répertoire "\ TestSuite \ TestCase" (sans visualiser le contenu des sous-dossiers). Dans ce cas, la validation principale des fichiers xml pour l'exactitude de la structure se produit (c'est-à-dire que toutes les balises du point de vue du balisage xml sont correctes) et les noms indiqués dans la balise "testCaseName" sont pris, après quoi l'utilisateur est invité à entrer l'un des noms possibles pour le test disponible cas. En cas de saisie erronée, le système vous demandera de saisir à nouveau le nom.

Une fois le nom TestCase reçu, le modèle interne est créé, qui est un groupe de TestCase (script de test) - WebPackage (stockage d'éléments) sous la forme d'objets java. Après avoir construit le modèle, TestCase (l'objet exécutable du programme) est construit directement. Au stade de la construction de TestCase, une validation secondaire a également lieu - il est vérifié que tous les formulaires spécifiés dans TestCase sont dans le WebPackage associé et que tous les éléments spécifiés dans l'action se trouvent dans le WebPackage dans les pages spécifiées. (La structure de TestCase et WebPackage est décrite en détail ci-dessous)

Une fois TestCase construit, le script s'exécute directement

Algorithme d'opération de script (logique clé)


TestCase est une collection d'entités Action, qui à son tour est une collection d'entités Event.

Testcase
-> Liste {Action}
-> Liste {événement}

Lorsque TestCase démarre, l'action démarre de manière séquentielle (chaque action renvoie un résultat logique)

Lorsque l'action démarre, l'événement démarre séquentiellement (chaque événement renvoie un résultat logique)

Le résultat de chaque événement est enregistré.

Par conséquent, le test est terminé soit lorsque toutes les actions ont été exécutées avec succès, soit si Action a renvoyé false.

* Mécanisme de crash

Parce que mon système sous test est ancien et a détecté des erreurs / bogues qui ne sont pas des erreurs, et certains événements ne fonctionnent pas la première fois, la plate-forme dispose d'un mécanisme qui peut s'écarter du concept ci-dessus d'un test strictement linéaire (cependant, il est fortement typé). Lors de la capture de telles erreurs, il est possible de répéter les cas en premier et d'effectuer des actions supplémentaires pour pouvoir répéter les actions

À la fin de l'application, un rapport est généré, qui est enregistré dans le répertoire "\ Reports". En cas d'erreur, une capture d'écran est prise, qui est enregistrée dans "\ Reports \ ErrorScreens"

Remplissage TestSuite


Donc, la description du test. Comme déjà mentionné, le paramètre principal nécessaire pour exécuter est le nom du test à exécuter. Ce nom est stocké dans le fichier xml dans le répertoire "/ TestSuite / TestCase". Tous les scripts de test sont stockés dans ce répertoire. Il peut y en avoir n'importe quel nombre. Le nom du scénario de test provient non pas du nom de fichier, mais de la balise «testCaseName» à l'intérieur du fichier.

TestCase définit exactement ce qui sera fait - c'est-à-dire action. Dans le répertoire «/ TestSuite / WebPackage» des fichiers xml, tous les localisateurs sont stockés. C'est-à-dire le tout dans les meilleures traditions - les actions sont stockées séparément, les localisateurs de formulaires Web séparément.

TestCase stocke également le nom du WebPackage dans la balise «webPackageName».

L'image totale est déjà là. Pour exécuter, vous devez avoir 2 fichiers xml: TestCase et WebPackage. Ils constituent un tas. WebPackage est indépendant - l'identifiant est le nom dans la balise «webPackageName». En conséquence, voici la première règle - les noms TestCase et WebPackage doivent être uniques. C'est-à-dire encore une fois - en fait, notre test est un tas de fichiers TestCase et WepPackage, qui sont connectés par le nom WebPackage, qui est spécifié dans TestCase. Dans la pratique, j'automatise un système et je tricote tous mes cas de test dans un WebPackage dans lequel j'ai un tas de descriptions de tous les formulaires.

La couche de décomposition logique suivante est basée sur un modèle tel que Page Object.

Objet Page
Page Object est l'une des solutions architecturales les plus utiles et utilisées en automatisation. Ce modèle de conception permet d'encapsuler le travail avec des éléments de page individuels. Page Object, pour ainsi dire, simule les pages de l'application testée en tant qu'objets.

Séparation de la logique et de la mise en œuvre

Il y a une grande différence entre la logique de test (que vérifier) ​​et son implémentation (comment vérifier). Un exemple de scénario de test: "L'utilisateur entre un nom d'utilisateur ou un mot de passe incorrect, appuie sur le bouton de connexion, reçoit un message d'erreur." Ce script décrit la logique du test, tandis que la mise en œuvre comprend des actions telles que la recherche de champs de saisie sur la page, leur remplissage, la recherche d'erreurs, etc. Et si, par exemple, la méthode d'affichage d'un message d'erreur change, cela n'affectera pas le script de test, vous devrez également entrer des données incorrectes, appuyez sur le bouton de connexion et vérifiez l'erreur. Mais cela affectera directement la mise en œuvre du test - il sera nécessaire de changer la méthode qui reçoit et traite le message d'erreur. En séparant la logique du test de sa mise en œuvre, les autotests deviennent plus flexibles et, en règle générale, plus faciles à maintenir.

*! On ne peut pas dire que cette approche architecturale a été pleinement appliquée. Il s'agit seulement de décomposer la description du script de test page par page, ce qui aide à écrire des tests plus rapidement et à ajouter des vérifications automatiques supplémentaires sur toutes les pages, stimule la description correcte des localisateurs (afin qu'ils ne soient pas les mêmes sur différentes pages) et crée une `` belle '' structure logique du test. La plateforme elle-même est mise en œuvre sur les principes de "l'architecture propre"

Ensuite, je vais essayer de ne pas détailler la structure de WebPackage et TestCase. Pour eux, j'ai créé un schéma DTD pour WebPackage et XSD 1.1 pour TestCase.

! IMPORTANT!


En maintenant les schémas DTD et XSD, le concept d'écriture de test rapide est implémenté.

Lorsque vous écrivez WebPackage et TestCase directement, vous devez utiliser l'éditeur xml avec des fonctions de validation DTD et XSD en temps réel intégrées avec génération automatique de balises, ce qui rendra le processus d'écriture d'un test automatique largement automatisé (toutes les balises requises seront remplacées automatiquement, des listes déroulantes seront affichées pour les valeurs d'attribut valeurs possibles, en fonction du type d'événement, des balises correspondantes seront générées) .

Lorsque ces schémas sont «vissés» au fichier xml lui-même, vous pouvez oublier l'exactitude de la structure du fichier xml, si vous utilisez un environnement spécial. Mon choix s'est porté sur oXygen XLM Editor. Encore une fois - sans utiliser un tel programme, vous ne comprendrez pas l'essence de la vitesse d'écriture. L'idée n'est pas très appropriée pour cela. il ne gère pas la construction «alternative» XSD 1.1, qui est la clé de TestCase.

Webpackage


WebPackaege - un fichier xml qui décrit les éléments des formulaires Web, situé dans le répertoire "\ TestSuite \ WebPackage". (il peut y avoir autant de fichiers que vous le souhaitez. Le nom des fichiers peut être n'importe quoi - seul le contenu compte).

DTD (inséré au début du document):
<!DOCTYPE webPackage [ <!ELEMENT webPackage (webPackageName, forms)> <!ELEMENT webPackageName (#PCDATA)> <!ELEMENT forms (form+)> <!ELEMENT form (formName, elements+)> <!ELEMENT formName (#PCDATA)> <!ELEMENT elements (element+)> <!ELEMENT element (name, locator)> <!ATTLIST element type (0|1|2|3|4|5|6|7) #REQUIRED> <!ATTLIST element alwaysVisible (0|1) #IMPLIED> <!ELEMENT name (#PCDATA)> <!ELEMENT locator (#PCDATA)> <!ATTLIST locator type (1|2) #IMPLIED> ]> 


En général, il semble approximativement
 <webPackage> <webPackageName>_</webPackageName> <forms> <form> <formName>______</formName> <elements> <element type="2" alwaysVisible="1"> <name>_</name> <locator type="2">.//div/form/div/div/form/table/tbody/tr/td[text()=""]/following-sibling::td/input</locator> </element> <element type="2"> <name>__</name> <locator>.//div/form/div/div/form/table/tbody/tr/td[text()=""]/following-sibling::td/input</locator> </element> ....... </elements> </form> ....... </forms> </webPackage> 


Comme déjà mentionné, afin que les éléments ne soient pas en tas - tout est décomposé selon les formulaires Web

L'entité clé est
 <element> 

La balise element a 2 attributs:

  • type
  • toujoursVisible

L'attribut type est obligatoire et spécifie le type d'élément. Dans la plate-forme, définissez le type d'octet

Pour le moment, spécifiquement pour moi-même dans la plate-forme, j'ai mis en œuvre les types suivants:

• 0 - n'a pas de signification fonctionnelle, généralement une sorte d'inscription
• 1 - bouton (bouton)
• 2 - champ de saisie
• 3 - case à cocher (checkBox)
• 4 - liste déroulante (sélectionner) - pas réellement implémenté, mais lui a laissé une place
• 5 - pour la liste déroulante srm: écrivez le nom, attendez que la valeur apparaisse - sélectionnez en fonction du modèle xpath spécifique - saisissez spécifiquement pour mon système
• 6 - srm select - utilisé sur des fonctions typiques telles que la recherche, etc. - taper spécifiquement pour mon système

Attribut AlwaysVisible - facultatif - indique si un élément est toujours présent sur le formulaire, peut être utilisé lors de la validation initiale / finale de l'action (c'est-à-dire qu'en mode automatique, vous pouvez vérifier que lorsque vous ouvrez le formulaire, il contient tous les éléments qui y sont toujours, lors de la fermeture formes, tous ces éléments ont disparu)

Valeurs possibles:

  • 0 - par défaut (ou si l'attribut n'est pas défini) - l'élément peut ne pas être sur la page (ne pas valider)
  • 1 - l'élément est toujours présent sur la page

Un attribut de type facultatif facultatif est implémenté avec la balise de localisation

Valeurs possibles:

  • 1 - recherche d'un élément par id (respectivement, spécifiez seulement id dans le localisateur)
  • 2 - par défaut (ou si l'attribut n'est pas défini) - recherche sur xpath - il est recommandé d'utiliser uniquement la recherche sur xpath, car Cette méthode combine presque tous les avantages du reste et est universelle

Testcase


TestCase - le fichier xml qui décrit directement le script de test se trouve dans le répertoire "\ TestSuite \ TestCase" (il peut y avoir autant de fichiers que vous le souhaitez. Le nom des fichiers peut être n'importe quoi - seul le contenu compte).

Circuit XSD:
 <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.1"> <xs:element name="testCase"> <xs:complexType> <xs:sequence> <xs:element name="testCaseName" type="xs:string"/> <xs:element name="webPackageName" type="xs:string"/> <xs:element name="actions" type="actionsType"/> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="actionsType"> <xs:sequence> <xs:element name="action" type="actionType" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:complexType name="actionType"> <xs:sequence> <xs:element name="name" type="xs:string"/> <xs:element name="orderNumber" type="xs:positiveInteger"/> <xs:element name="runConfiguration" type="runConfigurationType"/> </xs:sequence> </xs:complexType> <xs:complexType name="runConfigurationType"> <xs:sequence> <xs:element name="formName" type="xs:string"/> <xs:element name="repeatsOnError" type="xs:positiveInteger" minOccurs="0"/> <xs:element name="events" type="eventsType"/> <xs:element name="exceptionBlock" type="eventsType" minOccurs="0"/> </xs:sequence> <xs:attribute name="openValidation" use="required"> <xs:simpleType> <xs:restriction base="xs:byte"> <xs:enumeration value="1"/> <xs:enumeration value="0"/> </xs:restriction> </xs:simpleType> </xs:attribute> <xs:attribute name="closeValidation" use="required"> <xs:simpleType> <xs:restriction base="xs:byte"> <xs:enumeration value="1"/> <xs:enumeration value="0"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:complexType name="eventBaseType"> <xs:sequence> <xs:element name="orderNumber" type="xs:positiveInteger"/> </xs:sequence> <xs:attribute name="type" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="goToURL"/> <xs:enumeration value="checkElementsVisibility"/> <xs:enumeration value="checkElementsInVisibility"/> <xs:enumeration value="fillingFields"/> <xs:enumeration value="clickElement"/> <xs:enumeration value="dbUpdate"/> <xs:enumeration value="wait"/> <xs:enumeration value="scrollDown"/> <xs:enumeration value="userInput"/> <xs:enumeration value="checkInputValues"/> <xs:enumeration value="checkQueryResultWithUtilityValue"/> <xs:enumeration value="checkFieldsPresenceByQueryResult"/> </xs:restriction> </xs:simpleType> </xs:attribute> <xs:attribute name="invertResult" use="optional" default="0"> <xs:simpleType> <xs:restriction base="xs:byte"> <xs:enumeration value="1"/> <xs:enumeration value="0"/> </xs:restriction> </xs:simpleType> </xs:attribute> <xs:attribute name="hasExceptionBlock" use="optional" default="0"> <xs:simpleType> <xs:restriction base="xs:byte"> <xs:enumeration value="1"/> <xs:enumeration value="0"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:complexType name="eventsType"> <xs:sequence> <xs:element name="event" type="eventBaseType" maxOccurs="unbounded"> <xs:alternative test="@type='goToURL'" type="eventGoToURL"/> <xs:alternative test="@type='checkElementsVisibility'" type="eventCheckElementsVisibility"/> <xs:alternative test="@type='checkElementsInVisibility'" type="eventCheckElementsVisibility"/> <xs:alternative test="@type='fillingFields'" type="eventFillingFields"/> <xs:alternative test="@type='checkInputValues'" type="eventFillingFields"/> <xs:alternative test="@type='clickElement'" type="eventClickElement"/> <xs:alternative test="@TYPE='dbUpdate'" type="eventRequest"/> <xs:alternative test="@type='wait'" type="utilityValueInteger"/> <xs:alternative test="@type='scrollDown'" type="eventClickElement"/> <xs:alternative test="@type='userInput'" type="eventClickElement"/> <xs:alternative test="@type='checkQueryResultWithUtilityValue'" type="eventRequestWithValue"/> <xs:alternative test="@type='checkFieldsPresenceByQueryResult'" type="eventRequestWithValue"/> </xs:element> </xs:sequence> </xs:complexType> <!--   EVENTS --> <xs:complexType name="eventGoToURL"> <xs:complexContent> <xs:extension base="eventBaseType"> <xs:sequence> <xs:element name="url" type="xs:anyURI"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="eventCheckElementsVisibility"> <xs:complexContent> <xs:extension base="eventBaseType"> <xs:sequence> <xs:element name="fields" type="fieldType"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="eventFillingFields"> <xs:complexContent> <xs:extension base="eventBaseType"> <xs:sequence> <xs:element name="fields" type="fieldTypeWithValue"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="eventClickElement"> <xs:complexContent> <xs:extension base="eventBaseType"> <xs:sequence> <xs:element name="elementName" type="xs:string"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="eventRequest"> <xs:complexContent> <xs:extension base="eventBaseType"> <xs:sequence> <xs:element name="dbRequest" type="xs:string"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="utilityValueInteger"> <xs:complexContent> <xs:extension base="eventBaseType"> <xs:sequence> <xs:element name="utilityValue" type="xs:positiveInteger"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="eventRequestWithValue"> <xs:complexContent> <xs:extension base="eventBaseType"> <xs:sequence> <xs:element name="dbRequest" type="xs:string"/> <xs:element name="utilityValue" type="xs:string"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <!--   EVENTS --> <xs:complexType name="fieldType"> <xs:sequence> <xs:element name="field" maxOccurs="unbounded"> <xs:complexType> <xs:choice> <xs:element name="element" type="xs:string"/> <xs:element name="xpath" type="xs:string"/> </xs:choice> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="fieldTypeWithValue"> <xs:sequence> <xs:element name="field" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="element" type="xs:string"/> <xs:element name="value" type="valueType"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="valueType"> <xs:complexContent> <xs:extension base="xs:anyType"> <xs:attribute name="type" use="optional" default="1"> <xs:simpleType> <xs:restriction base="xs:byte"> <xs:enumeration value="1"/> <xs:enumeration value="2"/> <xs:enumeration value="3"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:extension> </xs:complexContent> </xs:complexType> </xs:schema> 


Vue générale:
 <!DOCTYPE testCase SYSTEM "./TestSuite/TestCase/entities.dtd" []> <testCase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="testShema.xsd"> <testCaseName>__testCase</testCaseName> <webPackageName>_webPackage__webPackageName</webPackageName> <actions> <action> <name>          </name> <orderNumber>10</orderNumber> <runConfiguration openValidation="1" closeValidation="1"> <formName>______</formName> <events> <event type="goToURL"> <orderNumber>10</orderNumber> <url>&srmURL;</url> </event> ....... </events> </runConfiguration> </action> ....... </actions> </testCase> 


Ici, dans cette ligne, vous pouvez voir comment fixer le schéma xsd pour que l'éditeur XML le voie:

 <testCase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="testShema.xsd"> 

Dans TestCase, j'utilise également des entités DTD qui sont stockées séparément dans le même répertoire - un fichier avec l'extension .dtd. Dans ce document, je stocke presque toutes les constantes de données. J'ai également construit la logique de telle manière que pour lancer un nouveau test, et tout au long du test, de nouvelles entités uniques ont été créées, un nouveau vaisseau spatial a été enregistré, il suffisait de changer 1 chiffre dans ce fichier.

Sa structure est très simple - je vais donner un exemple:
 <?xml version="1.0" encoding="UTF-8"?> <!ENTITY mainNumber '040'> <!ENTITY mail '@mail.ru'> <!ENTITY srmURL 'https://srm-test.ru'> 


Ces constantes sont insérées dans la valeur de balise comme suit:

 <url>&srmURL;</url> 

- peut être combiné.

! Recommandation - lors de l'écriture de testCase, vous devez spécifier ces entités DTD dans le document, et après que tout a fonctionné de manière stable, transférez-le dans un fichier séparé. Mon éditeur xml a des difficultés avec cela - il ne peut pas trouver DTD et ne prend pas en compte XSD, donc je le recommande

testCase

testCase - la balise la plus parent contient:

  • testCaseName - le nom de notre cas de test, ce paramètre est passé à l'entrée de l'application
  • webPackageName - le nom de WebPackage, qui est spécifié dans webPackageName (voir le paragraphe ci-dessus sur WebPackage)
  • actions - conteneur d'entité d'action

action

Contient:

  • nom - nom - il est recommandé de spécifier le nom du formulaire et les actions clés - quoi et pourquoi
  • orderNumber - numéro de série - paramètre nécessaire pour l'action de tri (introduit en raison du fait que lors de l'analyse de xml en java à l'aide de certains outils, l'analyse peut être effectuée dans un environnement multi-thread, de sorte que l'ordre peut aller) - lors de la spécification de l'action suivante, vous pouvez sauter - c'est-à-dire lors du tri, il importe seulement «plus / moins», et il est donc possible de livrer une action entre celles déjà décrites sans avoir besoin de changer la numérotation entière
  • runConfiguration - la description réelle de ce qui se passera dans le cadre de l'action

runConfiguration

Contient:

  • Attribut openValidation - facultatif, la valeur par défaut est "0"
    • 0 - ne pas effectuer la validation initiale du formulaire
    • 1 - validation initiale du formulaire
  • attribut closeValidation - facultatif, la valeur par défaut est "0"
    • 0 - ne procède pas à la validation du formulaire final
    • 1 - validation finale du formulaire
  • formName - le nom du formulaire dans lequel les actions seront effectuées - la valeur formName de WebPackage
  • repeatsOnError - facultatif, indique le nombre de répétitions à effectuer en cas d'échec
  • événements - conteneur d'entité d'événement
  • exceptionBlock - facultatif - un conteneur d'entités d'événement qui sont exécutées en cas d'erreur

événement

Unité structurelle minimale - cette entité montre quelles actions sont effectuées

Chaque événement est spécial, peut avoir des balises et des attributs uniques.

Le type de base contient:

  • attribut type - indique le type d'élément
  • l'attribut hasExceptionBlock est un attribut facultatif, par défaut "0" est nécessaire pour implémenter le mécanisme d'échec - l'attribut indique que nous pouvons nous attendre à une erreur sur cet événement
    • 0 - aucune erreur attendue
    • 1 - une erreur possible est attendue sur l'action
  • attribut invertResult - attribut facultatif, par défaut "0" - l'attribut indique qu'il est nécessaire de modifier le résultat de l'événement
    • 0 - laisser le résultat de l'événement
    • 1 - changer le résultat de l'événement à l'opposé

*! Un mécanisme pour décrire l'erreur attendue
Permettez-moi de vous donner un exemple trivial de l'endroit où je l'ai utilisé pour la première fois et de ce qu'il faut faire pour que cela fonctionne.

Cas: entrée Captcha. Pour le moment, je ne pouvais pas automatiser, pour ainsi dire, une vérification du crash du robot jusqu'à présent - ils ne m'écrivent pas un service de test captcha (mais il est plus facile pour moi de créer un réseau neuronal pour la reconnaissance))) Donc, nous pouvons faire une erreur en entrant. Dans ce cas, je crée un événement de contrôle dans lequel je vérifie que nous n'avons pas d'élément - une notification concernant un code de contrôle incorrect, je mets l'attribut hasExceptionBlock dessus. Auparavant, j'ai demandé une action pour que nous puissions avoir plusieurs répétitions (5) et après tout, j'ai enregistré exceptionBlock, dans lequel j'ai écrit que je devais appuyer sur le bouton de sortie pour la notification, puis l'action a été répétée.

Exemples de mon contexte.

Voici comment j'ai enregistré l'événement:

 <event type="checkElementsInVisibility" hasExceptionBlock="1"> <orderNumber>57</orderNumber> <fields> <field> <element>___</element> </field> </fields> </event> 

Et ici exceptionBlock après les événements

  <exceptionBlock> <event type="clickElement"> <orderNumber>10</orderNumber> <elementName>_____</elementName> </event> </exceptionBlock> 

Et oui, les actions sur une page peuvent être décomposées en plusieurs actions.

+ qui a remarqué 2 paramètres dans la config: defaultTimeOutsForWebDriverWait, lowTimeOutsForWebDriverWait. Voilà pourquoi ils le sont. Parce que J'ai le pilote Web entier dans un singleton, et je ne voulais pas créer un nouveau WebDriverWait à chaque fois, alors j'en ai un rapide et c'est en cas d'erreur (enfin, ou si vous venez de mettre hasExceptionBlock = "1", alors ce sera stupide avec moins de temps attente explicite) - eh bien, vous devez être d'accord, attendez une minute pour vous assurer que le message n'est pas sorti comme il faut, ainsi que créer un nouveau WebDriverWait à chaque fois. Eh bien, cette situation de quel côté ne colle pas nécessite une béquille - j'ai décidé de le faire.

Types d'événements


Ici, je vais donner un ensemble minimal de mes événements, comme un ensemble de reconnaissance, avec lequel je peux tester presque tout sur mon système.

Et maintenant, le code est fluide pour comprendre ce qu'est un événement et comment il est construit. Le code implémente essentiellement le framework. J'ai 2 classes - DataBaseWrapper et SeleniumWrapper. Dans ces classes, l'interaction avec les composants d'infrastructure est décrite et les fonctionnalités de la plateforme sont également reflétées. Je vais donner l'interface qui implémente SeleniumWrapper

 package logic.selenium; import models.ElementWithStringValue; import models.webpackage.Element; import org.openqa.selenium.WebElement; public interface SeleniumService { void initialization(boolean webDriverWait); void nacigateTo(String url); void refreshPage(); boolean checkElementNotPresent(Element element); WebElement findSingleVisibleElement(Element element); WebElement findSingleElementInDOM(Element element); void enterSingleValuesToWebField(ElementWithStringValue element); void click(Element element); String getInputValue(Element element); Object jsReturnsValue(String jsFunction); //Actions actions void doubleClick(Element element); void moveMouseToElement(Element element); void pressKey(CharSequence charSequence); void getScreenShot(String storage); } 

Il décrit toutes les fonctionnalités du sélénium et des puces de plate-forme de superposition - eh bien, en fait, la puce principale est la méthode "enterSingleValuesToWebField". N'oubliez pas que nous, dans WebPackage, spécifions le type d'élément. Ainsi, comment réagir à ce type lors du remplissage des champs est écrit ici. Nous écrivons 1 fois et oublions. La méthode ci-dessus doit être corrigée par vous-même en premier lieu. Par exemple, les types 5 et 6, actuellement en vigueur, ne conviennent qu'à mon système. Et si vous avez un filtre et que vous devez beaucoup filtrer, et c'est typique (dans votre application Web), mais pour l'utiliser, vous devez d'abord déplacer la souris sur le champ, attendre que certains champs apparaissent, aller à certains, attendre il y a quelque chose, puis allez-y et entrez ... Pressez stupidement le mécanisme d'action 1 fois, donnez un type unique à tout cela dans la structure du commutateur - ne vous embêtez pas plus - obtenez une méthode polymorphe pour tous les filtres d'application similaires.

Ainsi, dans le package «package logic.testcase.events», il existe une classe abstraite qui décrit les actions générales de l'événement. Afin de créer votre propre événement unique, vous devez créer une nouvelle classe, hériter de cette classe abstraite, et vous avez déjà dataBaseService et seleniumService dans le kit - puis vous déterminez de quelles données vous avez besoin et quoi en faire. Quelque chose comme ça. Eh bien et en conséquence, après avoir créé un nouvel événement, vous devez terminer la classe d'usine TestCaseActionFactory et, si possible, le schéma XSD. Eh bien, si un nouvel attribut est ajouté - modifiez le modèle lui-même. En fait, c'est très simple et rapide.

Donc, un scout set.

goToURL - généralement la première action - cliquez sur le lien spécifié

Un exemple:
 <event type="goToURL"> <orderNumber>10</orderNumber> <url>testURL</url> </event> 


fillFields - Remplissage des éléments spécifiés

Tags spéciaux:

  • champs - champ conteneur d'entité
    • champ - contient la balise element
      • élément - indique le nom de l'élément de webPackage
      • valeur - quelle valeur indiquer, a un attribut de type facultatif (si l'élément est une case à cocher, alors l'une des valeurs est indiquée: "cocher" ou "décocher")

  • attribut type - indique comment prendre la valeur, facultatif, la valeur par défaut est "1"
    • 1 - la valeur spécifiée est prise
    • 2 - dans ce cas, la fonction JS spécifiée du répertoire "\ TestSuite \ JS" est exécutée! IMPORTANT - le nom du fichier txt est indiqué, sans ".txt" (et jusqu'à présent, j'ai trouvé des applications pour les fonctions js jusqu'à présent uniquement sous cette forme - je l'utilise en un seul endroit pour générer une auberge aléatoire, mais le spectre des applications possibles est large)
    • 3 - dans ce cas, la requête dans la base de données est indiquée comme valeur, et le programme substitue le premier résultat de cette requête

Un exemple:
 <event type="fillingFields"> <orderNumber>10</orderNumber> <fields> <field> <element>test</element> <value>test</value> </field> </fields> </event> 


checkElementsVisibility - vérifie que les éléments spécifiés sont présents sur le formulaire (à savoir, visible, et pas seulement dans le DOM). Dans l'attribut field, un élément de WebPackage ou directement xpath peut être spécifié

Un exemple:
 <event type="checkElementsVisibility"> <orderNumber>10</orderNumber> <fields> <field> <element>test</element> </field> <field> <xpath>test</xpath> </field> </fields> </event> 


checkElementsInVisibility - similaire à checkElementsVisibility, mais vice versa

clickElement - cliquez sur l'élément spécifié

Un exemple:
 <event type="clickElement"> <orderNumber>10</orderNumber> <elementName>test</elementName> </event> 


checkInputValues - vérifie les valeurs entrées

Un exemple:
 <event type="checkInputValues"> <orderNumber>10</orderNumber> <fields> <field> <element>test</element> <value>test</value> </field> </fields> </event> 


dbUpdate - effectuer une mise à jour dans la base de données ( oXygen réagit étrangement à 1 événement du type dbUpdate - je ne sais pas quoi en faire et je ne comprends pas pourquoi )

Un exemple:
 <event type="dbUpdate"> <orderNumber>10</orderNumber> <dbRequest>update - </dbRequest> </event> 


CheckQueryResultWithUtilityValue - vérifie la valeur entrée par l'utilisateur avec la valeur de la base de données

Un exemple:
 <event type="checkQueryResultWithUtilityValue"> <orderNumber>10</orderNumber> <dbRequest>select ...</dbRequest> <utilityValue>test</utilityValue> </event> 


checkFieldsPresenceByQueryResult - vérifie la présence d'éléments sur le formulaire par xpath par motif. Si le modèle souhaité n'est pas spécifié, la recherche se fera selon le modèle .//* [text () [contient (normaliser-espace (.), "$")]], Où au lieu de "$", il y aura une valeur dans la base de données. Lorsque vous décrivez votre propre modèle, vous devez indiquer "$" à l'endroit où vous souhaitez placer la valeur de la base de données. Dans mon système, il y a ce qu'on appelle des grilles dans lesquelles il y a des valeurs qui sont généralement formées à partir d'une sorte de vue. Cet événement pour tester de telles grilles

Un exemple:
 <event type="checkFieldsPresenceByQueryResult"> <orderNumber>10</orderNumber> <dbRequest>test</dbRequest> <utilityValue></utilityValue> </event> 


Attendre - tout est simple - attendre le nombre spécifié de millisecondes. Malheureusement, même si cela est considéré comme une béquille, je vais dire avec certitude - parfois, il est impossible de s'en passer

Un exemple:
 <event type="wait"> <orderNumber>10</orderNumber> <utilityValue>1000</utilityValue> </event> 


scrollDown - faites défiler vers le bas à partir de l'élément spécifié. C'est fait de cette façon: il clique sur l'élément spécifié et appuie sur la touche «PgDn». Dans mes cas où j'ai dû faire défiler vers le bas, cela fonctionne très bien:

Un exemple:
 <event type="scrollDown"> <orderNumber>10</orderNumber> <elementName>test</elementName> </event> 


userInput - entrez une valeur dans l'élément spécifié. Le seul appareil semi-automatique de mon automatisation, utilisé uniquement pour le captcha. L'élément dans lequel entrer la valeur est indiqué. La valeur est entrée dans la boîte de dialogue contextuelle.

Un exemple:
 <event type="userInput"> <orderNumber>10</orderNumber> <elementName>capch_input</elementName> </event> 


À propos du code


J'ai donc essayé de rendre la plateforme conforme aux principes de l'architecture propre de l'oncle Bob.

Forfaits:

application - initialisation et lancement + configs et rapport (ne me grondez pas pour la classe Report - c'est ce qui le fait le plus vite possible, puis le plus vite possible)

logique - la logique clé + services de Selenium et DB. Il y a des événements.

modèles - POJO en XML et toutes les classes d'objets d'assistance

utils - singleton pour sélénium et db

Pour exécuter le code, vous devez télécharger jdk 12 et spécifier partout pour que ses puces s'allument. Dans Idea, cela se fait via Project Structure -> Modules and Project. N'oubliez pas non plus le coureur Maven.Et lorsque vous démarrez dans le fichier bat, ajoutez --enable-preview. Un exemple était.

Eh bien, pour tout démarrer, par JDK, vous devrez télécharger le pilote ojdbc et déposer le dzharnik dans le répertoire «SprintAutoTest \ src \ lib». Je ne le fournis pas, car en ce moment, tout est sérieux avec Oracle - pour le télécharger, il faut s'inscrire, mais je suis sûr que tout le monde s'en sortira d'une manière ou d'une autre (enfin, assurez-vous que tous les dossiers sont créés, sinon le rapport ne sera pas enregistré)

Résumé


, , . 1,5 , 5 – 6 . 3700 830 ( 4800 ). , , , , . – -, - , -, , , ( – closeValidation . , , , action, ).

, xml, , (.. 2 – 1- xml , 2- TestCase).

– . , , . :

:

  • + — « » + — ( – .. -)
  • ( action — event c + )
  • , , – -. , – . , . , – ,
  • unit ( true false)

-, , , Cucumber BDD ( ):

  • . C'est-à-dire , «When», , .
  • . ( , ). , Given When – 99% , – , , .

, , :

  • ,
  • , , ,
  • faire un contrôle de version centralisé. Pas seulement git, mais pour indiquer quelle version du test exécuter, enfin, ou, encore une fois, un module intelligent qui comprend quelle version est pertinente, laquelle n'est pas encore
  • comme je l'ai dit, pour commencer un nouveau test avec de nouvelles valeurs, je dois changer 1 chiffre, automatiser, pour ainsi dire, faire un module intelligent pour cela
  • bien que cela ne me dérange pas beaucoup que tous les localisateurs soient stockés en un seul endroit, toujours dans le bon sens, je devrais créer une structure encore plus conviviale pour les stocker
  • n'a pas barboté avec le serveur de sélénium. Je pense que cela vaut la peine d’y penser, ainsi que la possibilité d’une adaptation supplémentaire à CI, Team City, etc.

Eh bien, c'est tout. Je joins une référence à github: codes source .

Je serai très heureux des critiques constructives, j'espère que ce projet sera vraiment utile.

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


All Articles