Malvoyance – vis ma vie numérique avec Marie-Françoise Pierre

Une autre vision du web

Portrait d’un regard

Entrepreneuse, mère de famille et engagée dans diverses associations calédoniennes, Marie-Françoise Pierre passe une grande partie de ses journées devant des écrans. Nous l’avons rencontrée pour nous immerger dans sa vie numérique, qui est adaptée à son handicap : la malvoyance.

Dans cet article, nous allons présenter une vie numérique parmi des millions d’autres, en espérant qu’elle vous aidera à imaginer des sites plus accessibles, mais aussi détecter des problèmes d’accessibilité et en parler pour qu’ils soient corrigés.

Comment voit Marie ?

La déficience oculaire de Marie touche la partie centrale de sa rétine, la macula, ce qui affecte le centre de sa vision. Sa vision périphérique, en revanche, lui permet de se situer dans l’espace et de se déplacer sans problème. Cette déficience l’handicape à 80% ; certaines actions ne lui sont pas possibles, par exemple la conduite.

D’autres types de malvoyances impactent uniquement la vision périphérique, ce qui permet aux personnes de pratiquer des activités demandant une acuité visuelle fine, mais peut représenter un risque important lorsque des dangers surviennent sur les côtés.

Pour Marie, avoir une bonne hygiène de vie est impératif pour voir le mieux possible : une fatigue ou un manque de sommeil peuvent lui rendre encore plus difficile la lecture, voire lui occasionner des vertiges.

Si vous souhaitez expérimenter par vous-mêmes la façon dont Marie voit le monde, vous pouvez télécharger l’application Eye-View (disponible sur iOs et Android) et essayer la vue « DMLA » en agrandissant au maximum la tache grise centrale. Gardez bien votre regard au centre de la tache et laissez votre vision périphérique faire le reste… Pas facile !

Des équipements matériels et logiciels pour plus d’accessibilité

Chez Marie, tout est Mac : ordinateur, tablette et smartphone, car tous ces matériels intègrent directement des fonctions d’accessibilité pour les déficients visuels. Sinon sur PC, Marie doit être équipée du logiciel ZoomText, un logiciel qui coûte cher acheté individuellement (environ 60 000 francs pacifiques). Ces outils permettent de régler la taille du texte, la teinte et le contraste des couleurs, l’apparence du curseur, et propose également une synthèse vocale. Mais le plus souvent, Marie préfère ne pas utiliser la synthèse vocale ; elle a remarqué que celle-ci a tendance à s’entrecouper, répéter plusieurs fois la même chose, ou encore énoncer un autre texte que celui souhaité. Elle préfère lire elle-même les textes, aussi bien dans les visionneuses de documents que dans son client mail ou son navigateur.

Elle maîtrise Siri et a formé d’autres personnes dans le cadre l’association Valentin Hauÿ de Nouméa, mais a l’habitude d’écrire ses mails et autres messages au clavier.

Marie lit avec fluidité à condition que le texte soit à une taille suffisante (police 72 pour un document word affiché à 100%). Il est préférable aussi que la police soit sobre, sans empattements (lire cet article pour comprendre pourquoi !). Pour lire un texte, elle zoome au maximum, fait défiler la page horizontalement avec la souris, dézoome, rezoome… Toute une gymnastique qu’elle fait maintenant sans y penser. Elle explique qu’au départ ça donne un peu envie de vomir, mais qu’on s’y habitue vite.

Le clavier de Marie est tout à fait classique. Pour écrire avec plus d’aisance, elle y a simplement collé deux petits reliefs en plastique qui lui permettent de placer ses mains au bon endroit.

Elle utilise souvent l’appareil photo de son téléphone pour zoomer certains textes rencontrés dans la vie de tous les jours, par exemple des plaques portant un nom de rue, ou les cahiers d’école de ses enfants.

Des bugs auxquels vous n’auriez peut-être jamais pensé

