Screenplay Pattern : Structurer vos tests pour plus de lisibilité et de robustesse
Dans cette série d'articles sur les design patterns pour l'automatisation des tests, nous avons exploré plusieurs approches pour structurer et organiser nos tests. Aujourd'hui, nous découvrons le Screenplay Pattern, une évolution spécifiquement conçue pour résoudre les limitations du Page Object Model dans le contexte du Behavior-Driven Development (BDD).

Définition et objectif du pattern
Le Screenplay Pattern est un design pattern orienté acteur qui structure les tests automatisés autour de trois concepts fondamentaux :
- Les acteurs (Actors) : qui exécutent les actions
- Les tâches (Tasks) : ce que font les acteurs
- Les interactions (Interactions) : comment les acteurs interagissent avec le système
Objectif principal : Faciliter le passage de l'abstraction comportementale des scénarios métier orientés (en langage Gherkin) à l'implémentation technique, en maintenant une traçabilité claire entre les spécifications métier et le code de test.
Origine du pattern : : Répondre aux limites du POM dans le contexte BDD
Le Screenplay Pattern a été développé par Antony Marcano, Andy Palmer et John Ferguson Smart vers 2013-2015. Il est né de la volonité de dépasser les limitations du Page Object Model en adoptant une approche plus proche du langage métier et des comportements utilisateur. Il a été conçu pour répondre à une limite forte : comment traduire efficacement l’abstraction des comportements métier en actions automatisées, sans dégrader les bonnes pratiques du BDD.
Le problème avec POM et BDD
Avec le Page Object Model, il est difficile voire impossible de maintenir les bonnes pratiques BDD :
# Scenario Gherkin (langage métier)
Scenario: Un client achète un produit
Given Alice est connectée en tant que cliente
When elle recherche "iPhone 15"
And elle ajoute le produit à son panier
And elle finalise sa commande
Then elle voit la confirmation "Commande validée"
Implémentation avec le POM :
// Implémentation POM : rupture avec le langage métier
@When("elle recherche {string}")
public void elle_recherche(String produit) {
HomePage homePage = new HomePage(driver);
ProductListPage productPage = homePage.searchProduct(produit);
// Logique métier mélangée avec logique technique
}
Avec le Page Object Model, on atteint vite des blocages :
- Difficile d’exprimer des scénarios lisibles et riches côté métier.
- Risque d’avoir des tests qui décrivent trop le “comment” au lieu du “quoi”.
Philosophie fondamentale
Le Screenplay Pattern apporte une réponse grâce à sa philosophie Actor - Task - Interaction :
- Actor (Acteur) : Représente un utilisateur ou un rôle métier (client, administrateur, etc.)
- Task (Tâche) : Une action métier de haut niveau (se connecter, passer commande, etc.)
- Interaction (Interaction) : Actions atomiques sur l'interface (cliquer, saisir, etc.)
Cette philosophie permet d'écrire des tests qui se lisent comme des scénarios métier naturels et permet d’écrire vos tests sous forme de scénarios joués par des acteurs, qui réalisent des tâches métier composées d’interactions techniques.
Architecture du Screenplay Pattern