Vous souhaitez construire des logiciels (plus) accessibles ? Voici maintenant des points soulevés par Marie au cours de sa vie d’internaute. Peut-être repenserez-vous à ces cas d’utilisation lors de votre prochaine phase de spécifications… Car une fonctionnalité qui semble parfaite pour une personne valide peut être perçue comme buggée (ou « buggante ») lorsque l’on navigue différemment.

Le menu glissant

Imaginez un site web avec un bandeau comprenant différents onglets thématiques. Lorsque vous survolez un onglet avec la souris, un menu apparaît. Et lorsque vous survolez un item de ce menu, un sous-menu apparaît. Et lorsque la souris quitte soit l’onglet, soit le menu, soit le sous-menu… Tout se replie.

Ce type de design est très courant, en particulier sur les sites de vente ; à peu près gérable quand l’écran est affiché à 100%, il devient cauchemardesque s’il est zoomé à 500%. Comment garder la souris sur ces éléments tout en faisant défiler l’écran horizontalement ? Ces menus interactifs peuvent devenir un casse-tête.

Le carrousel vertigineux

Certains carrousels d’images défilent automatiquement et nécessitent que l’on identifie rapidement les informations utiles et qu’on les lise dans la foulée. Or, pour Marie, l’étape d’identification est plus longue que prévu ; ce qui était censé être une animation agréable et vivante devient une chasse agaçante où l’on joue au chat et à la souris.

Le captcha indéchiffrable

Les captchas sont des outils plus ou moins efficaces pour limiter l’accès des robots sur certaines parties du web. Mais ils ne sont pas toujours accessibles : les lettres indéchiffrables et les contrastes insuffisants bloquent parfois à Marie l’accès à certains services.

Le curseur fugitif

Dans certaines applications, Marie a remarqué que lorsqu’on zoome au maximum un champ où l’on est en train de saisir du texte, le focus de l’écran ne suit pas automatiquement le curseur. Du coup, lorsque le curseur quitte l’écran, on ne voit plus ce qu’on écrit. Elle a remarqué ce comportement en particulier sur son client de messagerie.

La barre qui s’incruste

Quand Marie regarde une vidéo, elle n’a pas le temps de lire les sous-titres. Mais lorsqu’elle a besoin de les lire et qu’elle appuie sur « Pause », il arrive que la barre de lecture cache la partie basse de la vidéo, ce qui masque le texte.

Cette petite série de bugs n’est qu’un échantillon des embûches que l’on peut rencontrer lorsqu’on navigue autrement que ce qui était imaginé par le concepteur d’un site web. La prochaine fois que vous ouvrirez un site (probablement dans moins de quelques minutes), regardez-le avec un oeil différent ; quelles parties vous semblent bien pensées pour l’accessibilité ? Quelles parties laissent à désirer ? Adopter ce regard nécessite de l’entraînement, et des méthodes et outils peuvent vous aider à le faire, comme nous en parlions précédemment.

L’accessibilité, ce n’est pas que pour les autres

Au cours de sa vie professionnelle, Marie a remarqué que les logiciels destinés à un usage interne étaient souvent moins soignés en termes d’ergonomie et d’accessibilité que les logiciels ouverts à un public plus large. Un constat qui laisse pensif, à l’heure où l’insertion des personnes handicapées devrait être un enjeu important des entreprises.

Pourtant, au bureau ou ailleurs, la majorité des mesures d’accessibilité sont profitables autant aux personnes handicapées qu’aux autres. N’importe qui pourrait avoir besoin de capter toutes les infos d’un site sans se baser sur les images (sa connexion étant trop lente pour les charger par exemple), et donc de se servir des attributs de texte alternatif. N’importe qui, souffrant d’une migraine passagère ou de fatigue oculaire, pourrait apprécier un site dont les contrastes respectent les normes d’accessibilité.

Cela est vrai aussi « dans la vraie vie »… Vous le savez s’il vous arrive de préférer monter via la pente douce plutôt que par les escaliers.

N’hésitez pas à partager dans les commentaires votre expérience du handicap, de l’accessibilité et des bugs associés que vous avez rencontrés !

Autres articles sur le thème de l’accessibilité

Chiffres sur l’accessibilité du web calédonien

30 days of accessibility testing

Logs Selenium : passez au niveau big boss

Des logs Selenium lisibles pour tous

Nous parlions de ce syndrome dans un précédent article ; le testeur chargé de l’automatisation des tests est parfois le seul à comprendre ses logs (on a appelé ça la logopathie). Cela réduit l’utilité des automates qui se retrouvent auréolés d’un mystère inutile.

Pour poursuivre dans cette série de bonnes pratiques d’automatisation des tests, nous vous proposons aujourd’hui plusieurs approches pour améliorer vos logs Selenium, sans le moindre framework hipster, juste avec du bon vieux Java. Nous utiliserons un système de logging des plus classiques, à savoir Log4J2, réglé sur la verbosité « INFO ». Libre à vous ensuite d’exploiter ces logs dans l’outil de reporting de votre choix (Allure par exemple). Allez, c’est parti !

Le script Selenium niveau débutant : rien que les logs d’erreur par défaut

Exemple de script Selenium

Voici un script très optimiste qui vérifie sur un célèbre site de vente calédonien que la première Peugeot 107 à vendre coûte 10 000 francs pacifiques ou moins.

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import java.util.concurrent.TimeUnit;

public class TestLogs {

    WebDriver driver;

    @Before
    public void setUp() {
        System.setProperty("webdriver.chrome.driver", "webdrivers/chromedriver.exe");
        driver = new ChromeDriver();
        driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS);
    }

    @Test
    public void testLogs(){
    driver.get("https://voitures.nc/");
        WebElement element = driver.findElement(By.id("recherche"));
        element.sendKeys(Keys.DELETE);
        element.sendKeys("107");
        driver.findElement(By.cssSelector("input[value='OK']")).click();
        driver.findElement(By.xpath("//a[contains(., 'Offre')]")).click();
        driver.findElement(By.xpath("(//table[contains(@href, 'detail_annonce')]//img)[1]")).click();
        Assert.assertTrue("ERREUR : le prix affiché est supérieur à " + 10000, Integer.parseInt(driver.findElement(By.xpath("//div[contains(@id, 'detail')]")).getText(). split("Prix : ")[1].split(" F cfp")[0]. replaceAll("\s+", "")) < 10000);
    }

    @After
    public void tearDown(){
        driver.quit();
    }
}

Problèmes du script Selenium

Comment on va maintenir ça ?

Ce qui saute déjà aux yeux avec ce script, c’est qu’il enfreint la règle de séparation du test et des objets pages. Si le Page Object Model ne vous éveille aucun souvenir, allez jeter un œil à cet article.

Ecrivez tous vos tests de cette façon et vous obtiendrez un magnifique plat de spaghettis impossible à maintenir !

Mais ce qui nous dérange le plus ici, c’est qu’en cas d’échec, on n’aura pas beaucoup d’informations

Exemples de logs cryptiques

Si la première Peugeot 107 affichée coûte plus de 10 000 XPF, on se retrouvera avec ces logs :

java.lang.AssertionError: ERREUR : le prix affiché est supérieur à 10000

at org.junit.Assert.fail(Assert.java:88)
 at org.junit.Assert.assertTrue(Assert.java:41)
 at TestLogs.testLogs(TestLogs.java:33)
 // [...] Et encore plein de lignes qu'on vous épargnera

Et rien avant ce problème d’AssertionError. Là, on a bien le test en tête, on comprend ce qui s’est passé. Cela dit, dans 6 mois, on ne saura plus ce qui était censé coûter 10 000 XPF maximum, sur quel site, etc…