Composants clés
- Actor : Point d'entrée qui orchestre les actions
- Task : Encapsule une séquence d'interactions pour accomplir un objectif métier
- Interaction : Action élémentaire sur l'interface utilisateur ( souvent en Page Object Model)
- Ability : Capacité technique (WebDriver, API client, etc.)
- Question : Permet d'interroger l'état du système
Exemple concret : Scénario d'achat utilisateur multi-pages
Prenons l'exemple d'un processus d'achat en ligne avec plusieurs étapes.
Structure du projet
src/
├── actors/
│ └── Actor.java
├── tasks/
│ ├── Login.java
│ ├── SearchProduct.java
│ ├── AddToCart.java
│ └── Checkout.java
├── interactions/
│ ├── Click.java
│ ├── Enter.java
│ └── Select.java
├── abilities/
│ └── BrowseTheWeb.java
├── questions/
│ ├── CartTotal.java
│ └── OrderConfirmation.java
└── ui/
├── LoginPage.java
├── ProductPage.java
└── CartPage.java
Implémentation
1. L'Actor
public class Actor {
private String name;
private List<Ability> abilities = new ArrayList<>();
public static Actor named(String name) {
return new Actor(name);
}
public Actor whoCan(Ability... abilities) {
this.abilities.addAll(Arrays.asList(abilities));
return this;
}
public void attemptsTo(Task... tasks) {
Arrays.stream(tasks).forEach(task -> task.performAs(this));
}
public <T> T asksFor(Question<T> question) {
return question.answeredBy(this);
}
}
2. Les Tasks
// Task de connexion
public class Login implements Task {
private final String username;
private final String password;
public static Login withCredentials(String username, String password) {
return new Login(username, password);
}
@Override
public void performAs(Actor actor) {
actor.attemptsTo(
Click.on(LoginPage.LOGIN_BUTTON),
Enter.theValue(username).into(LoginPage.USERNAME_FIELD),
Enter.theValue(password).into(LoginPage.PASSWORD_FIELD),
Click.on(LoginPage.SUBMIT_BUTTON)
);
}
}
// Task de recherche de produit
public class SearchProduct implements Task {
private final String productName;
public static SearchProduct named(String productName) {
return new SearchProduct(productName);
}
@Override
public void performAs(Actor actor) {
actor.attemptsTo(
Enter.theValue(productName).into(HomePage.SEARCH_FIELD),
Click.on(HomePage.SEARCH_BUTTON)
);
}
}
// Task d'ajout au panier
public class AddToCart implements Task {
private final String productName;
public static AddToCart product(String productName) {
return new AddToCart(productName);
}
@Override
public void performAs(Actor actor) {
actor.attemptsTo(
Click.on(ProductPage.productWithName(productName)),
Select.option("1").from(ProductPage.QUANTITY_DROPDOWN),
Click.on(ProductPage.ADD_TO_CART_BUTTON)
);
}
}
3. Les Interactions
public class Click implements Interaction {
private final By locator;
public static Click on(By locator) {
return new Click(locator);
}
@Override
public void performAs(Actor actor) {
WebDriver driver = actor.abilityTo(BrowseTheWeb.class).getDriver();
driver.findElement(locator).click();
}
}
public class Enter implements Interaction {
private final String text;
private By target;
public static Enter theValue(String text) {
return new Enter(text);
}
public Enter into(By locator) {
this.target = locator;
return this;
}
@Override
public void performAs(Actor actor) {
WebDriver driver = actor.abilityTo(BrowseTheWeb.class).getDriver();
driver.findElement(target).clear();
driver.findElement(target).sendKeys(text);
}
}
4. Le test final
java
@Test
public void customerCanCompletePurchase() {
// Given
Actor customer = Actor.named("Alice")
.whoCan(BrowseTheWeb.with(driver));
// When
customer.attemptsTo(
Login.withCredentials("alice@example.com", "password123"),
SearchProduct.named("iPhone 15"),
AddToCart.product("iPhone 15"),
Checkout.withPaymentMethod("Credit Card")
);
// Then
assertThat(customer.asksFor(OrderConfirmation.message()))
.contains("Votre commande a été confirmée");
assertThat(customer.asksFor(CartTotal.amount()))
.isEqualTo(999.00);
}
Comparatif POM vs Screenplay
Le scénario métier
# Scenario Gherkin (langage métier)
Scenario: Un client achète un produit
Given Alice est connectée en tant que cliente
When elle recherche "iPhone 15"
And elle ajoute le produit à son panier
And elle finalise sa commande
Then elle voit la confirmation "Commande validée"
Page Object Model
// Approche POM traditionnelle
@Test
public void testLogin() {
LoginPage loginPage = new LoginPage(driver);
HomePage homePage = loginPage.login("user", "pass");
ProductPage productPage = homePage.searchProduct("iPhone");
CartPage cartPage = productPage.addToCart();
assertTrue(cartPage.isProductInCart("iPhone"));
}
Limitations critiques :
- Mélange des responsabilités dans les step definitions
- Logique métier dispersée entre les pages et les steps
- Difficile de réutiliser les actions entre différents scénarios
- Couplage fort à la structure des pages UI et donc difficile à maintenir lors de changements d'UI
- Perte de l'expressivité métier du Gherkin
- Risque d’avoir des tests qui décrivent trop le “comment” au lieu du “quoi”.
Screenplay Pattern
// Approche Screenplay
@Test
public void customerCanPurchaseProduct() {
Actor customer = Actor.named("Customer")
.whoCan(BrowseTheWeb.with(driver));
customer.attemptsTo(
Login.withCredentials("user", "pass"),
SearchProduct.named("iPhone"),
AddToCart.theSelectedProduct()
);
assertThat(customer.asksFor(CartContents.products()))
.contains("iPhone");
}
Avantages :
- ✅ Langage métier naturel
- ✅ Séparation claire des responsabilités
- ✅ Réutilisabilité des tâches
- ✅ Maintenabilité accrue
- ✅ Tests plus expressifs
Bonnes pratiques
1. Nommage expressif
// ❌ Mauvais
actor.attemptsTo(DoSomething.with("data"));
// ✅ Bon
customer.attemptsTo(Login.withCredentials("john@doe.com", "password"));
2. Composition de tâches
public class CompletePurchase implements Task {
private final Product product;
private final PaymentMethod paymentMethod;
@Override
public void performAs(Actor actor) {
actor.attemptsTo(
SearchProduct.named(product.getName()),
AddToCart.theSelectedProduct(),
Checkout.withPaymentMethod(paymentMethod)
);
}
}
3. Assertions expressives
// ❌ Assertion technique
WebElement element = driver.findElement(By.id("message"));
assertEquals("Success", element.getText());
// ✅ Assertion métier
assertThat(customer.asksFor(TheNotificationMessage.displayed()))
.isEqualTo("Votre commande a été traitée avec succès");
4. Gestion des erreurs
public class HandleErrors implements Task {
@Override
public void performAs(Actor actor) {
try {
actor.attemptsTo(SomeRiskyTask.perform());
} catch (TaskException e) {
actor.attemptsTo(RecoverFromError.gracefully());
}
}
}
6. Paramétrage flexible
public class Login implements Task {
public static Login asStandardUser() {
return withCredentials(TestData.STANDARD_USER);
}
public static Login asAdminUser() {
return withCredentials(TestData.ADMIN_USER);
}
public static Login withCredentials(Credentials credentials) {
return new Login(credentials);
}
}
7. Respecter les bonnes pratiques du BDD
- Penser métier avant technique : écrire vos tasks au niveau du langage business.
- Découper les tâches : une task doit refléter une intention claire, pas 50 clics enchaînés.
- Ne pas dupliquer les interactions : centralisez-les (click, enter, select…).
- Collaborer avec les PO/BA : Screenplay est un pont naturel avec les scénarios BDD.
- Accepter la complexité initiale : l’investissement est largement rentabilisé sur le long terme.
FAQ
Q : Quand utiliser Screenplay plutôt que POM ?
R : Screenplay est recommandé pour :
- Les applications complexes avec de nombreux workflows
- Les équipes mixtes (développeurs, testeurs, product owners)
- Les projets nécessitant une maintenance à long terme
- Les tests orientés comportement utilisateur
Q : Screenplay n'est-il pas trop complexe pour débuter ?
R : La courbe d'apprentissage est plus élevée initialement, mais l'investissement est rapidement rentabilisé par :
- La facilité de maintenance
- La réutilisabilité des composants
- La lisibilité des tests
- La collaboration facilitée entre équipes
Q : Comment gérer les données de test avec Screenplay ?
R : Plusieurs approches sont possibles :
// Factory pattern
Actor customer = TestActors.standardCustomer();
// Builder pattern
Actor customer = Actor.named("John")
.withEmail("john@test.com")
.withRole(CustomerRole.PREMIUM)
.whoCan(BrowseTheWeb.with(driver));
// Données externes
Actor customer = Actor.fromDataSet("customer1.json");
Q : Peut-on combiner Screenplay avec d'autres patterns ?
R : Absolument ! Screenplay fonctionne très bien avec :
- Factory Pattern : pour créer les acteurs
- Builder Pattern : pour construire des tâches complexes
- Strategy Pattern : pour différents types d'interactions
- Observer Pattern : pour les rapports et logs
Q : Screenplay impacte-t-il les performances des tests ?
R : L'impact est négligeable. La petite surcharge liée à l'abstraction est largement compensée par :
- Une meilleure organisation du code
- Moins de duplication
- Des tests plus stables
- Une maintenance simplifiée
Le temps gagné en développement et maintenance dépasse largement le coût en performance.
Q : Pourquoi Screenplay est-il mieux adapté au BDD ?
R : Parce qu’il permet de relier naturellement les scénarios Gherkin (Given/When/Then) à des tasks métier, ce qui est compliqué, voire impossible, avec POM seul.
A retenir
Le Screenplay Pattern représente une évolution majeure dans l'approche des tests automatisés. En adoptant une perspective orientée acteur et tâche, il transforme nos tests en véritables spécifications exécutables, facilitant la collaboration et la maintenance à long terme. Il permet de passer du comment (clics, champs, sélecteurs) au quoi (intentions métier), tout en restant fidèle à l’esprit du BDD
Dans le prochain article, nous explorerons comment combiner intelligemment tous ces patterns pour créer un framework de test robuste et évolutif.