Et si jamais le bouton de recherche n’est pas trouvé, les logs remontent cela, ce qui est moins parlant :

*** Element info: {Using=id, value=recherche}

at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
// [...]

Heureusement que le champ de recherche a un id et que celui-ci est explicite, mais ce n’est pas toujours le cas… Par exemple, sur le site d’Amazon, le champ de recherche a pour id « twotabsearchtextbox », ce qui est un peu moins compréhensible.

Le script Selenium niveau supérieur : chaque étape est logguée

Dans le bloc ci-dessous, le test a bien changé, il répond maintenant au pattern du Page Object Model. Il est beaucoup plus court, et surtout il est plus lisible grâce à des noms de méthodes explicites. Son setup et son teardown sont remontés d’un cran et se trouvent dans une nouvelle classe, « AbstractTest », dont hérite le test ; de cette façon, ils pourront être utilisés dans tous les tests.

La page invoquée, VoituresNcPage, a également son propre objet, qui contient ses propres méthodes.

Et les méthodes de bas niveau utilisées dans l’objet VoituresNcPage sont elles-mêmes remontées dans une classe mère, AbstractPage.

De cette façon, on respecte les célébrissimes principes DRY (don’t repeat yourself) et KISS (keep it super simple).

Le nouveau test Selenium

import org.junit.Test;
import pages.VoituresNcPage;
import utils.AbstractTest;

public class TestLogs extends AbstractTest {

    @Test
    public void testLogs() {
        accesURL("https://voitures.nc/");
        VoituresNcPage voituresNCpage = new VoituresNcPage(this);
        voituresNCpage.rechercherProduit("107");
        voituresNCpage.accesAuxOffres();
        voituresNCpage.classerParPrix();
        voituresNCpage.deplierAnnonceNumero(1);
        voituresNCpage.verifierPrixInferieurOuEgalA(10000);
    }

}

Les méthodes de haut et de bas niveau

Toutes les méthodes de bas niveau donnent lieu à une ligne de log. Les méthodes de vérification donnent lieu à un log en cas d’échec. Un exemple avec la méthode qui permet de classer les produits par prix :

// Dans VoituresNcPage. On trouve les objets et les méthodes spécifiques à la page.

private final By BTN_CLASSER_PAR_PRIX = By.xpath("(//a[contains(@href, 'orderBy=prix')])[1]");
public void classerParPrix(){
   clickElement(BTN_CLASSER_PAR_PRIX);
}

[...]
// Dans AbstractPage. On trouve des méthodes qui pourraient servir à toutes les pages web du monde. Des vérifications sont faites avant chaque interaction, pour vérifier que l'élément concerné est bien présent, et logger ce problème le cas échéant.

protected int timeOut = 5; // Cette valeur sert à configurer combien de temps un élément doit être "attendu" avant qu'on ne considère qu'il est absent de la page.

protected void clickElement(By by) {
 logger.info("Clic sur l'élément '" + getPathFromBy(by) + "'"); // Un premier log de bas niveau
 assertElementPresent(by);
 driver.findElement(by).click();
}

protected String getPathFromBy(By by){
    return by.toString().split(": ")[1]; // On a choisi de faire ce split pour gagner en concision, mais vous aurez peut-être envie de conserver le type de sélecteur.
}

public void assertElementPresent(By by) {
    if(!isElementPresent(by)){
        logger.error("ERREUR : '" + getPathFromBy(by) + "' n'a pas été trouvé(e) sur la page."); // Un deuxième log de bas niveau
    }
    assertTrue(isElementPresent(by));
}

public boolean isElementPresent(By by) {
    boolean isElementPresent = false;
    int time = 0;
    while (!isElementPresent && time < timeOut) {
        if (isElementPresentNow(by)) {
            isElementPresent = true;
        } else {
            isElementPresent = false;
        }
        sleep(1);
        time++;
    }
    return isElementPresent;
}

// Un petit "hack" qui permet d'ignorer temporairement le timeout.
public boolean isElementPresentNow(By by) {
    driver.manage().timeouts().implicitlyWait(0, TimeUnit.MILLISECONDS);
    boolean isElementPresent = (driver.findElements(by).size() != 0);
    driver.manage().timeouts().implicitlyWait(timeOut, TimeUnit.MILLISECONDS);
    return isElementPresent;
}

Les nouveaux logs

Désormais, les logs ressemblent maintenant à ça :

Accès à l'URL https://voitures.nc/
Renseignement du texte '107' dans l'élément 'recherche'
Clic sur l'élément 'input[value='OK']'
Clic sur l'élément '//a[contains(., 'Offre')]'
Clic sur l'élément '(//a[contains(@href, 'orderBy=prix')])[1]'
Clic sur l'élément '(//table[contains(@href, 'detail_annonce')]//img)[1]'
ERREUR : le prix affiché est supérieur à 10000

On comprend un peu mieux ce qui s’est passé. Un testeur n’ayant pas lui-même écrit ce test peut, en se concentrant un peu, retracer le scénario. Ca peut suffire, mais une fois encore, si on veut partager nos logs, on se retrouve dépendants de la clarté des sélecteurs.

Le script Selenium niveau big boss : des logs compréhensibles par tout le monde

Jusqu’à maintenant, rien de bien original. Mais nous voulons maintenant aller un peu plus loin, en permettant à toute personne de comprendre parfaitement les logs, sans avoir besoin du moindre bagage HTML et CSS. On pense par exemple aux intervenants fonctionnels qui auraient besoin de lancer les tests automatisés en autonomie et d’analyser leurs résultats. Chaque étape sera détaillée en langage naturel, joliment et proprement, aussi bien que si on les avait décrites nous-mêmes. A ce stade, les logs du test seront aussi clairs que possible, et surtout à peu de frais. Ca vend du rêve ? Bien sûr que non, car c’est gratos ! C’est parti !

Ce que nous n’allons pas faire

Une solution serait d’étoffer chaque méthode de la classe VoituresNcPage par un log spécifique.

public void classerParPrix(){
   logger.info("Classement des produits par prix");
   clickElement(BTN_CLASSER_PAR_PRIX);
}

Pourquoi pas, si vous avez le temps d’écrire ces logs et que vous vous engagez à les maintenir. Mais on va partir du principe que vous voulez gagner du temps et faire au plus simple.

Notre proposition : bye bye le By

Pour identifier et interagir avec les éléments de la page (boutons, liens, champs…), Selenium propose une classe dédiée : By. Elle permet de les sélectionner par xpath, nom de classe CSS, sélecteur CSS, id, texte de lien hypertexte (ou partie de ce texte), name, balise html…

private final By BTN_CLASSER_PAR_PRIX = By.xpath("(//a[contains(@href, 'orderBy=prix')])[1]");

Mais nous allons laisser tomber ce bon vieux By, ou plutôt l’améliorer pour la beauté de vos logs. Nous allons passer par une nouvelle classe, que nous allons appeler « Selecteur ».

// Dans AbstractPage

public class Selecteur {
   public String nom; // C'est cette variable qui va contenir la description de chaque élément en langage naturel !
   public By chemin;

   public Selecteur(String nom, By chemin){
      this.nom = nom;
      this.chemin = chemin;
   }

   public String getNom(){
      return nom;
   }

   public By getChemin(){
      return chemin;
   }
}

Remplacement des By par des Selecteurs

Dans les méthodes

Maintenant, on va modifier nos méthodes de bas niveau qui utilisent la classe By, en la remplaçant par la classe Selecteur. Par exemple :

// Dans AbstractPage

protected void clickElement(Selecteur selecteur) {
    logger.info("Clic sur " + selecteur.getNom());
    assertElementPresent(selecteur);
    driver.findElement(selecteur.getChemin()).click();
}
protected String getPathFromBy(Selecteur selecteur){
    return selecteur.getChemin().toString().split(": ")[1];
}
public void assertElementPresent(Selecteur selecteur) {
    if(!isElementPresent(selecteur.getChemin())){
        logger.error("ERREUR : " + selecteur.getNom() +
                " n'a pas été trouvé(e) sur la page. " +
                " (sélecteur : '" + getPathFromBy(selecteur) + "')");
    }
    assertTrue(isElementPresent(selecteur.getChemin()));
}
[...]

Dans les objets Page

A cette étape, il reste maintenant à transformer les sélecteurs « By » qui se trouvent dans vos objets pages. Et en premier paramètre, à vous de jouer : vous devez décrire, du mieux que vous pouvez, ce qu’est l’objet d’un point de vue fonctionnel ; à quoi il sert. Vous devez le faire concisément, mais surtout précisément, pour éviter toute ambiguïté.

private final Selecteur BTN_CLASSER_PAR_PRIX = new Selecteur("le bouton permettant de classer les articles par prix", By.xpath("(//a[contains(@href, 'orderBy=prix')])[1]"));

Résultats du refacto

Une fois que c’est fait, on reteste pour voir à quoi ressemblent maintenant les logs !

Accès à l'URL https://voitures.nc/
Renseignement du texte '107' dans le champ de recherche
Clic sur le bouton permettant de valider la recherche
Clic sur le lien vers les offres
Clic sur le bouton permettant de classer les articles par prix
Clic sur le bouton permettant de déplier l'annonce n°1
ERREUR : le prix affiché est supérieur à 10000

Là, on a toutes les informations et on comprend tout. Et que se passe-t-il si on regarde, comme au début de l’article, ce que montrent les logs si jamais le champ de recherche a disparu ?

Accès à l'URL https://voitures.nc/
Renseignement du texte '107' dans le champ de recherche
ERREUR : le champ de recherche n'a pas été trouvé(e) sur la page. (sélecteur : 'recherche')

Bien, on comprend aussi.

[BONUS] Le script Selenium niveau serial-bugkiller : le générateur de rapport de bug

Tels quels, vos logs expliqueront ce qu’on fait les automates de test. En changeant peu de choses à la proposition précédente, vos logs détailleront les étapes à suivre pour reproduire leur comportement. Et du coup, vous mettrez un peu moins de temps à rédiger vos rapports de bugs, car votre partie « Etapes pour reproduire » sera déjà écrite par votre automate, et n’importe qui pourra la comprendre, quel que soit son niveau de connaissance du projet.

Rendez-vous sur l'URL https://voitures.nc/
Renseigner le texte '107' dans le champ de recherche
Cliquer sur le bouton permettant de valider la recherche
Cliquer sur le lien vers les offres
Cliquer sur le bouton permettant de classer les articles par prix
Cliquer sur le bouton permettant de déplier l'annonce n°1
ERREUR : le prix affiché est supérieur à 10000

Vous n’aurez peut-être pas besoin de ce type de logs, ceux de l’étape précédente vous suffiront peut-être, mais cela vous donnera peut-être d’autres idées d’utilisations des logs.

Conclusion

Pour conclure, nous pourrions dire qu’il est illusoire de rechercher une Peugeot 107 à 10 000 XPF ou moins.

Mais aussi, que vos logs sont un atout dont vous pouvez tirer une grande valeur, et à peu de frais. Ce que nous avons exploré dans cet article n’est qu’un exemple parmi d’autres de valorisation des logs.

Et vous, comment utilisez-vous vos logs de tests automatisés ? Comment les avez-vous améliorés au fil du temps ?

Crédit image

L’image de couverture est une reprise de Miss Auras (Le Livre rouge), Sir John Lavery (1856-1941).